Support for Block as Property in the Episerver Content Delivery API

Since Episerver 7, CMS has support to add Blocks as Properties. This helps you to group Properties easier as well as reusing code.

Unfortunately the Episerver Content Delivery API does not support these blocks. They are simply not serialized into the JSON response.

In my example I will use the StartPage from the Alloy Templates that has the Block SiteLogotypeBlock added as a Property:

public virtual SiteLogotypeBlock SiteLogotype { get; set; }

When I use the Content Delivery API to request the Start Page, I don’t see it in the JSON response.

no logo

The structure of Block as Property

Without digging too deep into how Episerver structure the Blocks when they are added as properties, you can find that the PropertyData for the SiteLogotypeBlock is defined as BlockProperty.

Normally I would simply need to create a PropertyModel that handles BlockProperty and handles the Block property values similar to how I add PropertyModels with complex value models.

But I don’t want to do that for every Block Type that might be added as a Block!

Extending with a PropertyModelConverter

Fortunately, all BlockProperty inherit from BlockProperty!

This makes it easier for me to create my own PropertyModelConverter (the part responsible of finding a PropertyModel for the PropertyData) and check if the current PropertyData is assignable from BlockProperty.

Default:

propertyModel.PropertyType == propertyData.GetType()

Which does not consider heritance.

My implementation:

propertyData is PropertyBlock;

With this, I can take any BlockProperty into my own PropertyModel.

The BlockPropertyModel

What I did was to create a PropertyModel that is using the ContentModelMapper to serialize the Block. This is the same function that is used to make the requested Content into a serializable model.

Unfortunately the ContentModelMapper can only handle IContent. PageData implements IContent and so does Blocks that are created in the Assets Pane due to Episerver’s runtime proxy.

However Blocks that are added as Properties does not implement IContent.

Dirty workaround ;)

So how can I ask the ContentModelMapper to map my non-IContent Block? This is not the best solution I’ve found but I ask the IContentRepository to create a Default instance of the Block Type in the same way as if I would create a block programmatically.

var fakeContent = this.contentRepository.GetDefault(ContentReference.SiteBlockFolder, contentTypeId);

After that I copy all of the property values from the original Block into my fake instance.

var propertyDataCollection = block.Property.CreateWritableClone();
foreach (var propertyData in propertyDataCollection)
{
    fakeContent.Property.Set(propertyData.Name, propertyData);
}

And finally I ask the ContentModelMapper to map this into a ContentModel.

this.contentModelMapper.TransformContent((IContent)fakeContent);

After cleaning upp things that I don’t really need like Name, ParentLink etc. I can use this as the Value of the PropertyModel.

Summary

To be able to accomplish this, I needed to be able to find a way to handle all different types of PropertyBlocks and be able to serialize the actual Block Types.

Using the BlockPropertyModelConverter I am able to map the BlockProperty of any type into my BlockPropertyModel, which in turn use BlockModelMapper to create a serializable model using the normal ContentModelMapper.

Now I will get any Block that is added as a Property serialized as JSON into the Content Delivery API Response!

logo

If you want to understand more about the Episerver Content Delivery API serializing, you can read it in the documentation.

Advertisements

Prevent Episerver ContentDelivery to hijack all application/json requests

In one of my projects where we are using the Content Delivery API from Episerver, we also have some custom logic in the templates for some Ajax posting.

A JavaScript makes a POST request to the current url and expects a json response, therefore the request has an Accept Header with the value: application/json.

contentdeliveryapi post request

Issue

This will respond with a 405 error with the message HTTP verb used to access this page is not allowed.

405 error.PNG

This might not be best practise to shoot XHR requests to the same request and manage the response in the same code that also handles the HTML rendering, but that is how it was build back then.

Looking into the binaries, I managed to find that this is caused by something in the EPiServer.ContentApi.Cms.dll binary.

Cause

