SaaS CMS has officially launched! Learn more now.

Magnus Rahl
Dec 16, 2010
(2 votes)

Building custom criteria for Visitor groups in CMS 6 R2

EPiServer CMS 6 R2

As you might know the next release of EPiServer CMS is around the corner. Any day now the beta release of EPiServer CMS R2 will be publically available.

Visitor Groups

One of the new features in this version of EPiServer CMS is the increased possibilities to personalize content through Visitor Groups. Simply put, Visitor Groups are a set of criteria that control what visitors see. These criteria can developed to perform any logic, for example there are built in criteria to identify if the visitor is a returning visitor and if the visitor is located in a certain geographical area. The editor combines any number of criteria and sets their parameters to define how a Visitor Group is matched, then proceeds to mark certain content as visible to that Visitor Group.

Read more about Visitor Groups and other new features of EPiServer CMS 6 R2 beta in Allan Thræn’s blog post.

Building a custom criterion: Browser

To demonstrate how you can construct your own criterion for editors to use in their Visitor Groups definitions, here’s an example which detects what browser the visitor is using.

Creating a model

For the criterion to be able to store parameters those parameters must be defined in a model. Instances of the model (corresponding to criteria in Visitor Groups) will be stored in the Dynamic Data Store (DDS). For this to work well we need to implement the EPiServer.Data.Dynamic.IDynamicData interface, which is also required by the base class that will be handling the model. It also requires us to implement the System.ICloneable interface. Both these are really simple to implement and will probably look the same for most of your criteria models:

namespace ACME.Personalization.VisitorGroups.Criteria
    public class BrowserModel : IDynamicData, ICloneable
        public EPiServer.Data.Identity Id { get; set; }
        public object Clone()
            var model = (BrowserModel)base.MemberwiseClone();
            model.Id = Identity.NewIdentity();
            return model;

Creating the criterion

The actual criterion is a class which closes the EPiServer.Personalization.VisitorGroups.CriterionBase<T> base class by supplying the model type. The base class requires us to implement one method, IsMatch, which will be called with the current IPrincipal and HttpContextBase objects as arguments. The responsibility of this method is (surprise!) to return whether the criterion is met or not when the Visitor Group containing it is evaluated in a request.

For EPiServer to recognize our new criterion we also have to decorate it with a VisitorGroupCriterion attribute (namespace EPiServer.Personalization.VisitorGroups). This attribute is based on a class in the System.ComponentModel.Composition assembly, so we must add a reference to that assembly to our project (you can find it in the bin folder of your site after installing CMS 6 R2).

Here’s the skeleton of our criterion class:

namespace ACME.Personalization.VisitorGroups.Criteria
        Category = "User Criteria",
        DisplayName = "Browser",
        Description = "Criterion that matches type and version of the user's browser",
        LanguagePath = "/shell/cms/visitorgroups/criteria/browser"/*,
        ScriptUrl = "ClientResources/Criteria/usercriterion.js"*/)]
    public class BrowserCriterion : CriterionBase<BrowserModel>
        public override bool IsMatch(System.Security.Principal.IPrincipal principal,
                                     HttpContextBase httpContext)
            throw new NotImplementedException();

As you can see the VisitorGroupCriterion has a number for properties to set. The Category is used to group similar criteria in the view where the editor selects criteria to add to a Visitor Group. The other attributes are probably self-explanatory, except the ScriptUrl which you can see is commented out in my example. That property is used to specify a javascript file containing a function which is called when the criterion loads in the user interface to set up and customize the view. We will not be using such a file in this example.

Adding something useful to the model

This far, our model doesn’t contain anything which means that the editor can not specify any parameters for the criterion. So let’s add some properties which we want the editor to set. We will let the editor select a browser, a version and a compare condition for the version. We’ll represent these using an integer and two new enums:

namespace ACME.Personalization.VisitorGroups.Criteria
    public enum CompareCondition
    public enum BrowserType

There are of course more browsers and other ways of representing this, but this is just for demonstration. So let’s add the properties to our model:

public class BrowserModel : IDynamicData, ICloneable
    /* ... Lines omitted ...*/
        SelectionFactoryType = typeof(EnumSelectionFactory),
        LabelTranslationKey = "/shell/cms/visitorgroups/criteria/browser/browsertype",
        AdditionalOptions = "{ selectOnClick: true }"),
    public BrowserType Browser { get; set; }
        SelectionFactoryType = typeof(EnumSelectionFactory),
        LabelTranslationKey = "/shell/cms/visitorgroups/criteria/browser/comparecondition",
        AdditionalOptions = "{ selectOnClick: true }"),
    public CompareCondition Condition { get; set; }
        DefaultValue = 0,
        LabelTranslationKey = "/shell/cms/visitorgroups/criteria/browser/majorversion",
        AdditionalOptions = "{ constraints: {min: 0}, selectOnClick: true }"),
        Range(0, 0xff)]
    public int MajorVersion { get; set; }

