Mark Everard

Hello, I'm Mark – a PhD physicist turned technologist / architect.

Archive for May, 2011

Personalization Engine – Creating an IContentProvider

with one comment

Last week I blogged about the Fortune Cookie Personalization Engine for EPiServer (CMS6 R2), which I’ve released as an open source project on codeplex and it’s also available on the EPiServer Nuget feed. The overriding idea of the PersonalizationEngine is to provide a more automated way of serving personalized content, by matching up a Visitor Group with a means of providing EPiServer content (PageData) that is relevant to that group. This time I’m going to run through how as a developer you can create your own ContentProviders for use with the PersonalizationEngine.

Example – building a StartPageChildrenContentProvider

This example content provider will provide a list of all child pages that live directly underneath the defined EPiServer start page (not a particularly useful personalized content provider – but a good example nontheless.)

  1. Create your class and implement the IContentProvider members. The only method we have to provide an implementation for is the GetContent method which returns a collection of PageData objects.
    public class StartPageChildrenContentProvider : IContentProvider
    {
        public IEnumerable GetContent(string languageBranch)
        {
            throw new NotImplementedException();
        }
    
        public Guid VisitorGroupId { get; set; }
        public string ContentCriteria { get; set; }
    }
    
  2. Implement the public IEnumerable<PageData> GetContent(string languageBranch) method, in our case this uses the DataFactory API to return the Child pages of the StartPage.
    public IEnumerable GetContent(string languageBranch)
    {
        return DataFactory.Instance.GetChildren(PageReference.StartPage, new LanguageSelector(languageBranch));
    }
    

    The above is obviously a simple implementation and doesn’t take into account any page access rights. If you wish the ContentProvider to reflect these then you should add them into the GetContent() method.

  3. Mark the class up with a VisitorGroupContentProvider attribute. This attribute allows three arguments
    • DisplayName – a simple description of the ContentProvider
    • Description – a description of what the ContentProvider does
    • CriteriaEditModel – the strongly typed model used to allow editor’s to input criteria for use by the ContentProvider – these must inherit from ICriteriaModel. Our simple case doesn’t need one and so we can either leave this attribute blank, or specify the DefaultCriteriaModel – I’ll blog more about CriteriaEditModels later on
    [VisitorGroupContentProvider(DisplayName = "StartPage children content provider",
    Description = "Returns the immediate child pages the live underneath the website Start Page ",
    CriteriaEditModel = typeof(DefaultCriteriaModel))]
    public class StartPageChildrenContentProvider : IContentProvider
    {
        ...
    }
    

    Once you have marked the attribute, the Personalization Engine will pick up your ContentProvider on startup and it will be available for Editors to use.

Performance

ContentProviders will generally use the underlying EPiServer API to return PageData objects. Although some calls such as GetChildren used in the above example will already hook into the EPiServer Caching framework and return cached objects, there are some other API calls that will directly hit the database (such as FindPagesWithCriteria). In these cases we must be more careful of performance, as the PersonalizationEngine could perform many GetContent calls on a single page.

In these cases, instead of your ContentProvider directly implementing IContentProvider it should instead inherit from the CachedContentProviderBase class. This abstract class itself implements IContentProvider – but also provides one abstract method to override.


protected override IEnumerable GetContentReferences(string languageBranch)