I used DotPeek to look what’s going on in that file and could see that on initialization, the ContentDelivery attaches to the RoutedContent event on the IContentRouteEvents interface.

When content is Routed the method ContentApiRouteService.ShouldRouteRequest is used to check whether or not this request should be rewritten to use the ContentDelivery API instead.

The default implementation of ContentApiRouteService checks whether the Accept Header contains “application/json”. Which is the case for of our current implementation.

With rewritten I mean that instead of the normal request destination which usually is your WebForms ASPX template or MVC Controller, the request should be routed to the ContentDelivery API at /api/episerver/v2.0/.

Solution

As we are depending on the request to reach our template, we need to disable this. The best way I could find is to override the ContentApiRouteService .ShouldRouteRequest method.

I replaced it with a method that just returned false. I never want the request to be routed to the ContentDelivery API. When I use the ContentDelivery API, I use the endpoint found at /api/episerver/v2.0.

You can find how I override the implementation of ContentApiRouteService at GitHub together with how I replace it in the IoC container using a Configurable Module.

Personally I think this is a case where Episerver disturbs our default rendering and I would prefer that

1) the ContentDelivery routing for requests with Accept Header “application/json” is an opt-in configuration as it is interfering with our

2) or that the routing is configurable so that I can opt-out without having to write code that overrides the default behaviour

Getting a previewable property value with UIHint.PreviewableText

You might have seen that the field “Name in URL” first gives you the value and the editor needs to click “change” to edit it.
nameinurl

You can also accomplish this on your normal string properties by using the UI Hint “previewabletext” (also found as the constant UIHint.PreviewableText).

[Display(Name = "My Property")]
[UIHint(UIHint.PreviewableText)]
public virtual string MyProperty { get; set; }

And it will look like this:
previewable

Adding ImageVault buttons to TinyMCE 2 in Episerver

With the new TinyMCE version in Episerver 11, there are some new changes on how to set up your toolbars.

Episerver has documented it quite well and how you set it up in the back end is quite similar to how you would set it up using TinyMCE’s JavaScript API.

But it took a while until I could find how to add my ImageVault buttons.

Finally I found that I need to add these:

  • imagevault-insert-media
  • imagevault-edit-media

They will give you the ImageVault buttons you’ve always been waiting for!

Tweaking and extending serialization from Episerver Content Delivery API

As you’ve probably heard, Episerver is creating a Headless CMS API called “Content Delivery API“.

With this, you can get, list and search (requires Episerver Find) content from Episerver in a JSON format.

Matthew Mols has some blog posts about how to get started: Getting started with the Episerver Content Delivery API and Add HTTP Caching to the Episerver Content Delivery API.

I just started looking at it myself and found some basic ways how to tweak the output of the API when it comes to Content and Properties.

Basics

A simplified description of what you get from the API is a JSON serialized ContentApiModel. ContentApiModel is basically a copy of the Content populated depending on what content interfaces your content implements.

The model returned is

  • If the content implements ILocale, set the Language property
  • If the content implements ILocalizable, set the MasterLanguage and ExistingLanguage properties
  • If the content implements IVersionable, set the properties StartPublish, StopPublish as well as Status
  • If the content implements ICategorizable, add a CategoryPropertyModel to the ContentApiModel.Properties property

Also, for each PropertyData in your Content.Property try to find a registered IPropertyModelHandler that has an TypeModel that can handle the Property Type.

TypeModel you say?

The TypeModel contains three things:

Type Name Description
Type PropertyType Which Property Type this TypeModel can support. For example PropertyContentReference, PropertyXhtmlString etc.
Type ModelType The type of the class that takes care of the rendering. Needs to implement the Interface IPropertyModel.
string ModelTypeString The name of the PropertyModel, normally the same name as the name of the ModelType.

PropertyModel

This is a ContentDelivery version of the PropertyData. This is what is rendered as a JSON for each Property on your Content.

For example the PropertyNumber would be represented by NumberPropertyModel. The NumberPropertyModel has a Value (the numeric value of your property) and a PropertyDataType which usually is the name of the value type (in this case “PropertyNumber”).

How do I add Custom Properties or replace the JSON output?

Adding for new Properties

For example if I’d be using ImageVault including their custom Property Types, the property won’t be displayed as no IPropertyModelHandler that can handle PropertyMedia.

Instead of creating your own implementation of IPropertyModelHandler, you can just add more TypeModels to the existing one in an InitializationModule.

So let’s add a ModelType for PropertyMedia in the InitializationModule. Mimicking how Episerver handles their common Property types, the JSON output will look like this:

"Image": {
    "Value": {
        "Id": 33114,
        "Effects": [],
        "IsReadOnly": true,
        "IsModified": false
    },
    "PropertyDataType": "PropertyMedia"
}

Which isn’t really helpful since ImageVault images are not served as content so we can’t use that ID with the ContentDeliveryApi. We need to add a little extra information so let’s create a new PropertyModel that will wrap the MediaReference, but also add a Url to the image.

Replacing for existing Properties

This is the same procedure but instead of creating a new TypeModel, just replace the value of the ModelType property with an own Type in the InitializationModule.

Check an example of InitializationModule to add or replace properties.

The toolbox for PropertyModels

There are some nice base classes that you can play with. The key Interface you need to work with is the IPropertyModel:

IPropertyModel

This is the most basic Interface that you need to implement according to the default PropertyModelHandler. It only contains two properties:

Type Name Description
string Name The name of the Property Model, usually the name of the Episerver Property type.
string PropertyDataType The name of the Property Model, usually the name of the Episerver Property type.

What I can see when reflecting Episerver’s code, it seems like your implementation also need a constructor with a parameter with the PropertyData.

public class MyPropertyModel : IPropertyModel
{
    public MyPropertyModel(MyPropertyData property)
    {
        // Do something to the property, and add the output to Properties for serialization
    }

    public string Name { get; set; }
    public string PropertyDataType { get; set; }
}

For your convenience, Episerver have a base class for this: EPiServer.ContentApi.Core.PropertyModel where TValue is the value you should output in your JSON, and TType is the PropertyData. Check some examples where I’m serializing the Property value or even serializing with extra information.

As the PropertyModel is serialized to a JSON, all properties on your implementation will be included in the result (except if you decorate the property with the JsonIgnore attribute – see below).

IPersonalizableProperty

If the output of your Custom Property depends on personalization, implement the IPersonizableProperty. The interface only requires a Property called ExcludePersonalizedContent, but it seems like your implementation need a constructor with the PropertyData AND a boolean.

public class MyPersonalizedPropertyModel : IPropertyModel, IPersonizableProperty
{
    public MyPersonalizedPropertyModel(MyPropertyData property, bool excludePersonalizedContent)
    {
        if (excludePersonalizedContent)
        {
            // add logic to exclude personalized content
        }
    }

    public string Name { get; set; }
    public string PropertyDataType { get; set; }
    public bool ExcludePersonalizedContent { get; set; }
}

For your convenience, Episerver also have a base class for this: EPiServer.ContentApi.Core.PersonalizablePropertyModel where TValue is the value you should output in your JSON, and TType is the PropertyData.

IExpandableProperty

IExpandabeProperty is used for properties that where you can return a simplified set of data for the initial API Request, so that the Client can make further requests to dig deeper into the data.

ContentAreas and ContentReferences are two examples for this. The first response for a ContentArea would look like this:

"MyContentArea": {
    "Value": [
        {
            "ContentLink": {
                "Id": 9,
                "WorkId": 0,
                "GuidValue": "5f3d81e6-28f8-4f16-b998-87378fc9c4d6",
                "ProviderName": null
            },
            "DisplayOption": "",
            "Tag": null
        }
    ],
    "PropertyDataType": "PropertyContentArea"
}

But if it would be expanded by having the parameter ?expand=Steps or ?expand=*, the JSON output for the ContentArea would look like this:

"MyContentArea": {
    "ExpandedValue": [ // When expanding, expanded information goes here
        {
            "ContentLink": {
                "Id": 9,
                "WorkId": 0,
                "GuidValue": "5f3d81e6-28f8-4f16-b998-87378fc9c4d6",
                "ProviderName": null
            },
            "Name": "My Block",
            "Language": {
                "DisplayName": "English",
                "Name": "en"
            },
            "ExistingLanguages": [
                {
                    "DisplayName": "English",
                    "Name": "en"
                }
            ],
            "MasterLanguage": {
                "DisplayName": "English",
                "Name": "en"
            },
            "ContentType": [
                "Block",
                "MyBlock"
            ],
            "ParentLink": {
                "Id": 8,
                "WorkId": 0,
                "GuidValue": "87a9ae8a-a2a8-40e5-8c09-5c5c55a73e17",
                "ProviderName": null
            },
            "RouteSegment": null,
            "Url": null,
            "Changed": "2018-04-09T16:05:01Z",
            "Created": "2018-04-09T16:05:01Z",
            "StartPublish": "2018-04-09T16:05:01Z",
            "StopPublish": null,
            "Saved": "2018-04-09T16:05:34Z",
            "Status": "Published",
            "Category": {
                "Value": [],
                "PropertyDataType": "PropertyCategory"
            },
            // Other properties goes here
        }
    ],
    "Value": [ // Here goes the original value before expanding
        {
            "ContentLink": {
                "Id": 9,
                "WorkId": 0,
                "GuidValue": "5f3d81e6-28f8-4f16-b998-87378fc9c4d6",
                "ProviderName": null
            },
            "DisplayOption": "",
            "Tag": null
        }
    ],
    "PropertyDataType": "PropertyContentArea"
}

Note that expanding will only dig through the first level of nested Content.

[JsonIgnore]

If you have Properties on your ModelType that you don’t want to be included in the JSON output, just decorate it with the JsonIgnore Attribute.

Summary

I hope you learned something new about Episerver’s Content Delivery API. It’s still kind of new and the documentation is not very good yet.

If you have any tips and tricks, feel free to blog about it and give me a shout! Always nice learning something new!

Differences between DeletingContent/DeletedContent between emptying trash and deleting content

We stumbled upon an area where the DeletingContent and DeletedContent events slightly differently when you’re deleting content through the IContentRepository, manually emptying the trash and the scheduled job “Automatic Emptying of Trash”.

Let’s look at what the DeletingContent and DeletedContent events are.

There are multiple articles and blog posts that mention this but simply these are simple event delegates when content are changed. In this case I’m interested in to do things when content is deleted. Note that moving something to the Trash in the UI is not considered as “deleting content”.

Just add an InitializationModule and hook up your events:

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class InitializationModule : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var contentEvents = ServiceLocator.Current.GetInstance();
        contentEvents.DeletingContent += OnDeletingContent;
        contentEvents.DeletedContent += OnDeletedContent;
    }

    private void OnDeletedContent(object sender, DeleteContentEventArgs deleteContentEventArgs)
    {
    }

    private void OnDeletingContent(object sender, DeleteContentEventArgs deleteContentEventArgs)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}

As you can see there is a class called “DeleteContentEventArgs“. This contains information for the content:

Name Summary
CancelAction Set value to abort the current event handling
CancelReason Gets or sets the reason for cancel.
Content The content that the event applies to
ContentLink The Content that the event applies to
Creator This property keeps track of the class/instance that created the page object.
DeletedDescendents Gets or sets a list of references to any descendents that will also be deleted from this delete operation.
Items Gets a key-value collection that can be used to organize and share data between events handlers during an event chain.
RequiredAccess The required access that the event applies to.
TargetLink The parent that the event applies to

