Exam Area: Area 1 – Product Knowledge (15%) Reference: https://docs.developers.optimizely.com/content-management-system/docs/visitor-groups
Personalization in CMS 12 lets editors show different content to different audiences without developer involvement. It is based on Visitor Groups.
Visitor Group: "Returning Visitors from Sweden"
Criteria:
+ Geographic location: Country = Sweden
+ Visit count: > 3
Content personalised for this group:
→ Hero banner: Swedish-specific offer
→ CTA button: "Visa erbjudande"
A Visitor Group is a named segment defined by one or more criteria:
| Criterion Type | Example |
|---|---|
| Geographic Location | Country = "United Kingdom" |
| Browser type | Firefox / Chrome / Safari |
| Time of day | Between 09:00 and 17:00 |
| Number of visits | More than 5 visits |
| Role membership | User is in "PremiumCustomers" role |
| Query string | ?campaign=summer2024 |
| Referring URL | Came from Google |
| Custom criterion | (developed by the team) |
Admin → Visitor Groups → Create new
→ Name: "Returning UK Visitors"
→ Add criterion: Geographic Location → Country = UK
→ Add criterion: Number of visits > 2
→ Logic: All criteria must match (AND) / Any (OR)
→ Save
In Edit View, editors personalise Content Area items:
Content Area → Add block → Right-click block → "Personalise"
→ Select Visitor Group: "Returning UK Visitors"
→ This block is only shown to matching visitors
Multiple blocks can be added to the same area slot with different visitor groups — CMS shows the first matching one.
using EPiServer.Personalization.VisitorGroups;
public class PersonalisationService
{
private readonly IVisitorGroupRoleRepository _visitorGroupRepo;
private readonly IVisitorGroupRepository _repository;
public PersonalisationService(
IVisitorGroupRoleRepository visitorGroupRoleRepo,
IVisitorGroupRepository repository)
{
_visitorGroupRepo = visitorGroupRoleRepo;
_repository = repository;
}
public bool IsInGroup(string groupName, IPrincipal user, HttpContext context)
{
// Uses the visitor group role provider
return context.User.IsInRole("G:" + groupName);
}
public IEnumerable<VisitorGroup> GetAllGroups()
=> _repository.List();
}
[VisitorGroupCriterion(
Category = "Custom",
DisplayName = "First Time Visitor",
Description = "True if the visitor has never been to the site before")]
public class FirstTimeVisitorCriterion : CriterionBase<EmptyCriterionModel>
{
public override bool IsMatch(IPrincipal principal, HttpContext httpContext)
{
// Check if visitor has a "visited" cookie
return !httpContext.Request.Cookies.ContainsKey("_visited");
}
}
Editors can preview what the site looks like for a specific visitor group:
Edit View → Context Panel → Visitor Groups gadget
→ Select group: "Returning UK Visitors"
→ Preview renders as if the current user is in that group
CRITICAL: Personalised content must NOT be cached by CDN or output cache, because different visitors see different content from the same URL.
// Mark responses with personalised content as private/no-cache
Response.Headers["Cache-Control"] = "private, no-store";
// OR use VaryBy headers if using server-side output cache
[ResponseCache(VaryByHeader = "Cookie", Duration = 0)]
// Get all visitor groups
var groups = _visitorGroupRepository.List();
// Get specific group
var group = _visitorGroupRepository.Load("group-guid");
// Save custom group programmatically
_visitorGroupRepository.Save(new VisitorGroup
{
Name = "VIP Customers",
Criterias = new List<VisitorGroupCriteria> { ... }
});
CriterionBase<T> and decorate with [VisitorGroupCriterion])