It is EPiServer best practise to NEVER directly cache PageData objects and to instead cache their corresponding PageReferences (see http://joelabrahamsson.com/entry/how-episerver-cms-caches-pagedata-objects for more information). The CachedContentProviderBase class will handle caching the PageReferences returned from this method and preferentially use that cache when returning any calls to GetContent(). The cache is set as a sliding cache of 20 minutes.

Our implementation would now look like

public class StartPageChildrenContentProvider : CachedContentProviderBase
{
    protected override IEnumerable GetContentReferences(string languageBranch)
    {
        var pages = DataFactory.Instance.GetChildren(PageReference.StartPage, new LanguageSelector(languageBranch));
        return pages.Select(p =>; p.PageLink);
    }
}

and that’s all there is to it……… 🙂

Written by mark

May 17th, 2011 at 12:00 pm

Posted in ASP.NET,C#,EPiServer

EPiServer Personalization Engine

with 3 comments

By now you’ll have heard a lot about the new Personalization / Visitor Group functionality in EPiServer CMS 6 R2, and with good reason. Not only is there an extensible framework for developers to add their own Visitor Group matching criteria, there is a understandable user interface for editors and the whole concept definitely fits in with current expectations of “today’s web”.

Setting up an area of personalized content is straightforward. Editors create Visitor Groups to categorise users using pre-defined rules and then, using the TinyMCE editor provide personalised content for each group; on every page where they require it.

However, on large scale sites working purely through the rich text editor on a page-by-page could quickly turn into a very large administrative effort.

We discussed this at Fortune Cookie, as we wanted to allow our clients to benefit from the personalization features without the administrative load of setting personalized content through TinyMCE. What was needed was a more automated way of working with Visitor Groups, one which could provide new pathways to users (beyond regular navigation and search), and promote content deemed as relevant to them.

Introducing the FortuneCookie.PersonalizationEngine

The open source FortuneCookie PersonalizationEngine for EPiServer is composed of :

  • An API to return a list of ‘recommended content pages’ that are personalized to the current user.
  • An EPiServer UI Plugin to allow editors to manage the Personalization Engine rules and output.
  • An extensible framework making it simple for developers to plugin additional Personalization Engine rules.

The overriding idea is to match up a Visitor Group with a means of providing EPiServer content (PageData) that is relevant to that group. Editors can create Visitor Groups and then match them with pre-defined ContentProviders which are configured to provide content from a particular taxonomy. For example – you may match first-time visitors to your site to all pages which have a PageName property containing the term “Introduction” .

The Personalization engine will iterate through all of the Engine Rules that have been defined by an editor and for each rule

  1. Determine whether the current user is a part of the defined Visitor Group, if so it will ask the specified ContentProvider to return its list of pages.
  2. Collate of all the distinct content provider pages together (maintaining their order) and return them to the calling method.

The main public API to consume the output of the Personalization Engine is listed below. This method would be used by any Page / Control that wished to retrieve a list of ‘recommended content’.

public class PersonalizationEngine
{
     public IEnumerable<PageData> GetRecommendedContent(IPrincipal principal, string languageBranch, int pageCount)
     {
          ....
     }
}

ContentProviders allow fine-grained searching and granular content provision by allowing an editor (or through code) to specify a Critieria which is can be used within the GetContent method. For example – you could define multiple rules using a PageNameContentProvider which would return Pages with names, containing “Introduction”, and then one that would return Pages with names containing “Alloy”.

Hopefully this concept will become a little more obvious once I’ve described the ContentProviders that are included with the initial release.

  • Page search content provider – Performs a full-text page property search against the editor defined criteria
  • Pages in category content provider – Performs a FindPagesWithCriteria search to find pages tagged with a Category matching the defined criteria
  • Pages with pagename content provider – Performs a FindPagesWithCriteria search to find pages that have a PageName property matching the defined criteria
  • Recently changed pages content provider – Performs a RecentlyChangedPagesFinder search to return the sites recently changed pages
  • Search referrer page search content provider – Performs a full-text page property search against the search term found in the user’s http header. If you were to combine this with a Visitor Group that looks for referrals from Google, Bing, other Search engine then you can easily replicate the referrer search functionality Allan Thraen blogged about here

Personally I think that the Pages in Category Content Provider is the most flexible, as this allows editors to easily define their own taxonomy using EPiServer categories and have the Personalization Engine push this content to specific Visitor Groups. It would be very straight forward on a commerce site, to use the Personalization Engine to push a list of related products to users who have purchased a particular item.

I’m sure there are a lot more ideas for ContentProviders, which is why I’ve spent some time trying to make the API simple to work with (I’d be interested to hear whether I’ve achieved that). I’ll blog about that in future posts.

Try it now

The Personalization Engine combined with the Visitor Group functionality gives EPiServer editors an additional degree of freedom to provide their site’s visitors with personalized content.

The PersonalizationEngine requires EPiServer CMS 6 R2 and runs on .Net 3.5 / 4.0. The source code and further developer information is available on http://personalization.codeplex.com/

The BEST way to try this out is to use the AWESOME EPiServer Nuget Feed, and install the package directly into your CMS 6 R2 projects.

And for those of you wondering, Workflows part five is coming soon……….

Written by mark

May 13th, 2011 at 9:10 am

Posted in ASP.NET,C#,EPiServer

Impersonating Visitor Groups from Code

without comments

If you require that a particular HttpRequest impersonates one or more Visitor Groups,  then you can do so by adding one of two keys to the current HttpContext.

Before calling the logic to check each criteria in the VisitorGroup; and determining whether the current User is matched to the group, the VisitorGroupRole class will first check if the current HttpContext contains an item called “ImpersonatedVisitorGroupsById” or an item called “ImpersonatedVisitorGroupsByName”. If these items contain either a valid VisitorGroupRole.Id, or VisitorGroupRole.Name respectively, then the current user will be matched to that Visitor Group regardless of whether they match the Criteria rules.

This is used by EPiServer in edit mode, to allow editors to preview pages as viewed by particular Visitor Groups.

Here  is an method that will ensure that users are always evaluated as being in the Visitor Groups specified by id the method’s arguments. (Note, this method uses the MVC ControllerContext to access the current HttpContext. If you are using this method in a WebForms page you’ll need to substitute this for HttpContext.Current


protected void ImpersonateVisitorGroupsById(string[] visitorGroupIds)
{
     var impersonatedGroupsById =
               ControllerContext.HttpContext.Items["ImpersonatedVisitorGroupsById"] as List<string>
                                                                      ?? new List<string>();
     impersonatedGroupsById.AddRange(visitorGroupIds);
     ControllerContext.HttpContext.Items["ImpersonatedVisitorGroupsById"] = visitorGroupIds;
}

Written by mark

May 9th, 2011 at 9:00 am

Posted in ASP.NET,C#,Code,EPiServer