Compared to most other Content Events, the DeleteContentEventArgs.Content property is null. If you’re insterested in the content you are deleting, you’ll need to use IContentLoader to get it based on DeleteContentEventArgs.ContentLink.

The special thing with DeleteContentEventArgs is that the DeleteContentEventArgs.DeletedDescendents contains ContentReferences for all content that are descendants to the content that are to be deleted.

Here is what’s different depending on how you’re deleting

IContentRepository.Delete

Let’s start with the easy thing. IContentRepository.Delete to delete your content.

var pageReference = new PageReference(133);
var contentRepository = ServiceLocator.Current.GetInstance();
contentRepository.Delete(pageReference, false);

In this case, the ContentLink is the page you’re deleting is added to DeleteContentEventArgs.ContentLink and all content beneath the content are added to the DeleteContentEventArgs.DeletedDescendents property, similar to the image above.

Scheduled Job: Automatic Emptying of Trash

This job deletes all content that was moved to the Trash more than 30 days ago.

For each child to the Trash node (usually Id 2), delete it and the DeleteContentEventArgs.ContentLink is set to that child. All content beneath that child are added to the DeletedDescendents property.

Quite straight forward, right?

Manually emptying Trash

This is where things are different.

Instead of setting DeleteContentEventArgs.ContentLink to the content that are to be deleted, the ContentReference to the Trash node is set to DeleteContentEventArgs.ContentLink.

Summary

If you’re interested in doing something when a piece of content is deleted, for example a specific Content Type, remember to check the ContentLink as well as the DeletedDescendents.

Automatic visual tests with Episerver: Prepare your conditions with Content Providers

In my previous post I talked about how to set up automatic visual tests – and for a time, it was good. Then editors and other team members starts changing the content and your tests starts showing deviations based on content changes rather than code changes.

Yes, you can set up rules on not changing areas included in the tests, but can you really trust everyone?

To get around this, I started working on a set of Content Providers that will give a static set of content. Kind of like when you’re creating unit tests and mocking implementations for interfaces:

public interface IService {
   string Get(int id);

   string[] List();
}
var mockService = new Mock<IService>(); // Start mocking the interface "IService"

mockService
   .Setup(s => s.Get(It.IsAny<int>())) // Parameter is any integer
   .Returns((string)null); // Return null
mockService
   .Setup(s => s.Get(10)) // But if parameter is 10
   .Returns("alf"); // Return "alf"

mockService
   .Setup(s => s.List()) // When calling List()
   .Returns(new[] { "alf" }); // Return an array that only contains "alf"

IService service = mockService.Object; // Done mocking the interface and create an instance of it

Now let’s create a Test Class to verify that everything Works before building the real implementation of IService.

// Assert that the mocked instance returns what we expect
Assert.AreEqual("alf", service.Get(10));
Assert.IsNull(service.Get(1));

var list =service.List();
Assert.AreEqual(1, list.Length);
Assert.AreEqual("alf",list[0]);

Based on the content types in Alloy Templates I have a sample of the Content Provider that sets up examples of the pages and blocks that I want to test.

Separate project for Models

First of all when I create an Episerver solution, I want to have all Episerver Model related in a separate project. That way I can easier separate code that belongs to presentation (the actual MVC web project) from code that belongs to business logic.

After I have created a standard Alloy Template solution called Toders.VisualTests.Web I move all Content Types and Episerver specific assets that are tightly connected to the Content Types (Attributes, or similar) into a specific Models project.

For Alloy there are also some interfaces, Global constant strings and similar that I need to move so that the project compiles properly. Check my commit in GitHub to see the moved files.

Creating the Content Providers

Once I have moved the Models into a separate project, I can create a separate project that will contain my Content Providers.

As the Episerver content structures for Assets (Blocks and Media) and Pages are separate. I will need to create two separate Content Providers, the AssetProvider and PageProvider.

