When using EPiServer Composer in EPiServer CMS 6 R2 one of my favourite features was the personalisation container:

Personalisation Container

For those who haven't used it, it allowed editors to quickly add personalisation to a page by dragging a personalisation container onto a page, selecting some visitor groups then drag/dropping some content functions which will be displayed based on the visitor groups selected.

We now have EPiServer 7 with blocks where blocks can be used in place of Composer. If shared blocks are to be personalised then they must be personalised at a block level using access rights (Edit block > Forms editing > Visible to: "Manage").  This leaves a major headache: Blocks will be personalised for every page they are used on. This isn't the case when using the Personalisation Container and may not be appropriate for all blocks in EPiServer 7.

So I decided to try and try and bring Composer Personalisation container like functionality to EPiServer 7. This would allow editors to use a container block to show/hide blocks based on visitor group.

Solution

I wanted a block that could be used in a similar way to the old personalisation container. So we could:

  • Drag blocks into the area and have them appear only if particular visitor groups are matched
  • Select one more visitor group(s) that will be used to personalise the block

Block definition

/// 
/// Used to insert a personalised container block
/// 
[ContentTypeAttribute(
        AvailableInEditMode = true
    , Description = "Used to add a group of blocks that should only be shown with personalisation"
    , DisplayName = "Personalisation Block"
    , GUID = "F2476C1F-CA2D-48B6-8817-B8CC52259195"
    , GroupName = "Personalisation"
    )]
public class PersonalisationBlock : EPiServer.Core.BlockData
{
    [Display(
            Name = "Visitor group(s)"
        , Description = "Users matching these groups will show the content"
        , GroupName = SystemTabNames.Content
        , Order = 200)]
    [UIHint("VisitorGroupListSelector")]
    public virtual string VisitorGroups { get; set; }

    [Display(
            Name = "Personalised blocks"
        , Description = "Blocks to be shown if visitor group(s) match"
        , GroupName = SystemTabNames.Content
        , Order = 210)]
    public virtual ContentArea PersonalisedContentArea { get; set; }
}

There is nothing special about this code. However the interesting part about this block is that by using an EditorDescriptor we can do things that were only previously possible with custom properties. This is wired up with the "UIHint" attribute. We are using the EditorDescriptor here to present a check-box list of visitor groups:

[EditorDescriptorRegistration(
    TargetType = typeof(string), 
    UIHint = "VisitorGroupListSelector")]
public class VisitorGroupListSelector : EditorDescriptor
{
    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
    {
        // Use a custom factory to select vistor groups
        SelectionFactoryType = typeof(VisitorGroupSelectionFactory);

        // We can use the built in checkbox list editor for selecting visitor groups
        ClientEditingClass = "epi.cms.contentediting.editors.CheckBoxListEditor";
        base.ModifyMetadata(metadata, attributes);
    }
}


public class VisitorGroupSelectionFactory : EPiServer.Shell.ObjectEditing.ISelectionFactory
{
    public IEnumerable GetSelections(ExtendedMetadata metadata)
    {
        var repository = new VisitorGroupStore(DynamicDataStoreFactory.Instance);
        var list = repository.List()
            .OrderBy(v => v.Name)
            .Select(v => new SelectItem() { Text = v.Name, Value = v.Name })
            .ToList();
        return list;
    }
}

This gives us a nice "custom" control as follows:

Visitor Groups Property

Block validation warning

public class VisitorGroupsValidator : IValidate
{
    IEnumerable IValidate.Validate(PersonalisationBlock instance)
    {
        try
        {
            if (String.IsNullOrEmpty(instance.VisitorGroups))
            {
                return new[] { 
                new ValidationError() { 
                    ErrorMessage = "You have selected no visitor groups, this content will never be visible to any users", 
                    PropertyName = "VisitorGroups",
                    Severity = ValidationErrorSeverity.Warning
                } 
            };
            }
            return new ValidationError[0];    
        }
        catch
        {
            //Hate swalling the error, but its only a warning
            return new ValidationError[0];
        }

    }
}

This code is a custom validator to warn users if they have not selected any visitor groups when editing the block as the block isn't much use unless you do. This renders as follows:

warning message

Controller

public class PersonalisationBlockController : BlockController
{
    public override ActionResult Index(PersonalisationBlock currentBlock)
    {
        this.ViewBag.MatchedVisitorGroup = false;

        if (String.IsNullOrWhiteSpace(currentBlock.VisitorGroups) == false)
        {
            var helper = new VisitorGroupHelper();

            // Look for matching visitor groups
            IList selectedVisitorGroups = currentBlock.VisitorGroups.Split(",".ToCharArray());
            foreach (string group in selectedVisitorGroups)
            {
                if (helper.IsPrincipalInGroup(ControllerContext.HttpContext.User, group))
                {
                    this.ViewBag.MatchedVisitorGroup = true;
                    break;
                }
            }
        }

        return PartialView(currentBlock);
    }
}

This is pretty simple and looks if the visitor group is matched sets a boolean in the ViewBag that we can use and returns the view.

View

@model PersonalisationBlock

@using System

@if (ViewBag.MatchedVisitorGroup == false && EPiServer.Editor.PageEditing.PageIsInEditMode)
{
    

The following content is not visible to users using the current selected visitor group:

} @if (ViewBag.MatchedVisitorGroup || EPiServer.Editor.PageEditing.PageIsInEditMode) { @Html.PropertyFor(x => x.PersonalisedContentArea) }

This is a simple Razor view and renders the content area if we are in edit mode or a visitor group has been matched. It also pops a warning message up above the content if we are in edit mode to tell editors that the block(s) will be hidden when viewing the site. I decided to always show users the content rather than show/hide it to avoid confusion when editing:

Edit mode warning message

Wrapping up

This is a simple demonstration of how its possible to using EPiServer 7 blocks to create a personalisation container that should be more familiar to editors who have used EPiServer Composer in the past.

Future enhancements will include thing like like hiding blocks when in preview mode and/or adding selected visitor groups to the block context menu.

Its all proof of concept stuff right now so use at your own risk!

Feedback

I'd be happy to hear any feedback on the comments below or @davidknipe


Comments