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:

  1. 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)
  2. Scheduled job that grabs the latest reviews for all your products and stores the response internally
  3. 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:

One Comment

Add a Comment

Your email address will not be published. Required fields are marked *