When creating new property type that stores content references then it’s recommended to implement PropertySoftLinkIndexer class. It will be used for indexing links to other content. For example when item type of PropertyList has ContentReference property then we should implement PropertySoftLinkIndexer returning all links from the list. In this article I will show how to implement generic PropertySoftLinkIndexer for PropertyList.
Let use modified Contact block example to build the list property. I changed the Contact class and now it has two properties Name and Link.
1 2 3 4 5 |
public class Contact { public string Name { get; set; } public ContentReference Link { get; set; } } |
When deleting content that is referenced by contact item we want to see warning message.
Basic implementation
When we defined one PropertyList type with only one ContentReference inside, then we can use simple indexer implementation prepared for this single class. We have to implement IPropertySoftLinkIndexer<IList<Contact>> interface and annotate class with ServiceConfiguration attrbiute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using System; using System.Collections.Generic; using System.Linq; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAbstraction.Internal; using EPiServer.ServiceLocation; using EPiServer.SpecializedProperties; using PropertyListRegistration.demo.Models.Blocks; namespace PropertyListRegistration.Business { [ServiceConfiguration(typeof(IPropertySoftLinkIndexer))] public class ContactListPropertyIndexer : IPropertySoftLinkIndexer<IList<Contact>> { private readonly SoftLinkFactory _softLinkFactory; public ContactListPropertyIndexer(SoftLinkFactory softLinkFactory) { this._softLinkFactory = softLinkFactory; } public IEnumerable<SoftLink> ResolveReferences(IList<Contact> property, IContent owner) { if (owner == null) { throw new ArgumentNullException(nameof(owner)); } if (property == null || !property.Any()) { return Enumerable.Empty<SoftLink>(); } var softLinkList = new List<SoftLink>(); foreach (var contentReference in property.Select(p=>p.Link).Where(c=> !ContentReference.IsNullOrEmpty(c))) { if (ContentReference.SelfReference.Equals(contentReference, true) || ContentReferenceComparer.IgnoreVersion.Equals(contentReference, owner.ContentLink)) { continue; } var softLink = this._softLinkFactory.Create(owner); softLink.ReferencedContentLink = contentReference; softLink.SoftLinkType = ReferenceType.PageLinkReference; softLinkList.Add(softLink); } return (IEnumerable<SoftLink>)softLinkList; } } } |
In ResolveReferences method I’m iterating through all list items and indexing value of “Link” property.
Using reflection to get all references
When Contact class has more than one ContentReference property then we could try to find all links using reflection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using System; using System.Collections.Generic; using System.Linq; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAbstraction.Internal; using EPiServer.ServiceLocation; using EPiServer.SpecializedProperties; using PropertyListRegistration.demo.Models.Blocks; namespace PropertyListRegistration.demo.Business { [ServiceConfiguration(typeof(IPropertySoftLinkIndexer))] public class ReflectionBasedContactListPropertyIndexer : IPropertySoftLinkIndexer<IList<Contact>> { private readonly SoftLinkFactory _softLinkFactory; public ReflectionBasedContactListPropertyIndexer(SoftLinkFactory softLinkFactory) { this._softLinkFactory = softLinkFactory; } public IEnumerable<SoftLink> ResolveReferences(IList<Contact> property, IContent owner) { var contentReferences = GetContentReferences(property); return ResolveReferences(contentReferences, owner); } private static IList<ContentReference> GetContentReferences<T>(IEnumerable<T> propertyValue) where T: class { var contentReferences = new List<ContentReference>(); foreach (var item in propertyValue) { foreach (var propertyInfo in item.GetType().GetProperties()) { if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(ContentReference))) { continue; } var value = propertyInfo.GetValue(item) as ContentReference; if (value == null) { continue; } contentReferences.Add(value); } } return contentReferences; } private IEnumerable<SoftLink> ResolveReferences(IList<ContentReference> property, IContent owner) { if (owner == null) { throw new ArgumentNullException(nameof(owner)); } if (property == null || !property.Any()) { return Enumerable.Empty<SoftLink>(); } var softLinkList = new List<SoftLink>(); foreach (var contentReference in property) { if (ContentReference.SelfReference.Equals(contentReference, true) || ContentReferenceComparer.IgnoreVersion.Equals(contentReference, owner.ContentLink)) { continue; } var softLink = this._softLinkFactory.Create(owner); softLink.ReferencedContentLink = contentReference; softLink.SoftLinkType = ReferenceType.PageLinkReference; softLinkList.Add(softLink); } return (IEnumerable<SoftLink>)softLinkList; } } } |
In GetContentReferences method I’m finding all ContentReferences using reflection and then pass the result to the ResolveReferences method responsible for indexing.
Using build-in ContentReference list indexer
In the private ResolveReferences method I’m indexing references based on ContentReferences list parameter. There is already an indexer for list of ContentReferences. We could try to use it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
using System; using System.Collections.Generic; using System.Linq; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAbstraction.Internal; using EPiServer.ServiceLocation; using EPiServer.SpecializedProperties; using PropertyListRegistration.demo.Models.Blocks; namespace PropertyListRegistration.demo.Business { [ServiceConfiguration(typeof(IPropertySoftLinkIndexer))] public class ContentReferencesListBasedPropertyIndexer : IPropertySoftLinkIndexer<IList<Contact>> { private readonly SoftLinkFactory _softLinkFactory; public ContentReferencesListBasedPropertyIndexer(SoftLinkFactory softLinkFactory) { this._softLinkFactory = softLinkFactory; } public IEnumerable<SoftLink> ResolveReferences(IList<Contact> property, IContent owner) { var contentReferences = GetContentReferences(property); var contentReferenceListIndexer = FindContentReferenceIndexer(); var result = contentReferenceListIndexer.ResolveReferences(contentReferences, owner); return result; } private static IList<ContentReference> GetContentReferences<T>(IEnumerable<T> propertyValue) where T : class { var contentReferences = new List<ContentReference>(); foreach (var item in propertyValue) { foreach (var propertyInfo in item.GetType().GetProperties()) { if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(ContentReference))) { continue; } var value = propertyInfo.GetValue(item) as ContentReference; if (value == null) { continue; } contentReferences.Add(value); } } return contentReferences; } private IPropertySoftLinkIndexer<IList<ContentReference>> FindContentReferenceIndexer() { var propertySoftLinkIndexers = ServiceLocator.Current.GetAllInstances<IPropertySoftLinkIndexer>(); foreach (var propertySoftLinkIndexer in propertySoftLinkIndexers) { var renderInterfaces = propertySoftLinkIndexer.GetType().GetInterfaces().Where(t => t.Name.Equals(typeof(IPropertySoftLinkIndexer<>).Name)); foreach (var renderInterface in renderInterfaces) { var genericArgument = renderInterface.GetGenericArguments().SingleOrDefault(); if (genericArgument == typeof(IList<ContentReference>)) { return (IPropertySoftLinkIndexer<IList<ContentReference>>)propertySoftLinkIndexer; } } } throw new ArgumentException("Cannot find implementation for IList<ContentReference> indexer"); } } } |
In FindContentReferenceIndexer method I get all available instances of IPropertySoftLinkIndexer and search for the class with implemented IPropertySoftLinkIndexer<IList<ContentReference>> interface. ContentReference list indexer has ResolveReferences that takes list of ContentReferences as parameter and returns SoftLinks list.
I had to use ServiceLocator instead of dependency injection, because of bi-directional dependencies.
Making indexer generic
When we have many defiitions of PropertyList with different ContentReference properties inside then we could try to use generic implementation of ListPropertyIndexer. Instead of using IPropertySoftLinkIndexer<IList<Contact>>, the indexer will implement generic version of IPropertySoftLinkIndexer<IList<T>>. We won’t use ServiceConfiguration anymore, because the indexer will be registered in ConfigurableModule.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
using System; using System.Collections.Generic; using System.Linq; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAbstraction.Internal; using EPiServer.ServiceLocation; using EPiServer.SpecializedProperties; namespace PropertyListRegistration.demo.Business { //registered automatically through SoftLinkIndexerRegistrationModule //[ServiceConfiguration(typeof(IPropertySoftLinkIndexer))] public class ContentReferencesListBasedPropertyIndexer : IPropertySoftLinkIndexer<IList<T>> { private readonly SoftLinkFactory _softLinkFactory; public ContentReferencesListBasedPropertyIndexer(SoftLinkFactory softLinkFactory) { this._softLinkFactory = softLinkFactory; } public IEnumerable<SoftLink> ResolveReferences(IList<T> property, IContent owner) { var contentReferences = GetContentReferences(property); var contentReferenceListIndexer = FindContentReferenceIndexer(); var result = contentReferenceListIndexer.ResolveReferences(contentReferences, owner); return result; } private static IList<ContentReference> GetContentReferences(IEnumerable<T> propertyValue) { var contentReferences = new List<ContentReference>(); foreach (var item in propertyValue) { foreach (var propertyInfo in item.GetType().GetProperties()) { if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(ContentReference))) { continue; } var value = propertyInfo.GetValue(item) as ContentReference; if (value == null) { continue; } contentReferences.Add(value); } } return contentReferences; } private static IPropertySoftLinkIndexer<IList<ContentReference>> FindContentReferenceIndexer() { var propertySoftLinkIndexers = ServiceLocator.Current.GetAllInstances<IPropertySoftLinkIndexer>(); foreach (var propertySoftLinkIndexer in propertySoftLinkIndexers) { var renderInterfaces = propertySoftLinkIndexer.GetType().GetInterfaces().Where(t => t.Name.Equals(typeof(IPropertySoftLinkIndexer<>).Name)); foreach (var renderInterface in renderInterfaces) { var genericArgument = renderInterface.GetGenericArguments().SingleOrDefault(); if (genericArgument == typeof(IList<ContentReference>)) { return (IPropertySoftLinkIndexer<IList<ContentReference>>)propertySoftLinkIndexer; } } } throw new ArgumentException("Cannot find implementation for IList<ContentReference> indexer"); } } } |
The ConfigurableModule filters out all PropertyList types and registers indexers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
using System; using System.Linq; using EPiServer.Core; using EPiServer.Framework; using EPiServer.Framework.Initialization; using EPiServer.Framework.TypeScanner; using EPiServer.PlugIn; using EPiServer.ServiceLocation; using EPiServer.ServiceLocation.Compatibility; using EPiServer.SpecializedProperties; namespace PropertyListRegistration { [InitializableModule] [ModuleDependency(typeof(ServiceContainerInitialization))] public class SoftLinkIndexerRegistrationModule: IConfigurableModule { public void Initialize(InitializationEngine context) { } public void Uninitialize(InitializationEngine context) { } public void ConfigureContainer(ServiceConfigurationContext context) { var typeScannerLookup = context.Container.GetInstance<ITypeScannerLookup>(); var types = typeScannerLookup.AllTypes; foreach (var type in types) { var hasAttributes = type.GetCustomAttributes(typeof(PropertyDefinitionTypePlugInAttribute), true).Any(); if (hasAttributes == false) { continue; } // PropertyContentReferenceList already has indexer implementation if (type == typeof(PropertyContentReferenceList)) { continue; } var baseType = type.BaseType; while (baseType != null) { if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(PropertyList<>)) { context.Services.Configure(c => { RegisterIndexer(c, baseType); }); break; } baseType = baseType.BaseType; } } } private static void RegisterIndexer(ConfigurationBuilder c, Type baseType) { var genericArgument = baseType.GetGenericArguments().SingleOrDefault(); c.For(typeof(IPropertySoftLinkIndexer)).Use(typeof(GenericListIndexer<>).MakeGenericType(genericArgument)); } } } |
Indexing PropertyList with other indexable properties
The item of the PropertyList can have other indexable properties like list of ContentReferences.
We could have indexing logic for each of those properties inside indexer, but I prepared generic solution. Every content is indexed by ContentSoftLinkIndexer.
I defined FakeContent class used to wrap obect into Content. For each PropertyList item I’m creating FakeContent and then execute GetLinks method. In this example the indexer will return all links from ContentReference list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
using System; using System.Collections.Generic; using System.Linq; using EPiServer.Core; using EPiServer.Core.Internal; using EPiServer.DataAbstraction; using EPiServer.ServiceLocation; using EPiServer.SpecializedProperties; namespace PropertyListRegistration { //registered automatically through SoftLinkIndexerRegistrationModule //[ServiceConfiguration(typeof(IPropertySoftLinkIndexer))] public class GenericListIndexer<T> : IPropertySoftLinkIndexer<IList<T>> { private readonly ServiceAccessor<ContentSoftLinkIndexer> _contentSoftLinkIndexer; public GenericListIndexer(ServiceAccessor<ContentSoftLinkIndexer> contentSoftLinkIndexer) { // Have to use ServiceLocator to get ContentSoftLinkIndexer because of bi-directional dependencies _contentSoftLinkIndexer = contentSoftLinkIndexer; } public IEnumerable<SoftLink> ResolveReferences(IList<T> propertyValue, IContent owner) { // add all ContentReferences properties var softLinks = IndexContentReferenceProperties(propertyValue, owner).ToList(); // add all properties that has implementation for IPropertySoftLinkIndexer var contentSoftLinkIndexer = this._contentSoftLinkIndexer(); foreach (var p in propertyValue) { var test = new FakeContent(p) {ContentLink = owner.ContentLink}; softLinks.AddRange(contentSoftLinkIndexer.GetLinks(test)); } return softLinks; } private IEnumerable<SoftLink> IndexContentReferenceProperties(IEnumerable<T> propertyValue, IContent owner) { var contentReferences = GetContentReferences(propertyValue); var contentReferenceListIndexer = FindContentReferenceIndexer(); var result = contentReferenceListIndexer.ResolveReferences(contentReferences, owner); return result; } private IPropertySoftLinkIndexer<IList<ContentReference>> FindContentReferenceIndexer() { var propertySoftLinkIndexers = ServiceLocator.Current.GetAllInstances<IPropertySoftLinkIndexer>(); foreach (var propertySoftLinkIndexer in propertySoftLinkIndexers) { var renderInterfaces = propertySoftLinkIndexer.GetType().GetInterfaces().Where(t => t.Name.Equals(typeof(IPropertySoftLinkIndexer<>).Name)); foreach (var renderInterface in renderInterfaces) { var genericArgument = renderInterface.GetGenericArguments().SingleOrDefault(); if (genericArgument == typeof(IList<ContentReference>)) { return (IPropertySoftLinkIndexer<IList<ContentReference>>) propertySoftLinkIndexer; } } } throw new ArgumentException("Cannot find implementation for IList<ContentReference> indexer"); } private static List<ContentReference> GetContentReferences(IEnumerable<T> propertyValue) { var contentReferences = new List<ContentReference>(); foreach (var item in propertyValue) { foreach (var propertyInfo in item.GetType().GetProperties()) { if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(ContentReference))) { continue; } var value = propertyInfo.GetValue(item) as ContentReference; if (value == null) { continue; } contentReferences.Add(value); } } return contentReferences; } private class FakeContent : BasicContent { private readonly object _instance; public FakeContent(object instance) { _instance = instance; } public override PropertyDataCollection Property { get { var properties = _instance.GetType().GetProperties(); var propertyDataCollection = new PropertyDataCollection(); foreach (var propertyInfo in properties) { var rawProperty = new FakePropertyData(propertyInfo.PropertyType) { Value = propertyInfo.GetValue(_instance), IsDynamicProperty = false, Name = propertyInfo.Name }; propertyDataCollection.Add(rawProperty); } return propertyDataCollection; } } } private class FakePropertyData : PropertyData { public FakePropertyData(Type propertyValueType) { PropertyValueType = propertyValueType; } public override object Value { get; set; } public override PropertyDataType Type => PropertyDataType.Json; public override Type PropertyValueType { get; } protected override void SetDefaultValue() { } public override PropertyData ParseToObject(string value) { throw new NotImplementedException(); } public override void ParseToSelf(string value) { throw new NotImplementedException(); } public override bool IsNull => this.Value == null; } } } |
The full source code can be found on Github.