-
-
Save CheapSk8/79ee3e8d0ac2599974702f47db092846 to your computer and use it in GitHub Desktop.
Sample of Custom Content Area Renderer that permits multiple items per Visitor Group in a Personalized Content Area
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using EpiSandbox.Helpers; | |
using EPiServer; | |
using EPiServer.Core; | |
using EPiServer.Core.Html.StringParsing; | |
using EPiServer.Personalization.VisitorGroups; | |
using EPiServer.Security; | |
using EPiServer.ServiceLocation; | |
using EPiServer.Web; | |
using EPiServer.Web.Mvc; | |
using EPiServer.Web.Mvc.Html; | |
using HtmlAgilityPack; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web.Mvc; | |
namespace EpiSandbox.Business.Rendering | |
{ | |
public class CustomContentAreaRenderer : ContentAreaRenderer | |
{ | |
private readonly IVisitorGroupRoleRepository _visitorGroupRoleRepo; | |
private Lazy<IEnumerable<VisitorGroup>> _visitorGroups = new Lazy<IEnumerable<VisitorGroup>>(() => (ServiceLocator.Current.GetInstance<IVisitorGroupRepository>()).List()); | |
public CustomContentAreaRenderer() : base() | |
{ | |
_visitorGroupRoleRepo = ServiceLocator.Current.GetInstance<IVisitorGroupRoleRepository>(); | |
} | |
public CustomContentAreaRenderer(IVisitorGroupRoleRepository visitorGroupRoleRepository, IContentRenderer contentRenderer, TemplateResolver templateResolver, IContentAreaItemAttributeAssembler attributeAssembler, IContentRepository contentRepository, IContentAreaLoader contentAreaLoader) | |
: base(contentRenderer, templateResolver, attributeAssembler, contentRepository, contentAreaLoader) | |
{ | |
_visitorGroupRoleRepo = visitorGroupRoleRepository; | |
} | |
public override void Render(HtmlHelper htmlHelper, ContentArea contentArea) | |
{ | |
// the majority of this block is boilerplate from Episerver's renderer | |
if (contentArea == null || contentArea.IsEmpty) { return; } | |
TagBuilder contentAreaTagBuilder = null; | |
var viewContext = htmlHelper.ViewContext; | |
if (!IsInEditMode(htmlHelper) && ShouldRenderWrappingElement(htmlHelper)) | |
{ | |
contentAreaTagBuilder = new TagBuilder(GetContentAreaHtmlTag(htmlHelper, contentArea)); | |
AddNonEmptyCssClass(contentAreaTagBuilder, viewContext.ViewData["cssclass"] as string); | |
viewContext.Writer.Write(contentAreaTagBuilder.ToString(TagRenderMode.StartTag)); | |
} | |
RenderContentAreaItems(htmlHelper, GetFilteredItems(contentArea)); // the change to use GetFilteredItems gives us better control of personalized content references | |
if (contentAreaTagBuilder != null) | |
{ | |
viewContext.Writer.Write(contentAreaTagBuilder.ToString(TagRenderMode.EndTag)); | |
} | |
} | |
/// <summary> | |
/// A new method for filtered personalized Items out of the content area in groups allowing | |
/// matches of multiple personalized groups in order of group ID reference. | |
/// </summary> | |
/// <param name="contentArea"></param> | |
/// <returns></returns> | |
private IEnumerable<ContentAreaItem> GetFilteredItems(ContentArea contentArea) | |
{ | |
var itemList = new List<ContentAreaItem>(); | |
// get all contnet items as ContentAreaItemDetail to iterate through | |
var detailedContentItems = contentArea.Fragments.Where(f => f as ContentFragment != null).Select(f => new ContentAreaItemDetail(f as ContentFragment)).ToList(); | |
// for loop so we can manipulate the list and remove items as we process them. this prevents us from having to iterate every item | |
for (ContentAreaItemDetail fragmentItem; (fragmentItem = detailedContentItems.FirstOrDefault()) != null;) | |
{ | |
// we can tell if the current item is a personalized group by checking the ContentGroup value. all personalized items reference a ContentGroup ID. | |
if (string.IsNullOrWhiteSpace(fragmentItem.ContentGroup)) | |
{ | |
// add if user has permission and remove from detail list to prevent duplicates | |
if (shouldUserSeeContent(fragmentItem.ContentFragment)) | |
{ | |
itemList.Add(new ContentAreaItem(fragmentItem.ContentFragment)); | |
} | |
detailedContentItems.Remove(fragmentItem); | |
} | |
else | |
{ | |
var groupItems = detailedContentItems.Where(i => i.ContentGroup == fragmentItem.ContentGroup); | |
// get the first visitor group ID the user is part of | |
var firstMatchID = filterVisitorGroupIds(groupItems?.SelectMany(i => i.VisitorGroupIds)?.Distinct()).FirstOrDefault(); | |
// get all items matching our first matched visitor group ID or all items set for "Everyone else sees" | |
var visitorGroupItems = ( | |
groupItems?.Where(i => i.VisitorGroupIds.Contains(firstMatchID)).NullIfEmpty() | |
?? groupItems.Where(i => !i.VisitorGroupIds.Any()) // the "Everyone else sees" items don't have Visitor Group IDs added to them | |
)?.Where(i => shouldUserSeeContent(i.ContentFragment)); | |
// add any matches to our list and remove all group items to continue iterating through content area items | |
if (visitorGroupItems?.FirstOrDefault() != null) | |
{ | |
itemList.AddRange(visitorGroupItems.Select(i => new ContentAreaItem(i.ContentFragment))); | |
} | |
detailedContentItems.RemoveAll(i => i.ContentGroup == fragmentItem.ContentGroup); | |
} | |
} | |
return itemList; | |
} | |
/// <summary> | |
/// Return only visitor group IDs the current user is a member of | |
/// </summary> | |
/// <param name="visitorGroupIds">The list of IDs to filter</param> | |
/// <returns>A list of VisitorGroup IDs as strings</returns> | |
private List<string> filterVisitorGroupIds(IEnumerable<string> visitorGroupIds) | |
{ | |
var visitorGroups = from id in visitorGroupIds | |
join vg in _visitorGroups.Value | |
on id equals vg.Id.ToString() | |
select vg; | |
var filteredGroupIds = new List<string>(); | |
foreach (var visitorGroup in visitorGroups) | |
{ | |
if (_visitorGroupRoleRepo.TryGetRole(visitorGroup.Name, out var roleProvider) && roleProvider.IsInVirtualRole(PrincipalInfo.CurrentPrincipal, null)) | |
{ | |
filteredGroupIds.Add(visitorGroup.Id.ToString()); | |
} | |
} | |
return filteredGroupIds; | |
} | |
/// <summary> | |
/// Check for access rights to view a specific ContentFragment | |
/// </summary> | |
/// <param name="fragment">The ContentFragment to check access rights against. This applies to Visitor Groups as well.</param> | |
/// <returns>True or False based on whether the current user has Read access</returns> | |
private bool shouldUserSeeContent(ContentFragment fragment) | |
{ | |
var securable = fragment as ISecurable; | |
if (securable == null) { return true; } | |
var securityDescriptor = securable.GetSecurityDescriptor(); | |
return securityDescriptor.HasAccess(PrincipalInfo.CurrentPrincipal, AccessLevel.Read); | |
} | |
/// <summary> | |
/// Represents a Content Area Item with more detail, like their visitor group assignments. | |
/// </summary> | |
private class ContentAreaItemDetail | |
{ | |
/// <summary> | |
/// Gets the content group ID referenced by the Content Fragment | |
/// </summary> | |
public string ContentGroup | |
{ | |
get | |
{ | |
return this.ContentFragment.ContentGroup; | |
} | |
} | |
/// <summary> | |
/// All attributes provided as part of this item. These contain references to the Group ID and Visitor Group IDs assigned for personalization. | |
/// </summary> | |
public Dictionary<string, string> FragmentAttributes { get; set; } = new Dictionary<string, string>(); | |
/// <summary> | |
/// The Content Fragment associated with this content item. | |
/// </summary> | |
public ContentFragment ContentFragment { get; private set; } | |
/// <summary> | |
/// The Content ID of the item as a GUID | |
/// </summary> | |
public Guid ItemGuid { get; private set; } | |
/// <summary> | |
/// A collection of Visitor Group IDs assigned to this item | |
/// </summary> | |
public List<string> VisitorGroupIds { get; set; } = new List<string>(); | |
public ContentAreaItemDetail(ContentFragment fragment) | |
{ | |
this.ContentFragment = fragment ?? throw new ArgumentNullException(nameof(fragment)); | |
this.ItemGuid = fragment.ContentGuid; | |
// we need the reference to the Visitor Group IDs assigned to this item, but they are only available in the InternalFormat HTML markup | |
// so we need to access the attribute in the markup to get the values | |
var fragmentElement = HtmlNode.CreateNode(fragment.InternalFormat); // create HTML Node to access attributes cleanly | |
this.FragmentAttributes = fragmentElement.Attributes.ToDictionary(k => k.Name.ToString(), v => v.Value); // dictionary makes future operations a bit easier and normalized | |
if (FragmentAttributes.TryGetValue(FragmentHandlerHelper.GroupsAttributeName, out var groupIds)) // groups attribute defines the Visitor Groups assigned to this item | |
{ | |
// each Visitor Group ID is added as a comma delimited string value to the Internal Format HTML | |
this.VisitorGroupIds = groupIds.Split(',').ToList(); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment