Singleton page

Quite often when developing a website on EPiServer, I am creating page types which are used only for a single page. Such pages could be - cart page, order page, password reset page etc. To load such page, the pattern I use is adding properties with content references of those pages on the start page, then load start page and then load configured page. This sounds too complicated when I know that this page has only a single instance. Also, it is quite often that I forget to configure these pages for different environments. So few months ago I had an idea to create extension methods which would allow loading these pages in a simple way.

A singleton page extension method looks for the first page of a particular type under given root page. Usually, you would like to search under start page. There are extensions for ContentReference and for PageData.

var testPage1 = ContentReference.StartPage.GetSingletonPage<TestPage>();

var startPage = _contentLoader.Get<StartPage>(ContentReference.StartPage);
var testPage2 = startPage.GetSingletonPage<TestPage>();

But it is also possible to search under any page. For example, in a commerce solution you might have a Checkout page with Order confirmation and Order summary pages as children.

var checkoutPage = ContentReference.StartPage.GetSingletonPage<CheckoutPage>();
var orderConfirmationPage = checkoutPage.GetSingletonPage<OrderConfirmationPage>();
var orderSummaryPage = checkoutPage.GetSingletonPage<OrderSummaryPage>();

A singleton page API under the hood uses simple concurrent dictionary to cache content references for particular singleton page under the root page. This allows to search for the page only once and next time use cached ContentReference.

An API also allows to create and inject your own caching mechanism. For example, you would like to use EPiServer's CacheManager or fake cache for testing.

public class FakeCache : DefaultContentReferenceCache
{
    public ConcurrentDictionary<CacheKey, ContentReference> InternalCache => Cache;
}

...

var fakeCache = new FakeCache();
Extensions.InjectedCache = new Injected<IContentReferenceCache>(fakeCache);

Currently, the project does not have a NuGet package, but it is quite easy to copy/paste an extension code from GitHub. The project also has tests which could be used as a reference.

Summary

Using this API you can create a project without the need to configure singleton pages or use API as a fallback when the page is not configured. Per Nergård recently wrote an article how to limit page types and block types to be created only once. Combining both techniques creates a good solution for managing singleton pages.