How to integrate Product Reviews in an Episerver website
In this post, I will go over how I implemented integration between my Episerver website with ProductReview.com.au
There were three things involved in the development:
- Custom Plugin – This really is just a settings page that allows authors to add multiple endpoints/APIs from Product Reviews (i.e. you have multiple products up for reviews)
- Scheduled job that grabs the latest reviews for all your products and stores the response internally
- New block that allows authors to render the outcome of the product review
Two reasons why we opted for the scheduled job approach instead of server-side/AJAX call on render of page are:
- Performance – we don’t have to make an extra server-side call to Product Reviews every time a webpage on your site is rendered since the latest results are already stored internally
- SEO – Google can’t index these reviews if the reviews are loaded through an AJAX call. The reviews have to be part of the first render of the page.
Plugin – Import External Data
public class ExternalData : BaseDbEntity
{
public string Source { get; set; }
public string Endpoint { get; set; }
public int DataType { get; set; }
public string Response { get; set; }
public DateTime? ImportDate { get; set; }
}
public class ExternalDataMap : BaseEntityTypeConfiguration
{
public ExternalDataMap()
{
ToTable("Module_ExternalData");
Property(e => e.Response).IsMaxLength();
}
}
@model List<Fusion.Cms.Core.Components.DataImportHandler.Models.ExternalData>
@using System.Web.Mvc
@using System.Web.Mvc.Html
@using EPiServer.Framework.Web.Resources
@using Fusion.Cms.Core.Components.DataImportHandler.Models
<!DOCTYPE html>
<html>
<head>
<title>Import External Data</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<!-- Shell -->
@Html.Raw(ClientResources.RenderResources("ShellCore"))
<!-- LightTheme -->
@Html.Raw(ClientResources.RenderResources("ShellCoreLightTheme"))
<link href="/EPiServer/CMS/App_Themes/Default/Styles/system.css" type="text/css" rel="stylesheet">
<link href="/EPiServer/CMS/App_Themes/Default/Styles/ToolButton.css" type="text/css" rel="stylesheet">
</head>
<body>
<div class="epi-contentContainer epi-padding">
<div class="epi-contentArea">
<h1 class="EP-prefix">External Data Import</h1>
<p>Manage your external data imports here.</p>
</div>
<div class="epi-formArea">
<form action="@Url.Action("Save","DataImportPlugin")" method="post">
<div id="FullRegion_panelPageTypes">
<table class="epi-default " cellspacing="0" border="0" style="border-style: none; border-collapse: collapse;">
<tbody>
<tr>
<th scope="col">Name</th>
<th scope="col">Source Endpoint</th>
<th scope="col">Type</th>
<th scope="col">Last import</th>
</tr>
@if (Model.Any())
{
for (var i = 0; i < Model.Count; i++)
{
<tr>
<td class="nowrap">
<input type="hidden" name="postedData[@i].Id" value="@Model[i].Id"/>
<input type="text" value="@Model[i].Source" name="postedData[@i].Source"/>
</td>
<td>
<input type="text" value="@Model[i].Endpoint" name="postedData[@i].Endpoint"/>
</td>
<td>
@Html.DropDownListFor(m => Model[i].DataType, ExternalDataTypes.ProductReview.GetSelectListItems(), new {@name ="postedData[" + i + "].DataType"})
</td>
<td>@(Model[i].ImportDate.HasValue ? Model[i].ImportDate.Value.ToString("G") : "--")</td>
</tr>
}
}
<tr>
<td class="nowrap">
<input type="hidden" name="postedData[@Model.Count].Id" value="0"/>
<input type="text" value="" name="postedData[@Model.Count].Source" placeholder="Add new source..." />
</td>
<td>
<input type="text" value="" name="postedData[@Model.Count].Endpoint" placeholder="Enter source URL"/>
</td>
<td>
@Html.DropDownList("postedData[@Model.Count].DataType", ExternalDataTypes.ProductReview.GetSelectListItems())
</td>
<td>--</td>
</tr>
</tbody>
</table>
</div>
<div class="epi-buttonContainer">
<span class="epi-cmsButton">
<input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Edit" type="submit" name="Save" value="Save" title="Save" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)">
</span>
</div>
</form>
</div>
</div>
</body>
</html>
[GuiPlugIn(DisplayName = "Import External Data",
Url = "/EPiServer/DataImport/DataImportPlugin",
Area = PlugInArea.AdminMenu)]
[Authorize(Roles = "CmsAdmins")]
public class DataImportPluginController : Controller
{
private readonly DataImportService _dataImportService;
public DataImportPluginController(DataImportService dataImportService)
{
_dataImportService = dataImportService;
}
// GET: DataImportPlugin
public ActionResult Index()
{
var model = _dataImportService.GetAllImportedData();
return View("~/Components/DataImportHandler/Views/Index.cshtml", model);
}
public ActionResult Save(List postedData)
{
foreach (var data in postedData)
{
if (!string.IsNullOrEmpty(data.Source) && !string.IsNullOrEmpty(data.Endpoint))
{
if (data.Id == 0)
{
data.ImportDate = DateTime.Now.AddMinutes(30);
_dataImportService.InsertDataImport(data);
}
else
{
_dataImportService.UpdateDataImport(data);
}
}
}
return RedirectToAction("Index");
}
}
public class DataImportService :IQueryService
{
private IDbContextScopeFactory _dbContextScopeFactory;
private IRepository _dataImportRepository;
public DataImportService(
IDbContextScopeFactory dbContextScopeFactory,
IRepository dataImportRepository
{
_dbContextScopeFactory = dbContextScopeFactory;
_dataImportRepository = dataImportRepository;
}
public virtual List GetAllImportedData()
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var dbContext = dbContextScope.DbContexts.Get();
return dbContext.Set().ToList();
}
}
public virtual List GetImportedData(int dataType)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var dbContext = dbContextScope.DbContexts.Get();
return dbContext.Set().Where(ed => ed.DataType == dataType).ToList();
}
}
public virtual ExternalData GetDataImport(int id)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
return _dataImportRepository.GetById(id);
}
}
public virtual void InsertDataImport(ExternalData externalData)
{
if (externalData == null)
{
throw new ArgumentNullException("externalData");
}
using (var dbContextScope = _dbContextScopeFactory.Create())
{
_dataImportRepository.Insert(externalData);
dbContextScope.SaveChanges();
}
}
public virtual void UpdateDataImport(ExternalData externalData)
{
if (externalData == null)
{
throw new ArgumentNullException("externalData");
}
using (var dbContextScope = _dbContextScopeFactory.Create())
{
_dataImportRepository.Update(externalData);
dbContextScope.SaveChanges();
}
}
public virtual void DeleteFileUpload(ExternalData externalData)
{
if (externalData == null)
{
throw new ArgumentNullException("externalData");
}
using (var dbContextScope = _dbContextScopeFactory.Create())
{
_dataImportRepository.Delete(externalData);
dbContextScope.SaveChanges();
}
}
}
Scheduled Job
[ScheduledPlugIn(DisplayName = "External Data Importer")]
public class DataImportScheduledJob : ScheduledJobBase
{
private readonly DataImportService _dataImportService;
private bool _stopSignaled;
public DataImportScheduledJob(DataImportService dataImportService)
{
_dataImportService = dataImportService;
IsStoppable = true;
}
public override void Stop()
{
_stopSignaled = true;
}
public override string Execute()
{
//Call OnStatusChanged to periodically notify progress of job for manually started jobs
OnStatusChanged(String.Format("Starting execution of {0}", this.GetType()));
var endpoints = _dataImportService.GetAllImportedData();
foreach (var endpoint in endpoints.Where(e => !string.IsNullOrEmpty(e.Endpoint)))
{
OnStatusChanged(String.Format("Importing data for {0}", endpoint.Source));
using (var webClient = new WebClient())
{
endpoint.Response = webClient.DownloadString(endpoint.Endpoint);
endpoint.ImportDate = DateTime.Now;
_dataImportService.UpdateDataImport(endpoint);
}
if (_stopSignaled)
{
return "Stop of job was called";
}
}
return "All configured external data have been imported";
}
}
Product Review Block
And this is what the new block looks like:
Submit your review | |
Nicola Ayan - Blog
Average rating: 0 reviews