The synergy between Kentico EMS and Kentico Cloud
30th of September, 2017 0 comments

The synergy between Kentico EMS and Kentico Cloud

As some time has passed since the launch of Kentico Cloud, the idea of an industry-changing cloud-first approach is finally sinking in. The "traditional CMS vs cloud-first CMS" discussion has been settled, and the differences are clear.

However, some important things to consider are:

  • You don't have to scrap your existing Kentico EMS website and re-build it using Kentico Cloud
  • You can reap the benefits of Kentico Cloud in conjunction with Kentico EMS
  • Using a partial implementation of Kentico Cloud can help you start the journey of omnichannel

There has been content out there regarding this, but today we're tackling how to get it done.

We'll work on the "Dancing Goat" Kentico EMS demo site, and replace one of the sections with content loaded from Kentico Cloud. Specifically, we'll replace the content on the Articles section.

Dancing Goat articles

Kentico Cloud as the source of content

When creating a new Kentico Cloud account, the supplied "Sample Project" contains Dancing Goat sample articles, which we can conveniently re-use.

Kentico Cloud content

Retrieving content in Kentico EMS

To retrieve the content from Kentico Cloud, we will use the Kentico Cloud Delivery .NET SDK. The installation is simple using NuGet. Run this in the NuGet Package Manager Console:

Install-Package KenticoCloud.Delivery

With the SDK installed, we can create a custom data source.

Following the instructions from the Kentico documentation, we'll create a data source webpart. We will ensure we retrieve the main fields of our articles, that will later be rendered by a repeater:

  • Title
  • Summary
  • Post date
  • Teaser image

We begin by creating a user control. Following the guidelines for fetching strongly-typed models, we can create an Article class. The properties of the class should match the codenames of the fields of our article content type in Kentico Cloud. One exception is the teaser image, where we introduce an additional property to extract the URL from the first asset in the list.

public partial class CMSWebParts_Custom_KenticoCloudDataSourceControl : CMSBaseDataSource
{
  public class Article
  {
    public string Title { get; set; }
    public string Summary { get; set; }
    public DateTime PostDate { get; set; }
    
    [JsonProperty("teaser_image")]
    public IEnumerable<Asset> TeaserImageAsset { get; set; }
    public string TeaserImage
    {
      get
      {
        return TeaserImageAsset.First().Url;
      }
    }
  }
  
  . . .

}

This is followed by the code to actually retrieve the content from Kentico Cloud. You should have your project ID ready, as this must be provided to the constructor when creating our instance of DeliveryClient.

public partial class CMSWebParts_Custom_KenticoCloudDataSourceControl : CMSBaseDataSource
{

  . . .
 
  protected override object GetDataSourceFromDB()
  {
    // Create a new instance of the delivery client.
    // The project ID must be provided
    var client = new DeliveryClient("(your project ID)");
    
    // Retrieve Article items
    var response = Task.Run(() => {
      return client.GetItemsAsync<Article>(new EqualsFilter("system.type", "article"));
    }).Result;
    
    DataSource = response.Items;
    
    return DataSource;
  }
}

The final step for our datasource webpart is to include the user control, and wire it up.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="KenticoCloudDataSource.ascx.cs" Inherits="CMSWebParts_Custom_KenticoCloudDataSource" %>
<%@ Register Src="~/CMSWebParts/Custom/KenticoCloudDataSourceControl.ascx" TagPrefix="uc1" TagName="KenticoCloudDataSourceControl" %>

<uc1:KenticoCloudDataSourceControl runat="server" ID="srcCloud" />
using CMS.PortalEngine.Web.UI;

public partial class CMSWebParts_Custom_KenticoCloudDataSource : CMSAbstractWebPart
{
    public override void OnContentLoaded()
    {
        base.OnContentLoaded();
        SetupControl();
    }

    protected void SetupControl()
    {
        if(!StopProcessing)
        {
            srcCloud.FilterName = (string)GetValue("WebPartControlID");
        }
    }
}

The datasource webpart can now be added to the page, alongside a basic repeater that will render the content.

Articles data source

The transformation can use the Eval method with the properties we created in the Article class.

<div class="col-md-3">
  <div class="article-tile">
    <a href="#">
      <img class="article-tile-image" src="<%#Eval("TeaserImage")%>" alt="<%#Eval("Title")%>">
    </a>
    <div class="article-tile-date">
      <%#Eval<DateTime>("PostDate").ToString("MMMM d")%>
    </div>
    <div class="article-tile-content">
      <h2 class="h4"><a href="#"><%#Eval("Title")%></a></h2>
      <p class="article-tile-text"><%#Eval("Summary")%></p>
    </div> 
  </div>
