Skip to content

Instantly share code, notes, and snippets.

@joseftw
Last active July 26, 2019 11:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joseftw/d3f4164bbcd47b8f38e0866b31eefa44 to your computer and use it in GitHub Desktop.
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
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)
{
}
}
}
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)}
}
}
};
}
}
}
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;
}
}
}
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; }
}
}
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