The DojoWidget attribute is an EPiServer class for passing values to the Dojo javascript framework used in the user interface. We are also using the EPiServer.Web.Mvc.VisitorGroups.EnumSelectionFactory to get som help representing the enums in the user interface. Also, we use the Required and Range attributes from the System.ComponentModel.DataAnnotations namespace found in the assembly with the same name, so we must add a reference to that assembly (it should be in the GAC). Most of the attribute-settings are self-explanatory but contain some Dojo magic that another article will have to dive deeper into. Also, note the language paths. These keys as well as keys for the enum values need to be added to a language file:

  <language name="English" id="en">
                <less>Less than</less>
                <lessorequal>Less than or equal to</lessorequal>
                <equal>Equal to</equal>
                <moreorequal>More than or equal to</moreorequal>
                <more>More than</more>
                <ie>Internet Explorer</ie>
                <other>Other browser</other>
              <browsertype>Browser type</browsertype>
              <comparecondition>With version</comparecondition>
              <majorversion>Major version</majorversion>

Adding the evaluation logic

To wrap things up we must implement the IsMatch method of our criterion class to actually do something (other than throwing an exception…). This very straight-forward in this example, here’s the code:

namespace ACME.Personalization.VisitorGroups.Criteria
    /* ... Lines omitted ... */
    public class BrowserCriterion : CriterionBase<BrowserModel>
        public override bool IsMatch(System.Security.Principal.IPrincipal principal,
                                     HttpContextBase httpContext)
            return MatchBrowserType(httpContext.Request.Browser.Browser)
                   && MatchBrowserVersion(httpContext.Request.Browser.MajorVersion);
        protected virtual bool MatchBrowserVersion(int majorVersion)
            switch (Model.Condition)
                case CompareCondition.Less:
                    return majorVersion < Model.MajorVersion;
                case CompareCondition.LessOrEqual:
                    return majorVersion <= Model.MajorVersion;
                case CompareCondition.Equal:
                    return majorVersion == Model.MajorVersion;
                case CompareCondition.MoreOrEqual:
                    return majorVersion >= Model.MajorVersion;
                case CompareCondition.More:
                    return majorVersion > Model.MajorVersion;
                    return false;
        protected virtual bool MatchBrowserType(string browserType)
            browserType = (browserType ?? String.Empty).ToLower();
            if (browserType.Equals("ie"))
                return Model.Browser == BrowserType.IE;
            else if (browserType.Equals("firefox"))
                return Model.Browser == BrowserType.FireFox;
                return Model.Browser == BrowserType.Other;

Fire it up!

So, we compile and go into Online Center to try out our new criterion! We find it in the “User Criteria” category as we specified, add it to a group and set the criterion properties:


I also added a “Unsupported browsers” group matching any of Firefox less than 3, IE less than 7 or Other. We then add some conditional content to an editor:


Finally, we try it out, first using Firefox 3.6:


And then using IE8 in IE7 compatibility mode:


Easy as pie!

Source code

The complete source code is available in the EPiServer World Code Section.

Dec 16, 2010


Dec 16, 2010 04:31 PM

Great job, Magnus!

Dec 16, 2010 04:54 PM

Nice work Magnus. I've a feeling visitor groups are going to get a lot of attention when R2 is released so this post will be a useful reference. Dec 17, 2010 09:04 AM

Nice example Magnus!

Martin Helgesen
Martin Helgesen Dec 31, 2010 01:36 PM

Very nice article Magnus

Please login to comment.
Latest blogs
Optimizely London Dev Meetup 11th July 2024

On 11th July 2024 in London Niteco and Netcel along with Optimizely ran the London Developer meetup. There was an great agenda of talks that we put...

Scott Reed | Jul 19, 2024

Optimizely release SaaS CMS

Discover the future of content management with Optimizely SaaS CMS. Enjoy seamless updates, reduced costs, and enhanced flexibility for developers...

Andy Blyth | Jul 17, 2024 | Syndicated blog

A day in the life of an Optimizely Developer - London Meetup 2024

Hello and welcome to another instalment of A Day In The Life Of An Optimizely Developer. Last night (11th July 2024) I was excited to have attended...

Graham Carr | Jul 16, 2024

Creating Custom Actors for Optimizely Forms

Optimizely Forms is a powerful tool for creating web forms for various purposes such as registrations, job applications, surveys, etc. By default,...

Nahid | Jul 16, 2024

Optimizely SaaS CMS Concepts and Terminologies

Whether you're a new user of Optimizely CMS or a veteran who have been through the evolution of it, the SaaS CMS is bringing some new concepts and...

Patrick Lam | Jul 15, 2024

How to have a link plugin with extra link id attribute in TinyMce

Introduce Optimizely CMS Editing is using TinyMce for editing rich-text content. We need to use this control a lot in CMS site for kind of WYSWYG...

Binh Nguyen Thi | Jul 13, 2024