AssetProvider

In the AssetProvider I create the structure for Media and Blocks that will be used on the Pages. It contains an AssetModel so that I can use the created static assets on my Pages.

PageProvider

In the PageProvider I create the structure of the pages that are to be tested. I can create pages that contain Blocks and Media from the ContentProvider.

As you can see I am using the AssetModel to deliver the created assets into the Page Provider.

Initiating everything

To get everything working, I need to create some new Content Types that will act as roots for my Content Providers – StaticStartPage for the Pages and StaticAssetsRootFolder. This is to easier identify which content to use as roots for my Content Providers.

Once the Content Types are created, I create a Start Page for my static content and . With the current implementation, I’ll need to restart my website because I will set up the static website on initialization.

At startup, I have an InitializationModule that checks whether a Page of the type StaticStartPage is created, ensures that there is a Folder of the type StaticAssetsRootFolder and initializes the AssetProvider and PageProvider. The reason I let them implement the original Start Page and ContentFolder is to

After initialization, the module also ensures that there is a SiteDefinition created that is using the static start page as start page.

Finally I need to set up my IIS server to listen to the url for the SiteDefinition (per default is it set to localhost:2335, but can be updated to anything in the Admin Mode).

Testing it

Finally I need to create some tests. This part is what I show in my previous blog post about Automatic visual tests.

For this article I’m using some other tests where I have split the implementation between just doing screenshots with PhantomJS and an implementation that user AppliTools Eyes.

Summary

Currently I’m testing what some content with various Display Options looks like what in a Content Area. I also look what text in different formats looks like.

If I would receive a bug report from my editors that bold text within a bullet list looks weird, I can in a TDD behaviour add it to the Page Provider and verify it with my tests:

Before:

var mainBody = "<h2>Header 2</h2>" +
    "<h3>Header 3</h3>" +
    "<p>Paragraph with <strong>strong</strong> and <em>italic</em> text. Also a <strong><em>combination</em></strong>.</p>" +
    "<ul>"+
    "<li>Bullet 1</li>" +
    "<li>Bullet 2</li>" +
    "<li>Bullet 3</li>" +
    "</ul>" +
    "<ol>"+
    "<li>Numbered 1</li>" +
    "<li>Numbered 2</li>" +
    "<li>Numbered 3</li>" +
    "</ol>";

    page.MainBody = new XhtmlString(mainBody);

After:

var mainBody = "<h2>Header 2</h2>" +
    "<h3>Header 3</h3>" +
    "<p>Paragraph with <strong>strong</strong> and <em>italic</em> text. Also a <strong><em>combination</em></strong>.</p>" +
    "<ul>"+
    "<li>Bullet 1</li>" +
    "<li>Bullet 2</li>" +
    "<li>Bullet 3</li>" +
    "<li><strong>Fat text</strong></li>" +
    "</ul>" +
    "<ol>"+
    "<li>Numbered 1</li>" +
    "<li>Numbered 2</li>" +
    "<li>Numbered 3</li>" +
    "</ol>";

    page.MainBody = new XhtmlString(mainBody);

I hope this gives you some inspiration on how to improve your QA and tests on your website. Of course having a separate website requires that you have a License that allows for multiple websites. This can be achieved using sub pages to your already existing websites, but there’s a risk that you can’t have a fully testable copy of your original website.

You can download the code from GitHub, and set it up with the following steps:

  • Set up a standard Alloy Templates database and adjust the connectionstrings.config
  • Set up an IIS server pointing to the src\Alloy.MVC.Template folder on your drive, make sure that it has two bindings – one for the ordinary Alloy Website and one for your static content
  • Make sure that the InitializationModule are using the correct url for the static website in the field StaticWebsiteUrl in the top of the class
  • Create a page of the type “Static Start Page” under the root page
  • Unfortunately you’ll need to restart the website so that the Site Definition will come in place