EPiServer 7 Preview – An adaptable content repository

I’ve blogged previously on why you’d want to create your “IDataFactory” and how decorators can help you keep your code clean. In this post I’ll expand on that subject.

A tale of five children

A given node contains five children. Two of these (in grey) are not visible in menues and two (in pink) are not yet published.

Depending on the context these children are fetched you’d want a different subset of these pages returned to you. If you’re in edit/admin mode you might want to see all the pages, if you’re on the public part of the site you’d probably want to remove the not published pages (FilterForVisitor) and if you’re generating some sort of menu you’d probably also want to remove the pages that are not visible in menus.

For now, imagine that we want to generate some menus and want to remove all the not published pages and those not visible in menus. THe start page controller should not be aware or care about how the pages are fetched and a specific content repository should not be aware or care about which other classes are involved. Using a decoratish apporach would look something like this

Fetching pages

Fetching content is done through the IContentRepository and it’s default implementation, the familiar DataFactory. I prefere to create my own repository interface to avoid mixing the two and also make it easier to alter.

public interface IMyContentRepository : IContentRepository { }
public StartPageController(IMyContentRepository contentRepository)
{
    _contentRepository = contentRepository;
}

Wrapping DataFactory

The base fetching of content will be handled by IContentRepository and my own base will basically just forward calls to that.

public abstract class ContentRepositoryWrapperBase : IMyContentRepository
{
    readonly IContentRepository _contentRepository;

    protected ContentRepositoryWrapperBase(IContentRepository contentRepository)
    {
        if (contentRepository == null) throw new ArgumentNullException("contentRepository");
        _contentRepository = contentRepository;
    }

    // All other methods from IContentRepository are omitted here
    public virtual IEnumerable<T> GetChildren<T>(ContentReference contentLink) where T : IContentData
    {
        return _contentRepository.GetChildren<T>(contentLink);
    }

We’re using an abstract base class with all virtual methods for the IContentRepository methods that just delegates to whatever implementation of IContentRepository was passed in. It’s implemented as a base class partly because ICOntentRepository contains so many methods and you often only want to decorate certain methods. There is often no need to implement anything special for the MoveToWastebasket method in a decorator dealing with FilterForVisitor for instance so to save a lot of typing it can be delegated down the chain.

Decorators

Each decorator inheritcs from the base class and implement relevant behavior on relevant methods.

public class ContentForVisitor : ContentRepositoryWrapperBase
{
    public ContentForVisitor(IContentRepository contentRepository) : base(contentRepository) {}

    public override IEnumerable<T> GetChildren<T>(ContentReference contentLink)
    {
        return FilterForVisitor
            .Filter(new PageDataCollection(base.GetChildren<T>(contentLink)))
            .OfType<T>();
    }
}
public class VisibleInMenuContent : ContentRepositoryWrapperBase
{
    public VisibleInMenuContent(IContentRepository contentRepository) : base(contentRepository) {}

    public override IEnumerable<T> GetChildren<T>(ContentReference contentLink)
    {
        // Yes, this can probably be implemented better
        return base.GetChildren<T>(contentLink)
            .OfType<PageData>()
            .Where(p => p.VisibleInMenu)
            .OfType<T>();
    }
}

Setup the decorators

Remember that EPiServers own registration make sure that when SM is asked for an implementation of IContentRepository it will resolve to DataFactory. We need to make sure to setup “our” IMyContentRepository. There are many ways of achieving this and here’s one (I’ve written more about containers and Epi 7 here and here).

For<IMyContentRepository>()
    .Use<ContentForVisitor>()
    .Named("ForVistor");

When you ask for the implementation named “ForVisitor” the following will happen

  1. Instantiate ContentForVisitor
  2. SM notice it takes an IContentRepository in it’s ctor
  3. SM resolves it to the EPi registered default, DataFactory

But this is just one level. If we wanted an implementation that both filtered for visitor and only showed pages visible in menus it could be registered like this

For<IMyContentRepository>()
    .Use<VisibleInMenuContent>()
    .Named("VisibleInMenu")
    .Ctor<IContentRepository>()
    .Is(c => c.GetInstance<IMyContentRepository>("ForVistor"));

When you ask for the implementation named “VisibleInMenu” the following will happen

  1. Instantiate VisibleInMenuContent
  2. SM notice it takes an IContentRepository in it’s ctor
  3. SM resolves it to the one we’ve registered with the name “ForVistor”
  4. See list above for resolving “ForVisitor”

Choosing implementation

Up until now we’ve only registered different alternatives (named instances) and not how SM will choose what to use and when. Which decorators you use and when you use them is often fairly specific to the type of site you’re building. Using the above examples we’d probably do something like this

For<IMyContentRepository>().Use(GetContentRepository);

private static IMyContentRepository GetContentRepository(IContext c)
{
    if (CreatingMenu)
    {
        return c.GetInstance<IMyContentRepository>("VisibleInMenu");
    }
    return c.GetInstance<IMyContentRepository>("ForVisitor");
}

Closing thoughts

Remember our controller that takes an IMyContentRepository?

public StartPageController(IMyContentRepository contentRepository)
{
    _contentRepository = contentRepository;
}

This class does not care which implementation it uses making it easy to change how it behaves without altering the StartPageController class. This controller is fairly static in it’s use (it’s used to generate the model for the start page). But some other services could perhaps be used both on the start page as well is in a schedule job and the ability to inject different content repositories in those two cases without changing the service can be very helpful.

The experiences drawn here have come largely from working with the site 1177.se and my dear colleges
Karl Ahlin, Patrik Akselsson, Oskar Bejbom and Mathias Kunto. Also thanks to Cecilia Eskeröd for providing me with some illustrations.