2014-05-19 13:01:49 +02:00
using System ;
using System.Collections.Generic ;
2017-07-21 17:19:00 +02:00
using System.Reflection ;
using System.Reflection.Emit ;
2014-05-19 13:01:49 +02:00
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Implements a strongly typed content model factory
/// </summary>
public class PublishedContentModelFactory : IPublishedContentModelFactory
{
2017-07-21 17:19:00 +02:00
private readonly Dictionary < string , ModelInfo > _modelInfos ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
private class ModelInfo
{
public Type ParameterType { get ; set ; }
public Func < IPropertySet , IPropertySet > Ctor { get ; set ; }
public Type ModelType { get ; set ; }
}
public Dictionary < string , Type > ModelTypeMap { get ; }
2014-05-19 13:01:49 +02:00
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 )
2014-05-19 13:01:49 +02:00
{
2017-07-21 17:19:00 +02:00
var ctorArgTypes = new [ ] { typeof ( IPropertySet ) } ;
var modelInfos = new Dictionary < string , ModelInfo > ( StringComparer . InvariantCultureIgnoreCase ) ;
ModelTypeMap = new Dictionary < string , Type > ( StringComparer . InvariantCultureIgnoreCase ) ;
2014-05-19 13:01:49 +02:00
foreach ( var type in types )
{
2017-07-21 17:19:00 +02:00
ConstructorInfo constructor = null ;
Type parameterType = null ;
foreach ( var ctor in type . GetConstructors ( ) )
{
var parms = ctor . GetParameters ( ) ;
if ( parms . Length = = 1 & & typeof ( IPropertySet ) . IsAssignableFrom ( parms [ 0 ] . ParameterType ) )
{
if ( constructor ! = null )
throw new InvalidOperationException ( $"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPropertySet." ) ;
constructor = ctor ;
parameterType = parms [ 0 ] . ParameterType ;
}
}
2014-05-19 13:01:49 +02:00
if ( constructor = = null )
2017-07-21 17:19:00 +02:00
throw new InvalidOperationException ( $"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPropertySet." ) ;
var attribute = type . GetCustomAttribute < PublishedContentModelAttribute > ( false ) ; // fixme rename FacadeModelAttribute
2014-05-19 13:01:49 +02:00
var typeName = attribute = = null ? type . Name : attribute . ContentTypeAlias ;
2017-07-21 17:19:00 +02:00
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 } \ "." ) ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
// 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
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
// much faster with a dynamic method but potential MediumTrust issues - which we don't support
2014-05-19 13:01:49 +02:00
// here http://stackoverflow.com/questions/16363838/how-do-you-call-a-constructor-via-an-expression-tree-on-an-existing-object
2017-07-21 17:19:00 +02:00
var meth = new DynamicMethod ( string . Empty , typeof ( IPropertySet ) , ctorArgTypes , type . Module , true ) ;
var gen = meth . GetILGenerator ( ) ;
gen . Emit ( OpCodes . Ldarg_0 ) ;
gen . Emit ( OpCodes . Newobj , constructor ) ;
gen . Emit ( OpCodes . Ret ) ;
var func = ( Func < IPropertySet , IPropertySet > ) meth . CreateDelegate ( typeof ( Func < IPropertySet , IPropertySet > ) ) ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
// fast enough and works in MediumTrust - but we don't
2014-05-19 13:01:49 +02:00
// read http://boxbinary.com/2011/10/how-to-run-a-unit-test-in-medium-trust-with-nunitpart-three-umbraco-framework-testing/
2017-07-21 17:19:00 +02:00
//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 ;
2014-05-19 13:01:49 +02:00
}
2017-07-21 17:19:00 +02:00
_modelInfos = modelInfos . Count > 0 ? modelInfos : null ;
2014-05-19 13:01:49 +02:00
}
2017-07-21 17:19:00 +02:00
public IPropertySet CreateModel ( IPropertySet set )
2014-05-19 13:01:49 +02:00
{
// fail fast
2017-07-21 17:19:00 +02:00
if ( _modelInfos = = null )
return set ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
if ( _modelInfos . TryGetValue ( set . ContentType . Alias , out ModelInfo modelInfo ) = = false )
return set ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
// 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}." ) ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
return modelInfo . Ctor ( set ) ;
2016-11-23 10:33:12 +01:00
}
2014-05-19 13:01:49 +02:00
}
}