Prevent certain Optimizely visitor groups from being used in content areas

At my current client we had the need to prevent the use of certain visitor groups in some of the content areas. I.e. some areas were only to support national content, and other areas only regional content (where each region had a different visitor group).

Here is a solution making use of ValidationAttribute. The visitor group ids are kept in the ContentAreaItem’s AllowedRoles property.

[AttributeUsage(AttributeTargets.Property)]
public sealed class ContentAreaAllowAttribute : ValidationAttribute
{
  public ContentAreaAllowAttribute(bool allowNational, bool allowRegional)
  {
    AllowNational = allowNational;
    AllowRegional = allowRegional;
  }

  public override bool IsValid(object value) => ValidateProperty(value as ContentArea);

  private bool ValidateProperty(ContentArea item)
  {
    if (item == null)
    {
      return true;
    }

    IRegionRepository regionRepository = ServiceLocator.Current.GetInstance<IRegionRepository>();
    IEnumerable<Guid> allRegionIds = regionRepository.GetAllRegions().Select(r => r.Id).ToArray();
    if (!AllowNational && ContainsNational(item, allRegionIds))
    {
      return false;
    }
    if (!AllowRegional && ContainsRegional(item, allRegionIds))
    {
      return false;
    }
    return true;
  }

  private static bool ContainsNational(ContentArea area, IEnumerable<Guid> regionIds) =>
    area.Items.Any(i => !IsRegional(i, regionIds));

  private static bool ContainsRegional(ContentArea area, IEnumerable<Guid> regionIds) =>
    area.Items.Any(i => IsRegional(i, regionIds));

  private static bool IsRegional(ContentAreaItem item, IEnumerable<Guid> regionIds)
  {
    if (item.AllowedRoles == null)
    {
      // No regions selected.
      return false;
    }
    foreach (string role in item.AllowedRoles)
    {
      // All region ids are guids
      if (Guid.TryParse(role, out Guid visitorGroupId) &&
          regionIds.Any(rid => rid == visitorGroupId))
      {
        return true;
      }
    }
    return false;
  }

  public override string FormatErrorMessage(string name)
  {
    string message = string.Empty;
    if (AllowNational && !AllowRegional)
    {
      message = "regional";
    }
    else if (!AllowNational && AllowRegional)
    {
      message = "national";
    }
    return $"'{name}' cannot contain {message} contents.";
  }

  public bool AllowNational { get; }

  public bool AllowRegional { get; }
}

The attribute may be used on any ContentArea property as below. Of course, allowing or disallowing both national and regional content at the same time makes no sense.

[Display(
  Name = "National content",
  Description = "..",
  GroupName = SystemTabNames.Content,
  Order = 10)]
[ContentAreaAllow(allowNational: true, allowRegional: false)]
public virtual ContentArea NationalContent { get; set; }

[Display(
  Name = "Regional content",
  Description = "..",
  GroupName = SystemTabNames.Content,
  Order = 20)]
[ContentAreaAllow(allowNational: false, allowRegional: true)]
public virtual ContentArea RegionalContent { get; set; }