November Happy Hour will be moved to Thursday December 5th.

Paul Gruffydd
Oct 30, 2018
  8788
(6 votes)

Limiting total items in a content area while supporting personalisation

As anyone who’s spoken to me at any length about Episerver can confirm, I’m something of a fan of Episerver’s inbuilt visitor group personalisation capabilities. It’s easy to use and, done right, can add a load of value for next to no effort but all too often I see it forgotten about during builds. The most common occurrence of this that I see is the use of a ContentArea’s Items collection rather than FilteredItems but I was reminded of another by Aniket’s recent(ish) blog post.

It’s a technique I’ve seen both in various blog posts and out in the wild being used on sites but, while it does exactly as it claims (sets a maximum number of items you’re allowed to add to a content area), it’s probably not what you need. Why? Take a look…

In this example we’ve got a content area for a hero carousel which can display no more than 3 blocks. To enforce this, I’ve added the validation attribute described in the posts linked above to limit the maximum number of items in the content area to 3. I’ve then set up some blocks so that only 3 will display. All good?

Well, not really. Even though only 3 items will display, the validation attribute will throw a validation error because it doesn’t consider personalisation. If we remove items until it stops throwing an error, in this instance we’d end up with only a single item being displayed on the site.

To fix the issue we could use FilteredItems as I mentioned above and that would kind of work but then consider this scenario…

As the editor will always be logged in and the validation will run in the context of the editor, the first item would be filtered out and so, even though we could quite feasibly return 4 items from the FilteredItems collection for a user viewing the site, the validation will let that through.

A better alternative would be to look at each of the content area items in the Items collection and check whether they’re part of a personalisation group. We can then total up the number of groups plus the number of items which aren’t in a group and that will give us the maximum number of items in the content area after they have been filtered.

Putting it all together, we get this:

/// <summary>
/// Sets the maximum item count in a content area once personalisation is applied
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class MaxItemsAttribute : ValidationAttribute
{
    private int _maxAllowed;

    public MaxItemsAttribute(int MaxItemsAllowed)
    {
        _maxAllowed = MaxItemsAllowed;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var contentArea = value as ContentArea;

        // Get all items or none if null
        var allItems = contentArea?.Items ?? Enumerable.Empty<ContentAreaItem>();

        // Count the unique personalisation group names, replacing empty ones (items which aren't personalised) with a unique name
        var i = 0;
        var maxNumberOfItemsShown = allItems.Select(x => string.IsNullOrEmpty(x.ContentGroup) ? (i++).ToString() : x.ContentGroup).Distinct().Count();

        return (maxNumberOfItemsShown > _maxAllowed) ? new ValidationResult($"The property \"{validationContext.DisplayName}\" is limited to {_maxAllowed} items") : null;
    }

}

Which we can use like this:

[Display(Name = "Hero Carousel")]
[AllowedTypes(typeof(HeroItem))]
[MaxItems(3)]
public virtual ContentArea HeroCarousel { get; set; }
Oct 30, 2018

Comments

valdis
valdis Oct 31, 2018 08:10 AM

nais, I would also add optional boolean to control whether personalized content should be counted in as well. just for flexibility

Paul Gruffydd
Paul Gruffydd Oct 31, 2018 02:29 PM

Hi Valdis. Thanks for the suggestion. It's simple enough to add that option (see below) but I'm not entirely sure of the use cases. I can't think of a realistic scenario where you'd want to restrict the number of items in a ContentArea but you wouldn't want to take personalisation into account.

public class MaxItemsAttribute : ValidationAttribute
{
    private int _maxAllowed;
    private bool _countAllItems;

    public MaxItemsAttribute(int MaxItemsAllowed, bool countAllItems = false)
    {
        _maxAllowed = MaxItemsAllowed;
        _countAllItems = countAllItems;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var contentArea = value as ContentArea;

        // Get all items or none if null
        var allItems = contentArea?.Items ?? Enumerable.Empty<ContentAreaItem>();

        // Count the unique personalisation group names, replacing empty ones (items which aren't personalised) with a unique name
        var i = 0;
        var maxNumberOfItemsShown = _countAllItems ? (contentArea?.Items?.Count() ?? 0) : 
            allItems.Select(x => string.IsNullOrEmpty(x.ContentGroup) ? (i++).ToString() : x.ContentGroup).Distinct().Count();

        return (maxNumberOfItemsShown > _maxAllowed) ? new ValidationResult($"The property \"{validationContext.DisplayName}\" is limited to {_maxAllowed} items") : null;
    }

}

Dan Matthews
Dan Matthews Jan 25, 2019 01:09 AM

This is cool - I hadn't really considered this edge case but I can see that it should be properly considered!

Please login to comment.
Latest blogs
Optimizely SaaS CMS + Coveo Search Page

Short on time but need a listing feature with filters, pagination, and sorting? Create a fully functional Coveo-powered search page driven by data...

Damian Smutek | Nov 21, 2024 | Syndicated blog

Optimizely SaaS CMS DAM Picker (Interim)

Simplify your Optimizely SaaS CMS workflow with the Interim DAM Picker Chrome extension. Seamlessly integrate your DAM system, streamlining asset...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Optimizely CMS Roadmap

Explore Optimizely CMS's latest roadmap, packed with developer-focused updates. From SaaS speed to Visual Builder enhancements, developer tooling...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Set Default Culture in Optimizely CMS 12

Take control over culture-specific operations like date and time formatting.

Tomas Hensrud Gulla | Nov 15, 2024 | Syndicated blog

I'm running Optimizely CMS on .NET 9!

It works 🎉

Tomas Hensrud Gulla | Nov 12, 2024 | Syndicated blog

Recraft's image generation with AI-Assistant for Optimizely

Recraft V3 model is outperforming all other models in the image generation space and we are happy to share: Recraft's new model is now available fo...

Luc Gosso (MVP) | Nov 8, 2024 | Syndicated blog