Files
Umbraco-CMS/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs

116 lines
5.6 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Implements a strongly typed content model factory
/// </summary>
public class PublishedContentModelFactory : IPublishedContentModelFactory
{
private readonly Dictionary<string, ModelInfo> _modelInfos;
private class ModelInfo
{
public Type ParameterType { get; set; }
2017-09-25 08:59:32 +02:00
public Func<IPublishedElement, IPublishedElement> Ctor { get; set; }
public Type ModelType { get; set; }
}
public Dictionary<string, Type> ModelTypeMap { get; }
2014-05-19 13:15:47 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentModelFactory"/> class with types.
/// </summary>
/// <param name="types">The model types.</param>
/// <remarks>
/// <para>Types must implement <c>IPublishedContent</c> and have a unique constructor that
/// accepts one IPublishedContent as a parameter.</para>
/// <para>To activate,</para>
/// <code>
2017-05-30 15:33:13 +02:00
/// var types = TypeLoader.Current.GetTypes{PublishedContentModel}();
2014-05-19 13:15:47 +02:00
/// var factory = new PublishedContentModelFactoryImpl(types);
/// PublishedContentModelFactoryResolver.Current.SetFactory(factory);
/// </code>
/// </remarks>
public PublishedContentModelFactory(IEnumerable<Type> types)
{
2017-09-25 08:59:32 +02:00
var ctorArgTypes = new[] { typeof(IPublishedElement) };
var modelInfos = new Dictionary<string, ModelInfo>(StringComparer.InvariantCultureIgnoreCase);
ModelTypeMap = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
foreach (var type in types)
{
ConstructorInfo constructor = null;
Type parameterType = null;
foreach (var ctor in type.GetConstructors())
{
var parms = ctor.GetParameters();
2017-09-25 08:59:32 +02:00
if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType))
{
if (constructor != null)
2017-09-25 08:59:32 +02:00
throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement.");
constructor = ctor;
parameterType = parms[0].ParameterType;
}
}
if (constructor == null)
2017-09-25 08:59:32 +02:00
throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement.");
var attribute = type.GetCustomAttribute<PublishedContentModelAttribute>(false); // fixme rename FacadeModelAttribute
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo))
throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\".");
// see Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks
// using ctor.Invoke is horrible, cannot even consider it,
// then expressions are 6-10x slower than direct ctor, and
// dynamic methods are 2-3x slower than direct ctor = best
// much faster with a dynamic method but potential MediumTrust issues - which we don't support
// here http://stackoverflow.com/questions/16363838/how-do-you-call-a-constructor-via-an-expression-tree-on-an-existing-object
2017-09-25 08:59:32 +02:00
var meth = new DynamicMethod(string.Empty, typeof(IPublishedElement), ctorArgTypes, type.Module, true);
var gen = meth.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Newobj, constructor);
gen.Emit(OpCodes.Ret);
2017-09-25 08:59:32 +02:00
var func = (Func<IPublishedElement, IPublishedElement>) meth.CreateDelegate(typeof (Func<IPublishedElement, IPublishedElement>));
// fast enough and works in MediumTrust - but we don't
// read http://boxbinary.com/2011/10/how-to-run-a-unit-test-in-medium-trust-with-nunitpart-three-umbraco-framework-testing/
//var exprArg = Expression.Parameter(typeof(IPropertySet), "content");
//var exprNew = Expression.New(constructor, exprArg);
//var expr = Expression.Lambda<Func<IPropertySet, IPropertySet>>(exprNew, exprArg);
//var func = expr.Compile();
modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type };
ModelTypeMap[typeName] = type;
}
_modelInfos = modelInfos.Count > 0 ? modelInfos : null;
}
2017-09-25 08:59:32 +02:00
public IPublishedElement CreateModel(IPublishedElement set)
{
// fail fast
if (_modelInfos == null)
return set;
if (_modelInfos.TryGetValue(set.ContentType.Alias, out ModelInfo modelInfo) == false)
return set;
// ReSharper disable once UseMethodIsInstanceOfType
if (modelInfo.ParameterType.IsAssignableFrom(set.GetType()) == false)
throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {set.GetType().FullName}.");
return modelInfo.Ctor(set);
}
}
}