</div>

Dealing with cache

One important aspect of Kentico Cloud is the Webhooks feature. It allows your application to be notified when content is changed in Kentico Cloud. This allows us to clear the cache in our datasource, and load the new data on the next request.

Setting up a web handler for clearing cache

We'll start setting this up by creating a code-only module in Visual Studio. With the code-only module set up, we'll create a web handler, which will be triggered by Kentico Cloud in the form of a webhook notification.

using System.Web;
using CMS.Routing.Web;
using KT.KenticoCloudCache;

[assembly: RegisterHttpHandler("cloud-listener/cache-bust", typeof(CacheBustHandler))]
namespace KT.KenticoCloudCache
{
  public class CacheBustHandler : IHttpHandler
  {
    public bool IsReusable { get { return true; } }
    
    public void ProcessRequest(HttpContext context)
    {
    
    }
  }
}

We can now work on the ProcessRequest method, where we need to accomplish the following:

  • Verify the validity of requests
  • Clear the cache for our articles data source

Looking at the verification hash signing example, we'll create a variation of the GenerateHash method that verifies the request.

private bool VerifyRequest(HttpContext context, string secret)
{
  var requestBody = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding).ReadToEnd();

  // The signature we need to verify
  var signature = context.Request.Headers["X-KC-Signature"];

  var safeUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
  var keyBytes = safeUtf8.GetBytes(secret);
  var messageBytes = safeUtf8.GetBytes(requestBody);
  using (var hmacsha256 = new HMACSHA256(keyBytes))
  {
    var hashMessage = hmacsha256.ComputeHash(messageBytes);
    return Convert.ToBase64String(hashMessage) == signature;
  }
}

Our ProcessRequest method can now use the VerifyRequest method to verify the authenticity, and then touch a cache key for clearing the articles cache.

Open the Webhooks section in Kentico Cloud, and create a new webhook. Set the URL address to our /cloud-listener/cache-bust web handler, and copy the secret key.

Webhook configuration

With the webhook configured, we can verify requests using the secret key, and clear the cache

public void ProcessRequest(HttpContext context)
{
  var requestValid = VerifyRequest(context, "(secret key)");
  if (!requestValid)
  {
    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
    context.Response.Write("Invalid signature");
    context.Response.End();
    return;
  }
  
  CacheHelper.TouchKey("kenticocloud.articles");
}

Wiring up the cache key in the datasource webpart

The kenticocloud.articles cache key needs to be set as a dependency on our datasource webpart. We can do this by adding the key to the list of dependencies (as per the documentation), by overriding the GetDefaultCacheDependencies method.

public partial class CMSWebParts_Custom_KenticoCloudDataSource : CMSAbstractWebPart
{
  public override string GetDefaultCacheDependencies()
  {
    var dependencies = base.GetDefaultCacheDependencies();
    
    if(dependencies != null)
    {
      dependencies += "\n";
    }
    
    dependencies += "kenticocloud.articles";
    
    return dependencies;
  }

  public override void OnContentLoaded()
  {
    base.OnContentLoaded();
    SetupControl();
  }
  
  protected void SetupControl()
  {
    if(!StopProcessing)
    {
      srcCloud.FilterName = (string)GetValue("WebPartControlID");

      srcCloud.CacheItemName = CacheItemName;
      srcCloud.CacheDependencies = CacheDependencies;
      srcCloud.CacheMinutes = CacheMinutes;
    }
  }
}

Done!

With our caching configured, when a change in Kentico Cloud is published, the cache of the datasource is cleared. This ensures optimal performance, while ensuring up-to-date content.

Choose your next step

So, you have established your content hub with Kentico Cloud, your content is available in the form of a mobile app, and your Kentico EMS site displays the same content. At this point, you can start planning the next move.

  • Do you migrate more content to Kentico Cloud?
  • Do you migrate all content to Kentico Cloud?
  • Is the smartest next step to roll out that digital billboard with content coming from Kentico Cloud? Or maybe an Apple Watch app?

A lot of businesses are concerned with the costs and effort required to give the cloud-first paradigm shift a try. This is why a small-scale rollout might be a better approach, with less risk involved.

Written by Kristian Bortnik


Tags

Comments