Last active
July 26, 2019 11:56
-
-
Save joseftw/d3f4164bbcd47b8f38e0866b31eefa44 to your computer and use it in GitHub Desktop.
Support for "injecting" AllowedTypes in EPiServer when ContentTypes doesn't know about each other. Raw
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 System.Web.Mvc; | |
using EPiServer.DataAbstraction.RuntimeModel; | |
using EPiServer.Framework; | |
using EPiServer.Framework.Initialization; | |
using EPiServer.ServiceLocation; | |
using ModularAllowedTypes.Business.Rendering; | |
using EPiServer.Web.Mvc; | |
using EPiServer.Web.Mvc.Html; | |
using StructureMap; | |
namespace InjectedAllowedTypes.Business.Initialization | |
{ | |
[InitializableModule] | |
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))] | |
public class DependencyResolverInitialization : IConfigurableModule | |
{ | |
public void ConfigureContainer(ServiceConfigurationContext context) | |
{ | |
context.Container.Configure(ConfigureContainer); | |
DependencyResolver.SetResolver(new StructureMapDependencyResolver(context.Container)); | |
} | |
private static void ConfigureContainer(ConfigurationExpression container) | |
{ | |
//Swap out the default ContentRenderer for our custom | |
container.For<IContentRenderer>().Use<ErrorHandlingContentRenderer>(); | |
container.For<ContentAreaRenderer>().Use<AlloyContentAreaRenderer>(); | |
//Implementations for custom interfaces can be registered here. | |
container.For<IContentTypeModelAssigner>().Use<InjectedContentDataAttributeScanningAssigner>(); | |
} | |
public void Initialize(InitializationEngine context) | |
{ | |
} | |
public void Uninitialize(InitializationEngine context) | |
{ | |
} | |
public void Preload(string[] parameters) | |
{ | |
} | |
} | |
} |
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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using EPiServer.Core; | |
using EPiServer.DataAbstraction.RuntimeModel; | |
using EPiServer.DataAnnotations; | |
using Feature.MediaPage; | |
using Feature.MusicBlock; | |
namespace InjectedAllowedTypes | |
{ | |
public static class InjectedAllowedTypes | |
{ | |
public static AllowedTypesAttribute GetMergedAllowedTypesAttribute(AllowedTypesAttribute allowedTypesAttribute, ContentTypeModel contentTypeModel, PropertyInfo property) | |
{ | |
var allCustomAttributes = GetCustomAllowedTypesAttributes(); | |
var key = string.Format("{0}.{1}", contentTypeModel.ModelType.Name, property.Name); | |
var customAttributeForType = allCustomAttributes.FirstOrDefault(x => x.Key == key); | |
if (customAttributeForType.Value == null) | |
{ | |
return allowedTypesAttribute; | |
} | |
var existingAllowedTypes = allowedTypesAttribute != null ? allowedTypesAttribute.AllowedTypes : new Type[] {}; | |
var existingRestrictedTypes = allowedTypesAttribute != null ? allowedTypesAttribute.RestrictedTypes : new Type[] {}; | |
var mergedAllowedTypesAttribute = new AllowedTypesAttribute | |
{ | |
AllowedTypes = | |
existingAllowedTypes.Concat(customAttributeForType.Value.AllowedTypes).Distinct().ToArray(), | |
RestrictedTypes = | |
existingRestrictedTypes.Concat(customAttributeForType.Value.RestrictedTypes).Distinct().ToArray() | |
}; | |
//It seems like EPiServer adds IContentData automatically, so we remove that one if we have a custom "attribute" value for the AllowedTypes. | |
if (customAttributeForType.Value.AllowedTypes.Any()) | |
{ | |
mergedAllowedTypesAttribute.AllowedTypes = | |
mergedAllowedTypesAttribute.AllowedTypes.Where(x => x != typeof (IContentData)).ToArray(); | |
} | |
return mergedAllowedTypesAttribute; | |
} | |
private static Dictionary<string, AllowedTypesAttribute> GetCustomAllowedTypesAttributes() | |
{ | |
return new Dictionary<string, AllowedTypesAttribute> | |
{ | |
{ | |
string.Format("{0}.{1}",typeof(MediaPage).Name, "ContentArea"), new AllowedTypesAttribute | |
{ | |
AllowedTypes = new[] {typeof (MusicBlock)} | |
} | |
} | |
}; | |
} | |
} | |
} |
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 System; | |
using System.ComponentModel.DataAnnotations; | |
using System.Globalization; | |
using System.Linq; | |
using System.Reflection; | |
using EPiServer.Core; | |
using EPiServer.DataAbstraction.RuntimeModel; | |
using EPiServer.DataAnnotations; | |
namespace InjectedAllowedTypes | |
{ | |
public class InjectedContentDataAttributeScanningAssigner : ContentDataAttributeScanningAssigner | |
{ | |
/// <summary> | |
/// Almost exact implementation of the AssignValuesToPropertyDefinition in the ContentDataAttributeScanningAssigner | |
/// the only thing that differs is the added call to CustomAllowedTypes.GetMergedAllowedTypesAttribute. | |
/// That call allows us to add more types to the Allowed/RestricedTypes without using the AllowedTypes attribute. | |
/// </summary> | |
/// <param name="propertyDefinitionModel"></param> | |
/// <param name="property"></param> | |
/// <param name="parentModel"></param> | |
public override void AssignValuesToPropertyDefinition(PropertyDefinitionModel propertyDefinitionModel, PropertyInfo property, ContentTypeModel parentModel) | |
{ | |
if (property.IsAutoGenerated() && !property.IsAutoVirtualPublic()) | |
{ | |
var exceptionMessage = string.Format(CultureInfo.InvariantCulture, | |
"The property '{0}' on the content type '{1}' is autogenerated but not virtual declared.", | |
property.Name, property.DeclaringType.Name); | |
throw new InvalidOperationException(exceptionMessage); | |
} | |
//This is our added logic to merge a predefined AllowedTypes attribute with our own AllowedTypes specified in code. | |
#region ModularAllowedTypes | |
var customAttributes = Attribute.GetCustomAttributes(property, true).ToList(); | |
if (customAttributes.Any(x => x is AllowedTypesAttribute)) | |
{ | |
var existingAllowedTypesAttribute = | |
customAttributes.FirstOrDefault(x => x is AllowedTypesAttribute) as AllowedTypesAttribute; | |
var mergedAllowedTypesAttribute = | |
InjectedAllowedTypes.GetMergedAllowedTypesAttribute(existingAllowedTypesAttribute, parentModel, property); | |
customAttributes.Remove(existingAllowedTypesAttribute); | |
customAttributes.Add(mergedAllowedTypesAttribute); | |
} | |
else | |
{ | |
var mergedAllowedTypesAttribute = | |
InjectedAllowedTypes.GetMergedAllowedTypesAttribute(null, parentModel, property); | |
if (mergedAllowedTypesAttribute != null) | |
{ | |
customAttributes.Add(mergedAllowedTypesAttribute); | |
} | |
} | |
#endregion | |
foreach (var attribute in customAttributes) | |
{ | |
if (attribute is BackingTypeAttribute) | |
{ | |
var backingTypeAttribute = attribute as BackingTypeAttribute; | |
if (backingTypeAttribute.BackingType != null) | |
{ | |
if (!typeof(PropertyData).IsAssignableFrom(backingTypeAttribute.BackingType)) | |
{ | |
var exceptionMessage = string.Format(CultureInfo.InvariantCulture, | |
"The backing type '{0}' attributed to the property '{1}' on '{2}' does not inherit PropertyData.", | |
backingTypeAttribute.BackingType.FullName, property.Name, property.DeclaringType.Name); | |
throw new TypeMismatchException(exceptionMessage); | |
} | |
if (property.IsAutoVirtualPublic()) | |
{ | |
ValidateTypeCompability(property, backingTypeAttribute.BackingType); | |
} | |
} | |
propertyDefinitionModel.BackingType = backingTypeAttribute.BackingType; | |
} | |
else if (attribute is AllowedTypesAttribute) | |
{ | |
var allowedTypesAttribute = attribute as AllowedTypesAttribute; | |
VerifyAllowedTypesAttribute(allowedTypesAttribute, property); | |
} | |
else if (attribute is DisplayAttribute) | |
{ | |
var displayAttribute = attribute as DisplayAttribute; | |
propertyDefinitionModel.DisplayName = displayAttribute.GetName(); | |
propertyDefinitionModel.Description = displayAttribute.GetDescription(); | |
propertyDefinitionModel.Order = displayAttribute.GetOrder(); | |
propertyDefinitionModel.TabName = displayAttribute.GetGroupName(); | |
} | |
else if (attribute is ScaffoldColumnAttribute) | |
{ | |
var scaffoldColumnAttribute = attribute as ScaffoldColumnAttribute; | |
propertyDefinitionModel.AvailableInEditMode = scaffoldColumnAttribute.Scaffold; | |
} | |
else if (attribute is CultureSpecificAttribute) | |
{ | |
var specificAttribute = attribute as CultureSpecificAttribute; | |
ThrowIfBlockProperty(specificAttribute, property); | |
propertyDefinitionModel.CultureSpecific = specificAttribute.IsCultureSpecific; | |
} | |
else if (attribute is RequiredAttribute) | |
{ | |
var requiredAttribute = attribute as RequiredAttribute; | |
ThrowIfBlockProperty(requiredAttribute, property); | |
propertyDefinitionModel.Required = true; | |
} | |
else if (attribute is SearchableAttribute) | |
{ | |
var searchableAttribute = attribute as SearchableAttribute; | |
ThrowIfBlockProperty(searchableAttribute, property); | |
propertyDefinitionModel.Searchable = searchableAttribute.IsSearchable; | |
} | |
else if (attribute is UIHintAttribute) | |
{ | |
var uiHintAttribute = attribute as UIHintAttribute; | |
if (!string.IsNullOrEmpty(uiHintAttribute.UIHint)) | |
{ | |
if (string.Equals(uiHintAttribute.PresentationLayer, "website")) | |
{ | |
propertyDefinitionModel.TemplateHint = uiHintAttribute.UIHint; | |
} | |
else if (string.IsNullOrEmpty(uiHintAttribute.PresentationLayer) && | |
string.IsNullOrEmpty(propertyDefinitionModel.TemplateHint)) | |
{ | |
propertyDefinitionModel.TemplateHint = uiHintAttribute.UIHint; | |
} | |
} | |
} | |
propertyDefinitionModel.Attributes.AddAttribute(attribute); | |
} | |
} | |
//Calls the VerifyAllowedTypesAttribute in the parent class with reflection. | |
//NOTE: Don't rename this method since the name of the method is used to call the parent method. | |
private static void VerifyAllowedTypesAttribute(AllowedTypesAttribute attribute, PropertyInfo property) | |
{ | |
var methodName = MethodBase.GetCurrentMethod().Name; | |
var method = GetMethodFromParent(methodName, BindingFlags.Static | BindingFlags.NonPublic); | |
var parameters = new object[] {attribute, property}; | |
method.Invoke(null, parameters); | |
} | |
//Calls the ValidateTypeCompability in the parent class with reflection. | |
//NOTE: Don't rename this method since the name of the method is used to call the parent method. | |
private static void ValidateTypeCompability(PropertyInfo property, Type backingType) | |
{ | |
var methodName = MethodBase.GetCurrentMethod().Name; | |
var method = GetMethodFromParent(methodName, BindingFlags.Static | BindingFlags.NonPublic); | |
var parameters = new object[] {property, backingType}; | |
method.Invoke(null, parameters); | |
} | |
//Calls the ThrowIfBlockProperty in the parent class with reflection. | |
//NOTE: Don't rename this method since the name of the method is used to call the parent method. | |
private static void ThrowIfBlockProperty(Attribute attribute, PropertyInfo property) | |
{ | |
var methodName = MethodBase.GetCurrentMethod().Name; | |
var method = GetMethodFromParent(methodName, BindingFlags.Static | BindingFlags.NonPublic); | |
var parameters = new object[] {attribute, property}; | |
method.Invoke(null, parameters); | |
} | |
private static MethodInfo GetMethodFromParent(string methodName, BindingFlags bindingFlags) | |
{ | |
var type = typeof (ContentDataAttributeScanningAssigner); | |
var method = type.GetMethod(methodName, bindingFlags); | |
if (method == null) | |
{ | |
var exceptionMessage = | |
string.Format( | |
"Couldn't find the {0} method in EPiServer class {1}. Maybe it has been renamed/removed?", | |
methodName, type.FullName); | |
throw new MissingMethodException(exceptionMessage); | |
} | |
return method; | |
} | |
} | |
} |
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 EPiServer.Core; | |
using EPiServer.DataAnnotations; | |
namespace Feature.MediaPage | |
{ | |
[ContentType(DisplayName = "Media Page", GUID = "5b393025-d856-4392-aa99-61f1a468ea79", Description = "")] | |
public class MediaPage : PageData | |
{ | |
[AllowedTypes(AllowedTypes = new [] {typeof(VideoBlock)})] | |
public virtual ContentArea ContentArea { get; set; } | |
} | |
} |
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 System; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
namespace ModularAllowedTypes | |
{ | |
public static class PropertyInfoExtensions | |
{ | |
public static bool IsAutoGenerated(this PropertyInfo p) | |
{ | |
if (p.GetGetMethod() != null && p.GetSetMethod() != null && p.GetGetMethod().GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1) | |
return p.GetSetMethod().GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length == 1; | |
return false; | |
} | |
public static bool IsAutoVirtualPublic(this PropertyInfo self) | |
{ | |
if (self == null) | |
throw new ArgumentNullException("self"); | |
if (self.IsAutoGenerated()) | |
return (self.GetAccessors(true)).All(m => | |
{ | |
if (m.IsVirtual) | |
return m.IsPublic; | |
return false; | |
}); | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment