EPiServer  /  CMS August 10, 2010

EPiAbstractions.Opinionated – A wrapper for EPiServer CMS and Page Type Builder

EPiAbstractions is an open source project that has evolved into a powerful toolset for creating testable, robust and well-designed sites with EPiServer CMS. In this post we'll take a look at it's Opinionated module (EPiAbstraction.Opinionated.dll) which plays a key role in accomplishing that.

Background

The standard way to perform CRUD operations (Create, Read, Update and Delete) for PageData objects with EPiServer CMS is to use the DataFactory class or methods in other classes that delegates to it. Hence DataFactory is a central component in the CMS. It has a few problems though. First of all it’s method aren’t virtual and it doesn’t implement an interface that defines many of the methods that is often needed. This will hopefully be fixed in the next version of the CMS and EPiAbstractions already provides a wrapper, IDataFactoryFacade, that solves this problem.

Another problem that is more complex is that DataFactory serves many masters. It is used internally in the framework, by the administrative UI (admin/edit), by base classes for UI components (PageBase etc), by third party components and by custom code in individual sites. Naturally DataFactory needs to have a rather large and diverse interface to satisfy all that uses it. And as with all components that tries to satisfy many different users it tends to not be perfect for any single users needs.

Finally, as EPiServer doesn’t (currently) support strongly typed pages it lacks support for filtering pages by type and returning them in any other type than PageData if you use Page Type Builder.

EPiAbstraction’s Opinionated module introduces a new type, IPageRepository that aims to mitigate these problems.

The IPageRepository interface

The IPageRepository interface defines the methods from DataFactory that I tend to use most when building web sites with EPiServer CMS along with some new methods that encapsulate logic that I tend to use very often. The most extreme example of that is the GetChildrenAccessibleToCurrentUser<T>(PageReference pageLink) method which returns all children whose page types matches (is of that type of a type that inherits/implements that type) the type T that the current user should be able to see. One could definitely argue that this method breaks a bunch of design principles and it does, but I’ve found that getting children filtered by type, access rights and publication status is something that I do so very often that I think it’s correct to include it in this interface from a pragmatic standpoint.

Anyhow, currently the IPageRepository consists of these methods:

PageData GetPage(PageReference pageLink)
T GetPage<T>(PageReference pageLink)
PageData GetPage(PageReference pageLink, ILanguageSelector selector)
T GetPage<T>(PageReference pageLink, ILanguageSelector selector)
IEnumerable<T> GetChildren<T>(PageReference pageLink)
IEnumerable<T> GetChildren<T>(PageReference pageLink, ILanguageSelector selector)
IEnumerable<T> GetChildrenAccessibleToCurrentUser<T>(PageReference pageLink)
IEnumerable<T> GetChildrenAccessibleToCurrentUser<T>(PageReference pageLink, ILanguageSelector selector)
T GetDefaultPageData<T>(PageReference parentPageLink)
T GetDefaultPageData<T>(PageReference parentPageLink, ILanguageSelector selector)
PageReference Save(PageData page, SaveAction action)
PageReference Publish(PageData page)
void Delete(PageData pageData)
IEnumerable<PageReference> GetAncestors(PageData pageData)
IEnumerable<T> GetDescendentsAccessibleToCurrentUser<T>(PageReference pageLink)
bool TryGetPage<T>(PageReference pageLink, out T page)

The PageRepository class

As you might guess the PageRepository class implements the IPageRepository interface. Most of the methods simply delegate to DataFactory and adds some extra filtering using LINQ extension methods. The methods that ends with AccessibleToCurrentUser also filters the PageData collections returned from DataFactory using EPiServer’s FilterForVisitor class.

IEPiServerContext and EPiServerContext

Apart from the PageRepository the Opinionated module also consists of an interface named IEPiServerContext and an implementation of that named EPiServerContext. The IEPiServerContext inteface is very simple. It only consists of two gettable properties, PageRepository and PageReference. The code looks like this:

public interface IEPiServerContext
{
    IPageRepository PageRepository { get; }

    IPageReferenceFacade PageReference { get; }
}

The primary reason for IEPiServerContext’s existence is that I’ve found that it’s very common to need both a PageRepository and the PageReference class (with it’s properties StartPage, WasteBasket and RootPage) in presenters in an MVP scenario or a controller in a MVC scenario. It’s so common that I found it logical to group them together. And considering that from the view of the presenter/controller they pretty much make up the context related to the CMS.

Having instances of the PageRepository and the PageReference classes grouped together also provides benefits when it comes to mocking as you’ll be able to read about in my blog post about EPiAbstractions.Fakes.

A real world usage scenario

Let’s say that we are using EPiMVP and that we are creating a presenter for a bread crumbs widget. The presenter will populate a view model with links to all the page’s ancestors in the page hierarchy on the site up to the start page. To do so it will need to be able to retrieve pages and it will need to know the start page’s ID (PageReference). Therefore we add a constructor parameter to our presenter of type IEPiServerContext. Assuming that we use a presenter factory (such as NinjectPresenterFactory or StructureMapPresenterFactory that ships with EPiMVP) that can resolve that dependency our presenter will be instantiated with an instance of EPiServerContext when running the site and we are able to easily stub out both PageReference and PageRepository, especially if we use the fakes in EPiAbstractions.Fakes.

An implementation could look something like this:

public class BreadCrumbsPresenter 
    : EPiPresenter<IEPiView<BreadcrumbsModel>, TypedPageData>
{
    private IEPiServerContext cmsContext;

    public BreadCrumbsPresenter(
        IEPiView<BreadcrumbsModel> view,
        TypedPageData page, 
        IEPiServerContext cmsContext)
            : base(view, page)
    {
        this.cmsContext = cmsContext;

        View.Load += View_Load;
    }

    void View_Load(object sender, EventArgs e)
    {
        if (!CurrentPageIsDescendantOfTheStartpage())
            return;

        PageData breadCrumbPage = CurrentPage;
        while (!IsStartPage(breadCrumbPage))
        {
            View.Model.Breadcrumbs.Insert(0, CreateBreadCrumb(breadCrumbPage));

            breadCrumbPage = cmsContext.PageRepository
                .GetPage(breadCrumbPage.ParentLink);
        }
    }

    private bool CurrentPageIsDescendantOfTheStartpage()
    {
        PageReference ancestorReference = CurrentPage.ParentLink;
        while (!PageReference.IsNullOrEmpty(ancestorReference))
        {
            if (ancestorReference.CompareToIgnoreWorkID(
                cmsContext.PageReference.StartPage))
                return true;

            ancestorReference = cmsContext.PageRepository
                .GetPage(ancestorReference).ParentLink;
        }

        return false;
    }

    private bool IsStartPage(PageData page)
    {
        return page.PageLink.CompareToIgnoreWorkID(
            cmsContext.PageReference.StartPage);
    }

    private Breadcrumb CreateBreadCrumb(PageData breadCrumbPage)
    {
        //Create a BreadCrumb based on the breadCrumbPage
    }
}

We could use pretty much the exact same technique if we were using ASP.NET MVC except we’d then be talking about controllers instead presenters and controller factories instead of presenter factories.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

My book

Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!

More about EPiServer CMS