Optimizely CMS 13 First Impressions

Optimizely CMS 13 First Impressions

Introduction

This week Optimizely released the long awaited CMS 13. I upgraded my Optimizely extensions package to CMS 13 and found some interesting things I needed to do to update my package. I will use this blog post to document some of those findings.

Sites deprecation in favor of applications

This change is probably the biggest change that will affect the upgrade to the newest version. When Optimizely created their SAAS platform they used the PAAS product as their base. In the SAAS world there is no notion of site becasue the web application is not hosted on the app service like the PAAS product. Those applications was created to replace sites so it could handle both use cases. This means everywhere ISiteResolver ans ISiteRepository is used will need to be updated the new APIs.

ISiteDefinitionResolver to IApplicationResolver

This is a pretty straightforward replacement. If you are still working with sites you can cast to InProcessWebsite to get access to the Hosts collection for the site. The nice thing about the new APIs are there are async methods as well.

var sitebyHostname = _applicationResolver.GetByHostname("hostname.com", false) as InProcessWebsite;
var siteByContent = _applicationResolver.GetByContent(new ContentReference()) as InProcessWebsite;

//Gets the the application from the PageHelper 
 var siteByContext = _applicationResolver.GetByContext() as InProcessWebsite;

ISiteDefinitionRepository to IApplicationRepository

This is a pretty straightforward replacement. If you are still working with sites you can cast to InProcessWebsite to get access to the Hosts collection for the site. The nice thing about the new APIs are there are async methods as well.

 var site = _applicationRepository.Get("siteName") as InProcessWebsite;
 var sites = _applicationRepository.List();

SiteDefinition.Current.StartPage

Use ContentReference.StartPage

Subscribing and Publising Events

The event system has been overhauled as well for CMS 13. In my extensions I was listening and publish events when a site had been updated. Here is how I updated to use the new sytem.

Add IEventSubscriber to Class Definition

In my extensions I was listening to the following events.

public Service(ISiteDefinitionEvents siteDefinitionEvents)
{
    private readonly ISiteDefinitionEvents _siteDefinitionEvents = siteDefinitionEvents;

    private void Init()
    {
        _siteDefinitionEvents.SiteCreated += SiteCreated;
        _siteDefinitionEvents.SiteUpdated += SiteUpdated;
        _siteDefinitionEvents.SiteDeleted += SiteDeleted;
    }

}

We need subscribe to these in a different way in CMS 13

public Service(ISiteDefinitionEvents siteDefinitionEvents) :    
    IEventSubscriber<ApplicationCreatedEvent>,
    IEventSubscriber<ApplicationUpdatedEvent>,
    IEventSubscriber<ApplicationDeletedEvent>
{
    private readonly ISiteDefinitionEvents _siteDefinitionEvents = siteDefinitionEvents;

    public Task HandleAsync(ApplicationCreatedEvent eventData, EventContext context, CancellationToken cancellationToken = default)
    {
        if (context.Broadcasted)
        {
            //handle on;y broadcast events
        }
        return Task.CompletedTask;
    }

    public Task HandleAsync(ApplicationDeletedEvent eventData, EventContext context, CancellationToken cancellationToken = default)
    {
        if (context.Broadcasted)
        {
            //handle on;y broadcast events
        }
        return Task.CompletedTask;
    }

    public Task HandleAsync(ApplicationUpdatedEvent eventData, EventContext context, CancellationToken cancellationToken = default)
    {
        if (context.Broadcasted)
        {
            //handle on;y broadcast events
        }
        return Task.CompletedTask;
    }

}

Custom Event Class

If you are creating your own event data type you will need to adjust the class

[DataContract]
[EventsServiceKnownType]
public class SettingEventData : IEventData
{
    [DataMember]
    public string? SiteId { get; set; }

    [DataMember]
    public string? ContentId { get; set; }

    [DataMember]
    public string? Language { get; set; }
}

Update to

[DataContract]
[EventData("b29b8aef-2a17-4432-ad16-8cd6cc6953e3", Broadcast = true)]
public class SettingEventData : IEventData
{
    [DataMember]
    public string? SiteId { get; set; }

    [DataMember]
    public string? ContentId { get; set; }

    [DataMember]
    public string? Language { get; set; }
}

Publish Event

Update your code to publsih events

IEventRegistry.Get(eventGuid).Raise(raiserId, data)

update to

_eventPublisher.PublishAsync(new SettingEventData
{
    SiteId = id,
    ContentId = e.Content.ContentGuid.ToString()
}).GetAwaiter().GetResult();

Register subsubscriptions

Add the following to your startup configuration

services.AddCmsEvents()
    .AddCmsEventType<SettingEventData>()
    .AddCmsEventSubscriber<ApplicationCreatedEvent, Service>()
    .AddCmsEventSubscriber<ApplicationUpdatedEvent, Service>()
    .AddCmsEventSubscriber<ApplicationDeletedEvent, Service>()
    .AddCmsEventSubscriber<SettingEventData, Service>();

Resolving Mime Types

Instead of using IMimeTypeResolver we need to use the StaticFileOptions

//make sure a content type provider is registered in startup
services.PostConfigure<StaticFileOptions>(options => options.ContentTypeProvider ??= new FileExtensionContentTypeProvider());

//use content type prvoider

public class ExplorerController(IOptions<StaticFileOptions> options) : Controller
{
    private readonly StaticFileOptions _options = options.Value;

    public async Task<IActionResult> Index()
    {
        var file = await getFile();
        var contentType = _mimeTypeResolver.ContentTypeProvider.TryGetContentType(file.Path, out var mimeType) ? mimeType : "application/octet-stream";

        Response.Headers.Append("Content-Disposition", new System.Net.Mime.ContentDisposition
        {
            FileName = file.Name,
            Inline = true,
        }.ToString());

        return File(file.OpenRead(), contentType);
    }
}

Addon Menu Updates

When creating menu options and views for your addons, some updates are needed to show the menu. They have really simplified the process for addon developers.

Update the Layout in your views to point to the CMS UI provided one.

@using EPiServer.Shell.Navigation
@using EPiServer.Framework.Web.Resources
@using EPiServer.Framework.Localization
@using Microsoft.AspNetCore.Antiforgery;
@addTagHelper *, EPiServer.Shell.UI
@model StorageExplorerViewModel
@{
    Layout = "/CmsUIViews/Views/Shared/CommonLayout.cshtml";
}

@section title
{
    <title>Storage Explorer</title>
}

@section header
{
    @ClientResources.RenderResources("storageexplorer", new[] { ClientResourceType.Style })
    <style>
        body {
            margin: 0;
        }
    </style>
}

@section mainContent
{
    <div id="root"
         data-epi-antiforgery-header-name="@(Model.AntiforgeryOptions?.HeaderName ?? "")"
         data-epi-antiforgery-form-field-name="@(Model.AntiforgeryOptions?.FormFieldName ?? "")"
        >
    </div>
    @ClientResources.RenderResources("storageexplorer", new[] { ClientResourceType.Script })
}
Share :

I am senior solution architect working at Perficient helping brands create exceptional digital experiences for their customers. I am a full stack developer who enjoys the full software development lifecycle. I have expertise in Optimizely, the Azure cloud stack, React, NextJS, CI/CD pipelines, test driven development and solution architecture.

Related Posts

Introducing lunchin Optimizely Cloud Extensions

Introducing lunchin Optimizely Cloud Extensions

Today I proud to announce the release of two new packages to the Optimizely NuGet feed.

Read More