Theme:

Using segmentation to display custom content for members

How to create page variants to display customized member content

There are quite a few cases imaginable where you would want to show different content on your site depending on who's visiting.

Apart from a separate member platform, showing tailored content on other pages can boost your conversion rate, help create a sense of community among your members and give your members a much more personalized overall experience.

The most default way of creating custom content is by restricting the access rights on the node in the content tree. But what if you could create variants of existing pages and change the content based on all kinds of specific criteria? Have we got news for you… You can!

In the following example we created a member variant of a content page. This way, when a logged in member is viewing the page, the content differs from the default page. Here’s how we did this…

Adding your segments in the backoffice

We created a Segments folder on the root of the content tree. This folder contains nodes specifying our segment definitions. The name of the node will be the name of the segment. In the property ‘Document Types’, we add the aliases of the documenttypes where we want to apply segmentation.

Allowing segmentation

Since Umbraco 8.7, there's an "Allow Segmentation"-toggle you can use in the backoffice, much like the "Allow vary by culture"-toggle (Umbraco v8.7 - Segments). All we have to do to make it visible, is actually use it...

We set up a ContentTypeSavingNotification using the following code. This will enable segmentation on a document type when saving it in the backoffice. We applied it to the saving event of the documenttype, so we don’t trigger the controller too much. Note this will not work for existing documenttypes until you save them again.


using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;

namespace UmbracoSegmentation.Events
{
    /// <summary>
    /// This will enable segmentation on a document type when it's saving
    /// We use the saving event instead of saved to not have trigger our controller again 
    /// </summary>
    public class AllowSegmentationEvent : INotificationHandler<ContentTypeSavingNotification>
    {
        public void Handle(ContentTypeSavingNotification notification)
        {
            var contentTypes = notification.SavedEntities;
            foreach (var contentType in contentTypes) {
                //Doesn't allow segementation when content type is element or varies by segment is already set
                if (contentType.IsElement || contentType.VariesBySegment())
                    continue;

                contentType.SetVariesBy(ContentVariation.CultureAndSegment, true);
            }
        }
    }
}

In the backoffice, we were now able to check “Allow segmentation” when editing a property after saving a content type – we did have to reload the page first.

Creating the segments

We then set up a SendingContentNotification. This code will be triggered every time you open or create an item in the content tree. In this case, we chose to only run the code when a new item is created.

We loop over our created segment definitions and check if they contain the documenttype of the item we just created. For each segment definition we found, we create a new variant (combo of culture and segment) – and add this to the existing variants.


using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using UmbracoSegmentation.Extensions;

namespace UmbracoSegmentation.Events
{
    public class CreateSegmentsEvent : INotificationHandler<SendingContentNotification>
    {
        private readonly IContentTypeService contentTypeService;

        public CreateSegmentsEvent(IContentTypeService contentTypeService)
        {
            this.contentTypeService = contentTypeService;
        }

        public void Handle(SendingContentNotification notification)
        {
            if (notification.Content.Id > 0)
                return;
            //Get Data folder and get segments
            var segmentDefinitions = notification.UmbracoContext.Content.GetAtRoot().FirstOrDefault(node => node.ContentType.Alias == "segments" && node.Name.Equals("Segments")).Children();
            var relevantSegmentDefinitions = segmentDefinitions.Where(def => def.Value<IEnumerable<string>>("documentTypes").Contains(notification.Content.ContentTypeAlias));

            //get all segements with content type
            var segments = relevantSegmentDefinitions.Select(def => def.Name);
            //if no segments quit
            if (!segments.Any())
                return;

            //get all properties of contentType which allow vary by segment
            var contentType = contentTypeService.Get(notification.Content.ContentTypeId.Value);
            if (contentType == null)
                return;
            var props = contentType.PropertyTypes.Where(p => p.VariesBySegment()).Select(p => p.Alias);
            props = props.Concat(contentType.CompositionPropertyGroups.SelectMany(p => p.PropertyTypes).Where(p => p.VariesBySegment()).Select(p => p.Alias));
            // if no props, quit
            if (!props.Any())
                return;

            //segments are coupled to variants so make the segment for all variations
            foreach (var variant in notification.Content.Variants)
            {
                //Get all properties
                //check if property allows vary by segment
                var properties = variant.Tabs.SelectMany(f => f.Properties).Select(p => p.Alias).Where(p => props.Contains(p));
                //make segment for property

                foreach (var segment in segments)
                {
                    var newVariant = variant.DeepClone();
                    newVariant.Name = segment;
                    newVariant.Segment = segment;
                    foreach (var property in properties)
                    {
                        SetDefaultValue(newVariant, property, null);
                    }
                    notification.Content.Variants = notification.Content.Variants.Append(newVariant);
                }
            }
        }

        private void SetDefaultValue(ContentVariantDisplay variant, string propertyAlias, object defaultValue)
        {

            var property = variant.Tabs.SelectMany(f => f.Properties)
                       .FirstOrDefault(f => f.Alias.InvariantEquals(propertyAlias));
            if (property != null && (property.Value == null || String.IsNullOrWhiteSpace(property.Value.ToString())))
            {
                property.Value = defaultValue;
            }

        }
    }

}

In the backoffice, we added a member segment definition and under its ‘Document Types’-property, we added the contentPage alias. On creating a new content page, the new member segment showed up under the culture variants dropdown.

Use case

We created a basic example of how to use segments to display custom content for logged in members. We set up a basic Umbraco 10 project and installed the starter kit to create our ‘sandbox’ environment. We also added a basic login page and created a test member.

We added a VariantController which overrides the default Umbraco RenderController. In this controller we checked if the current request is authenticated (is the visitor logged in?), in which case we set the VariationContext to the current Culture and our created member segment.


using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.Controllers;

namespace UmbracoSegmentation.Controllers
{
    public class VariantController : Umbraco.Cms.Web.Common.Controllers.RenderController
    {
        private readonly IVariationContextAccessor _variationContextAccessor;
        public VariantController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor, IVariationContextAccessor variationContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor)
        {
            this._variationContextAccessor = variationContextAccessor;
        }

        public override IActionResult Index()
        {
            // if logged in, set our segment to "Member"
            if (this.User.Identity.IsAuthenticated && _variationContextAccessor.VariationContext != null)
                _variationContextAccessor.VariationContext = new VariationContext(_variationContextAccessor.VariationContext.Culture, "Member");
            
            return base.Index();
        }
    }
}

We added the segmentation to our content page and allowed member segmentation on the page title as an example. We created a page called “Are you logged in?”

The result

Now, after saving and publishing both variants, we can finally see what this does on the front… Let’s visit the page before logging in…

No real surprises here, let’s be honest. The default content of the ‘Are you logged in’-page is displayed. That’s pretty default… Let’s log in!

And go back to the "Are you logged in?" page...

How about that! We can now see the customized member content!


Michiel Schoofs
Michiel Schoofs
Maud Langaskens
Maud Langaskens

Tech Tip: JetBrains Rider

Check it out here