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

smithsson68@gmail.com
Oct 6, 2015
  3780
(3 votes)

Limiting Page Type Instances

We had a requirement to limit the number of instances of a particular page type within parts of the page tree. I developed a simple attribute and validator which implements this.

You add the attribute to your page type class and specifiy the scope of the check. The scope options I added were :

  • Site - only x instances of the page type can exist on the site, anywhere in the page tree. Probably useful for singletons
  • Same parent - only x instances of the page type can exist in the same parent node, i.e. amongst siblings
  • Same parent and descendants - only x instances of the page type can exist in the same parent node including descendant nodes

Obviously, you could easily add more scope variants here to suite your requirements. The code can be found below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using EPiServer;
using EPiServer.Core;
using EPiServer.Validation;

[AttributeUsage(AttributeTargets.Class)]
public class AllowedInstancesAttribute : Attribute
{
    public AllowedInstancesAttribute(int maxInstances)
    {
        MaxInstances = maxInstances;
    }

    public enum InstanceScope
    {
        Site,
        SameParent,
        SameParentOrDescendant
    }

    public int MaxInstances { get; set; }
    public InstanceScope Scope { get; set; }
}

public class AllowedInstancesValidator : IValidate<PageData>
{
    private readonly IContentLoader contentLoader;

    public AllowedInstancesValidator(IContentLoader contentLoader)
    {
        this.contentLoader = contentLoader;
    }

    public IEnumerable<ValidationError> Validate(PageData instance)
    {
        var allowedInstanceAttribute = instance.GetType().GetCustomAttribute<AllowedInstancesAttribute>(true);

        if (allowedInstanceAttribute == null)
        {
            return Enumerable.Empty<ValidationError>();
        }

        var searchRoot = allowedInstanceAttribute.Scope == AllowedInstancesAttribute.InstanceScope.Site
                                ? ContentReference.StartPage
                                : instance.ParentLink;

        var instancesOfType = this.GetInstancesOfType(instance.GetType(), searchRoot, allowedInstanceAttribute.Scope);

        if (instance.PendingPublish)
        {
            instancesOfType++;
        }

        if (instancesOfType > allowedInstanceAttribute.MaxInstances)
        {
            return new[]
            {
                new ValidationError()
                    {
                        ErrorMessage = string.Format("Only {0} instances of this page type can exist at this level", allowedInstanceAttribute.MaxInstances),
                        PropertyName = "PageName",
                        Severity = ValidationErrorSeverity.Error,
                        ValidationType = ValidationErrorType.StorageValidation
                    }
            };
        }

        return Enumerable.Empty<ValidationError>();
    }

    private int GetInstancesOfType(Type type, ContentReference rootPage, AllowedInstancesAttribute.InstanceScope instanceScope)
    {
        var instances = 0;
        var children = contentLoader.GetChildren<IContent>(rootPage, new LoaderOptions());

        foreach (var child in children)
        {
            if (type == child.GetType())
            {
                instances++;
            }

            if (instanceScope == AllowedInstancesAttribute.InstanceScope.SameParent)
            {
                continue;
            }

            instances += this.GetInstancesOfType(type, child.ContentLink, instanceScope);    
        }

        return instances;
    }
}
Oct 06, 2015

Comments

Grzegorz Wiecheć
Grzegorz Wiecheć Oct 6, 2015 08:00 PM

Interesting feature. We implemented something simalar a while ago, but it was not that configurable. Your solution could be useful in many projects.

I could add that validator is not executed when moving content. So if you have "full" tree level and move another page from somewhere else the Validate method will not protect from extending the limit. To solve it we implemented additional validation in MoveContent method of IContentEvents service.

valdis
valdis Oct 7, 2015 12:22 PM

Wrap it up as nuget package please :)

Please login to comment.
Latest blogs
Shared optimizely cart between non-optimizley front end site

E-commerce ecosystems often demand a seamless shopping experience where users can shop across multiple sites using a single cart. Sharing a cart...

PuneetGarg | Dec 3, 2024

CMS Core 12.22.0 delisted from Nuget feed

We have decided to delist version 12.22.0 of the CMS Core packages from our Nuget feed, following the discovery of a bug that affects rendering of...

Magnus Rahl | Dec 3, 2024

Force Login to Optimizely DXP Environments using an Authorization Filter

When working with sites deployed to the Optimizely DXP, you may want to restrict access to the site in a particular environment to only authenticat...

Chris Sharp | Dec 2, 2024 | Syndicated blog

Video Guides: Image Generation Features in Optimizely

The AI Assistant for Optimizely now integrates seamlessly with Recraft AI, providing advanced image generation capabilities directly within your...

Luc Gosso (MVP) | Dec 1, 2024 | Syndicated blog

DAM integration new major version, performance improvements and Library Picker folder selection

As you might already have seen we have decided to delist the EPiServer.CMS.WelcomeIntegration version 1.4.0 where we introduced Graph support....

Robert Svallin | Nov 29, 2024

Adding Geolocation Personalisation to Optimizely CMS with Cloudflare

Enhance your Optimizely CMS personalisation by integrating Cloudflare's geolocation headers. Learn how my Cloudflare Geo-location Criteria package...

Andy Blyth | Nov 26, 2024 | Syndicated blog