Scott Reed
OMVPPaaS CMS Developer Certificationworkspace_premium +2
Jun 20, 2022
visibility 3294
star star star star star
(15 votes)

Hiding Visitor Group Criteria In CMS 12

In CMS 12 the Visitor Group managment system has been moved to a client side component the same as the standard editing experience and the .NET 5/6 framework has moved away from StructureMap.

Therefore the old way we had to filter out Visitior Group Criteria we don't want such as described here https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2019/6/hide-episerver-visitor-group-criteria/ does not work as described.

Therefore here is a solution you can implement to do the same job, configurable from appSettings.json

Stage 1: Implement A Custom Filtered Version of the IVisitorGroupsUIApiService

This class simply filters the API to return everything apart from types we want removed (from Stage 2)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using EPiServer.Cms.UI.VisitorGroups.Api.Builders;
using EPiServer.Cms.UI.VisitorGroups.Api.Services;
using EPiServer.Cms.UI.VisitorGroups.Api.ViewModels;
using EPiServer.Framework.Localization;
using EPiServer.Personalization.VisitorGroups;
using Microsoft.Extensions.Configuration;

namespace AlloyTemplates.Business.VisitorGroups
{
    public class FilteredVisitorGroupsUIApiService : IVisitorGroupsUIApiService
    {
        private readonly IVisitorGroupRepository _visitorGroupRepository;
        private readonly IVisitorGroupCriterionRepository _visitorGroupCriterionRepository;
        private readonly VisitorGroupDtoBuilder _visitorGroupDtoBuilder;
        private readonly LocalizationService _localizationService;
        private readonly IVisitorGroupsStatisticsLoggerRegistry _visitorGroupsStatisticsLoggerRegistry;
        private readonly IConfiguration _configuration;

        public FilteredVisitorGroupsUIApiService(
          IVisitorGroupRepository visitorGroupRepository,
          IVisitorGroupCriterionRepository visitorGroupCriterionRepository,
          VisitorGroupDtoBuilder visitorGroupDtoBuilder,
          LocalizationService localizationService,
          IVisitorGroupsStatisticsLoggerRegistry visitorGroupsStatisticsLoggerRegistry,
            IConfiguration configuration
          )
        {
            this._visitorGroupRepository = visitorGroupRepository;
            this._visitorGroupCriterionRepository = visitorGroupCriterionRepository;
            this._visitorGroupDtoBuilder = visitorGroupDtoBuilder;
            this._localizationService = localizationService;
            this._visitorGroupsStatisticsLoggerRegistry = visitorGroupsStatisticsLoggerRegistry;
            _configuration = configuration;
        }

        public IEnumerable<VisitorGroupListViewItemDto> ListAllGroups() => (IEnumerable<VisitorGroupListViewItemDto>)this._visitorGroupRepository.List().Select<VisitorGroup, VisitorGroupListViewItemDto>((Func<VisitorGroup, VisitorGroupListViewItemDto>)(visitorGroup => new VisitorGroupListViewItemDto(visitorGroup))).ToList<VisitorGroupListViewItemDto>();

        public VisitorGroupDto GetGroup(Guid id) => this._visitorGroupDtoBuilder.Build(this._visitorGroupRepository.Load(id));

        public VisitorGroup SaveGroup(VisitorGroup visitorGroup)
        {
            if (visitorGroup.Id == Guid.Empty)
                visitorGroup.Id = Guid.NewGuid();
            this._visitorGroupRepository.Save(visitorGroup);
            return visitorGroup;
        }

        public VisitorGroupDto CopyGroup(Guid id) => this._visitorGroupDtoBuilder.Build(this._visitorGroupRepository.Copy(this._visitorGroupRepository.Load(id), this._localizationService.GetString("/shell/cms/visitorgroups/index/copy")));

        public void DeleteGroup(Guid id) => this._visitorGroupRepository.Delete(id);

        public IEnumerable<VisitorGroupCriterionDto> ListAllVisitorGroupCriterion()
        {
            var filteredTypes = _configuration.GetSection("FilteredVisitorGroups:ExcludedCriterionFullTypes").Get<string[]>();

            var filteredList = filteredTypes == null || !filteredTypes.Any() ? _visitorGroupCriterionRepository.List() : _visitorGroupCriterionRepository.List()
                .Where(item => !filteredTypes.Contains(item.TypeName));

            return (from criterion in filteredList select _visitorGroupDtoBuilder.Build(criterion)).ToList();
        }

        public void DeleteStatisticsForGroup(Guid id) => this._visitorGroupsStatisticsLoggerRegistry.RemoveStatistics((IEnumerable<Guid>)new Guid[1]
        {
      id
        });
    }
}

Make sure also to register this in your DI code

services.AddTransient<IVisitorGroupsUIApiService, FilteredVisitorGroupsUIApiService>();

Stage 2: Add settings to appSettings.json

In this section of the root of the of the JSON file we can configure the full type name of the Criterion type we want removed.

  "FilteredVisitorGroups": {
    "ExcludedCriterionFullTypes": [
      "EPiServer.Personalization.VisitorGroups.Criteria.UserProfileCriterion, EPiServer.Cms.UI.AspNetIdentity"
    ] 
  } 

This example shows removing the UserProfileCriterion but will work with any type

Thanks all :-)

Jun 20, 2022

Comments

error Please login to comment.
Latest blogs
Surviving a Dirty Feed. Part 3: Error Handling and Event Batching

Committing a 250,000-row Optimizely catalog import row by row, returning a per-row failure report, and coalescing catalog events into one per batch.

Stanisław Szołkowski | Oct 16, 2026 |

Building UI Extensions for Optimizely SaaS CMS

Introduction Developers have been extending the functionality of Optimizely PaaS CMS for years using addons built in C# and deployed as NuGet...

Mark Stott | Oct 15, 2026

Cheapskate AI Translator: Because Translation Should Not Require a Second Mortgage

There is a particular kind of software project that begins with a detailed architecture document, a product roadmap, and a carefully assembled...

Linh Doan Cuu | Oct 15, 2026

Computed Fields in CMS 13 Content Contracts with Optimizely Graph

Content Contracts in Optimizely CMS 13 allow different content types to share a common structure when querying Optimizely Graph. But what if the...

Mardy | Oct 14, 2026