Virtual Happy Hour this month, Jun 28, we'll be getting a sneak preview at our soon to launch SaaS CMS!

Try our conversational search powered by Generative AI!

Magnus Rahl
Mar 29, 2011
(1 votes)

Richer UI for your custom Visitor Groups criteria

In a previous blog post I described an example for a custom criterion for Visitor Groups in EPiServer CMS 6 R2. This blog post describes how you can make that example richer by using the Dojo toolkit employed by the CMS UI.

API changes

Building on the code from the previous example, I would first like to update some of the basics of it, due to API changes in the RTM of CMS 6 R2. The model can now inherit CriterionModelBase which takes care of the DDS identity and more. You only have to implement the abstract Copy method, in which you may just call the base class’ ShallowCopy() method in case you only have value members in your model or as a start of a deep copy. ShallowCopy also gives the model copy a new DDS Identity. Using these changes the RoleModel looks like this:

/// <summary>
/// Model class for use by the RoleCriterion criterion for
/// Visitor Groups. Stores a role name and a compare condition.
/// </summary>
public class RoleModel : CriterionModelBase
    public override ICriterionModel Copy()
        return ShallowCopy();
        SelectionFactoryType = typeof(EnumSelectionFactory),
        AdditionalOptions = "{ selectOnClick: true }",
        LabelTranslationKey = "/shell/cms/visitorgroups/criteria/role/comparecondition"
        ), Required]
    public RoleCompareCondition Condition { get; set; }
        WidgetType = "dijit.form.FilteringSelect",
        AdditionalOptions = "{ selectonclick: true }",
        LabelTranslationKey = "/shell/cms/visitorgroups/criteria/role/rolename"
        ), Required]
    public string RoleName { get; set; }
/// <summary>
/// Enum representing compare conditions for booleans
/// </summary>
public enum RoleCompareCondition


Using Dojo widgets

Actually, I sneaked one other change into the RoleModel: The RoleName property now has the WidgetType property set in the DojoWidget attribute. Previously, when editing the criterion for use in a Visitor Group definition, the name of the role would have to be typed correctly into a textbox. Now I tell it to use the dijit.form.FilteringSelect control instead.

Dijit is the UI sibling of Dojo, much like jQuery UI is to jQuery. The FilteringSelect control is basically a dropdown, which can also be typed into to filter the available values in the dropdown. This is very useful in this case where there may be many roles to select from.


Data access

The FilteringSelect control will be reading its data (the roles to display) using an AJAX JSON call. This will be implemented using an ASP.NET MVC 2 controller. This requires a reference to System.Web.Mvc version 2 to be added to the project.

After adding that I just add a new ASP.NET MVC controller to the project. It looks like this:

[Authorize(Roles = "CmsAdmins, VisitorGroupAdmins")]
public class RoleCriterionController : Controller
    public JsonResult GetAllRoles()
        var roles = VirtualRoles.GetAllRoles();
        return Json(new
                        identifier = "id",
                        label = "name",
                        items = roles.Select(r => new { id = r, name = r })

First, there’s an attribute limiting the access to this controller to the necessary groups. These are the same virtual roles that have access to to the Visitor Groups editor in Online Center. Then there’s the controler with just one action/method returning a JsonResult object which will simply be seen as JSON by the caller.

The control we are going to use requires us to return a JSON object with three properties. The last one, items, is a list of the items to show in the dropwdown, each with two properties, id and name. As you can see both these are set to the same value (the name of the role). The properties outside the list, identifier and label, then define what properties inside each item in the list should be used as identifier (value in the dropdown) and label (text in the dropdown). So all this code is saying is that the dropdown should display role names and also return the role name selected.

For the EPiServer shell to register the route to this controller we have to add it to the section of web.config. I chose to add it in the protectedModules section with has the root path of the UI. Then add it with a unique name, which will be part of it’s path, and a reference to the assembly it resides in, for example:

<add name="ACME" resourcePath="~/">
    <add assembly="ACME.Web" />

I guess the resourcePath isn’t really needed since we use no MVC View for the controller.

Update: The resourcepath is used when resolving ScriptUrl in the VisitorGroupCriterion attribute of the criterion class. In the example below it is set to “Javascript/RoleCriterion.js” and this combined with the resourcePath gives the path where the javascript is requested: ~/JavaScript/RoleCriterion.js.


Wire it up

Finally, the FilteringSelect control in the criterion has to be wired up to perform the AJAX call and get the groups. This is done by referencing a javascript in the criterion class, which after that modification looks like this:

/// <summary>
/// Implementation of a EPiServer.Personalization.VisitorGroups.CriterionBase
/// which checks if a user is in a named role.
/// </summary>
    ScriptUrl = "JavaScript/RoleCriterion.js",
    Category = "User Criteria",
    DisplayName = "Role",
    Description = "Criterion that matches the user's roles",
    LanguagePath = "/shell/cms/visitorgroups/criteria/role")]
public class RoleCriterion : CriterionBase<RoleModel>
    public override bool IsMatch(System.Security.Principal.IPrincipal principal,
                                    HttpContextBase httpContext)
        var isInRole = principal.IsInRole(Model.RoleName);
        var shouldBeInRole = Model.Condition == RoleCompareCondition.Equal;
        return isInRole == shouldBeInRole;

The ~/JavaScript/RoleCriterion.js referenced by ScriptUrl looks like this:

(function() {
    return {
        uiCreated: function(namingContainer, settings) {
                url: '../../ACME/RoleCriterion/GetAllRoles',
                handleAs: 'json',
                preventCache: true,
                error: epi.cms.ErrorDialog.showXmlHttpError,
                load: function(jsonData) {
                    var roleStore = new{
                        data: jsonData
                    var roleWidget = dijit.byId(namingContainer + 'RoleName');
           = roleStore;
                    if (settings && settings.RoleName) {
                        roleWidget.set('value', settings.RoleName);

Starting from the top, dojo.require is Dojo’s way of loading modules/packages, it loads EPiServer’s error dialog. Then the anonymous function returns a JSON object with one property called uiCreated which in turn is a function. This function is called (surprise!) when the controls in the UI have been created. It’s first argument, namingContainer, gives the function a seed to construct the name of controls it needs to find. The second one, settings, contains the current value stored in our model, i.e. the current value that should be set in the dropdown.

Continuning reading the code, there’s another dojo.require which loads the The ItemFileReadStore is somewhat of an analogue of an ASP.NET DataSource control. It will be given the JSON data from the GetAllRoles action and then the FilteringSelect control will connect to it to display the roles.

The dojo.xhrGet method performs the AJAX call. The path of the VisitorGroups view is [uiURL]/CMS/VisitorGroups and the web.config registration mapped the controller in [uiURL]/ACME which gives the relative url to the action method ../../ACME/RoleCriterion/GetAllRoles.

The load property in the AJAX call contains a function taking care of the result and it does the following:

  • Instantiate a new ItemFileReadStore and feed it the data returned by the AJAX call. Because the data is returned in the specific format it is understood by the ItemFileReadStore.
  • Find the FilteringSelect control created by the EPiServer shell. This is done by using the namingContainer seed and the name of the control which will be the same as the property in the model, in this case RoleName.
  • Connect the ItemFileReadStore to the FilteringSelect and use settings to set the current value, if any.

Done! When adding the criterion we will now be getting a nice AJAX-fed filtering drop down:



Mar 29, 2011


Magnus Rahl
Magnus Rahl Mar 29, 2011 04:56 PM

Did I get it right now, Paul? :) Mar 30, 2011 10:34 AM

Awesome Magnus!

Please login to comment.
Latest blogs
Enhancing online shopping through Optimizely's personalized product recommendations

In this blog, I have summarized my experience of using and learning product recommendation feature of Optimizely Personalization Artificial...

Hetaxi | Jun 18, 2024

New Series: Building a .NET Core headless site on Optimizely Graph and SaaS CMS

Welcome to this new multi-post series where you can follow along as I indulge in yet another crazy experiment: Can we make our beloved Alloy site r...

Allan Thraen | Jun 14, 2024 | Syndicated blog

Inspect In Index is finally back

EPiCode.InspectInIndex was released 9 years ago . The Search and Navigation addon is now finally upgraded to support Optimizely CMS 12....

Haakon Peder Haugsten | Jun 14, 2024

Change the IP HTTP Header used for geo-lookup in Application Insights


Johan Kronberg | Jun 10, 2024 | Syndicated blog

Copying property values

In this article I’d like to show simple Edit Mode extension for copying property values to other language versions. In one of my previous blogposts...

Grzegorz Wiecheć | Jun 8, 2024 | Syndicated blog

Auto-translate with OpenAI GPT-4o in Optimizely CMS

Improvements for Episerver.Labs.LanguageManager! It's now possible to auto-translate both a page and its children at the same time! Additionally, m...

Tomas Hensrud Gulla | Jun 7, 2024 | Syndicated blog