Recently, I found out about the ContentKing CMS API that allows the user to trigger priority auditing of a page through an API. Basically telling ContentKing that a page has changed, and requesting re-evaluation of the SEO score.
In this blog I will explain how I integrated the CMS API in an episerver project.
Publish event
Firstly I needed to hook up to the Episerver publish event. This I did with an Initializable Module.
[InitializableModule] [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] public class PublishEventInitializationModule : IInitializableModule { public void Initialize(InitializationEngine context) { var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>(); contentEvents.PublishingContent += contentEvents_PublishingContent; } public void Uninitialize(InitializationEngine context) { var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>(); contentEvents.PublishingContent -= contentEvents_PublishingContent; } void contentEvents_PublishingContent(object sender, EPiServer.ContentEventArgs e) { var contentKingService = ServiceLocator.Current.GetInstance<IContentKingService>(); contentKingService?.TriggerPageAudit(e.Content); } }
When a page is published now the ContentKingService is called to trigger page auditing. The service reads the ContentKing API key needed using a settingsservice, and uses the UrlHelper and ISiteDefinitionResolver to retrieve the full url of the page published. Then does a POST to the ContentKing API using an HttpClient.
[ServiceConfiguration(ServiceType = typeof(IContentKingService), Lifecycle = ServiceInstanceScope.HttpContext)] public class ContentKingService : IContentKingService { private readonly UrlHelper _urlHelper; private readonly ISiteDefinitionResolver _siteDefResolver; private readonly ISettingsService _settingsService; private string EndPointUrl = "https://api.contentkingapp.com/"; public ContentKingService() { _urlHelper = ServiceLocator.Current.GetInstance<UrlHelper>(); _siteDefResolver = ServiceLocator.Current.GetInstance<ISiteDefinitionResolver>(); _settingsService = ServiceLocator.Current.GetInstance<ISettingsService>(); } public void TriggerPageAudit(IContent content) { var settings = _settingsService.GetSettings(); try { if (settings != null && settings.TriggerAnalyzeOnPublish && !string.IsNullOrEmpty(settings.CmsApiKey)) { var fullPageUrl = ResolveUrl(content); var url = EndPointUrl + "v1/check_url"; var requestHeaders = new NameValueCollection { { "Authorization", $"token {settings.CmsApiKey}" } }; var json = JsonConvert.SerializeObject(new { url = fullPageUrl }); var result = PostObject<object>(url, json, requestHeaders); } } catch { //ToDo Do something with exceptions.. } } public string ResolveUrl(IContent content) { var contentUrl = _urlHelper.ContentUrl(content.ContentLink); if (contentUrl.Contains("//")) return contentUrl; return new Uri(_siteDefResolver.GetByContent(content.ContentLink, true, true).SiteUrl, contentUrl).AbsoluteUri; } private static T PostObject<T>(string url, string jsonContent, NameValueCollection requestHeaders = null) { var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json"); using (var client = new HttpClient()) { if (requestHeaders != null && requestHeaders.Count > 0) { foreach (string key in requestHeaders.Keys) { client.DefaultRequestHeaders.Add(key, requestHeaders[key]); } } client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var httpResponse = client.PostAsync(url, httpContent).Result; if (!httpResponse.IsSuccessStatusCode) throw new Exception($"{httpResponse.StatusCode} | {httpResponse.ReasonPhrase}"); return httpResponse.Content.ReadAsAsync<T>().Result; } } }
CMS Admin Page
To make the integration more flexible I wanted to create a page in the cms-admin section of Episerver to manage the ContentKing API key, and enable or disable the triggering. To create a page in cms-admin section I created a controller for the page and added the EPiServer.PlugIn.GuiPlugIn attribute.
[EPiServer.PlugIn.GuiPlugIn( Area = EPiServer.PlugIn.PlugInArea.AdminMenu, Url = "/ContentKingAdmin/Index", DisplayName = "ContentKing Admin")] public class ContentKingAdminController : Controller { private readonly ISettingsService _settingsService; public ContentKingAdminController() { _settingsService = ServiceLocator.Current.GetInstance<ISettingsService>(); } public ActionResult Index() { var model = _settingsService.GetSettings(); return View(model); } [System.Web.Mvc.HttpPost] public ActionResult Save(SettingsModel model) { _settingsService.SaveSettings(model); return RedirectToAction("Index"); } }
Then I created a view to create a ui for the settings. (I stole some styling from another episerver admin page)
@{ Layout = null; } @model AlloyContentKing.Models.SettingsModel <!DOCTYPE html> <html> <head> <title></title> <!-- Mimic Internet Explorer 7 --> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"> <link rel="stylesheet" type="text/css" href="/EPiServer/Shell/11.19.1/ClientResources/epi/themes/legacy/ShellCore.css"> <script type="text/javascript" src="/EPiServer/Shell/11.19.1/ClientResources/ShellCore.js"></script> <link rel="stylesheet" type="text/css" href="/EPiServer/Shell/11.19.1/ClientResources/epi/themes/legacy/ShellCoreLightTheme.css"> <script type="text/javascript" src="/EPiServer/CMS/11.19.1/ClientResources/ReportCenter/ReportCenter.js"></script> <link href="../../../App_Themes/Default/Styles/system.css" type="text/css" rel="stylesheet"> <link href="../../../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">ContentKing Settings</h1> <p class="EP-systemInfo"> Settings for ContentKing module. </p> </div> <div class="epi-formArea"> <form id="cmsApiSettings" method="POST" action="\ContentKingAdmin\Save"> <strong>ContentKing CMS Api</strong> <div class="epi-size20"> <div> <label>Trigger Page Analize on Publish</label> @Html.EditorFor(x => Model.TriggerAnalyzeOnPublish) </div> <div> <label>ContentKing CMS Api Key</label> @Html.EditorFor(x => Model.CmsApiKey) </div> </div> <div class="epi-buttonContainer"> <span class="epi-cmsButton"> <input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Save" type="submit" name="ctl00$FullRegion$MainRegion$ImportFile" id="FullRegion_MainRegion_ImportFile" value="Save" title="Save"> </span> </div> </form> </div> </div> </body> </html>
The Admin page looks like this:
Dynamic Data Store
To store the settings I use Episerver Dynamic Data Store (DDS). To store data in the DDS you first need to create a model that Implements IDynamicData
public class SettingsModel : IDynamicData { public Identity Id { get; set; } public bool TriggerAnalyzeOnPublish { get; set; } public string CmsApiKey { get; set; } }
The SettingsService reads or writes the settings from and to the DDS.
[ServiceConfiguration(ServiceType = typeof(ISettingsService), Lifecycle = ServiceInstanceScope.Singleton)] public class SettingsService : ISettingsService { private Guid _settingsId = new Guid("05663fcf-9f39-4fc2-af49-bddfb76953e0"); public SettingsModel GetSettings() { var store = DynamicDataStoreFactory.Instance.CreateStore(typeof(SettingsModel)); return store.Items<SettingsModel>().FirstOrDefault(x => x.Id.ExternalId == _settingsId); } public void SaveSettings(SettingsModel model) { model.Id = _settingsId; var store = DynamicDataStoreFactory.Instance.CreateStore(typeof(SettingsModel)); store.Save(model); } }
This concludes my first ever blog. Let me know what you think or if you have any questions!