2014-05-19 13:01:49 +02:00
using System ;
using System.Collections.Generic ;
2017-09-25 12:58:54 +02:00
using System.Linq.Expressions ;
2017-07-21 17:19:00 +02:00
using System.Reflection ;
2014-05-19 13:01:49 +02:00
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Implements a strongly typed content model factory
/// </summary>
2017-09-26 14:57:50 +02:00
public class PublishedModelFactory : IPublishedModelFactory
2014-05-19 13:01:49 +02:00
{
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 ; }
2017-09-25 08:59:32 +02:00
public Func < IPublishedElement , IPublishedElement > Ctor { get ; set ; }
2017-07-21 17:19:00 +02:00
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>
2017-09-26 14:57:50 +02:00
/// Initializes a new instance of the <see cref="PublishedModelFactory"/> class with types.
2014-05-19 13:15:47 +02:00
/// </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>
2017-09-26 14:57:50 +02:00
public PublishedModelFactory ( IEnumerable < Type > types )
2014-05-19 13:01:49 +02:00
{
2017-07-21 17:19:00 +02:00
var modelInfos = new Dictionary < string , ModelInfo > ( StringComparer . InvariantCultureIgnoreCase ) ;
2017-09-25 12:58:54 +02:00
var exprs = new List < Expression < Func < IPublishedElement , IPublishedElement > > > ( ) ;
2017-07-21 17:19:00 +02:00
ModelTypeMap = new Dictionary < string , Type > ( StringComparer . InvariantCultureIgnoreCase ) ;
2014-05-19 13:01:49 +02:00
foreach ( var type in types )
{
2017-09-27 21:16:09 +02:00
// fixme - annoying - we want the xpression to be of a give type
// fixme - but then do we need ctor(IPublishedElement x) and what about ctor(IPublishedContent x)?
//var expr = ReflectionUtilities.GetCtorExpr<Func<IPublishedElement, IPublishedElement>>(type, false);
//if (expr == null)
// throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type IPublishedElement.");
//var ccc = ReflectionUtilities.EmitCtor<IPublishedElement, IPublishedElement>(type, false);
//if (ccc == null)
// throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type IPublishedElement.");
// so... the model type has to implement a ctor with one parameter being, or inheriting from,
// IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor,
// we have to iterate over all ctors and try to find the right one
2017-07-21 17:19:00 +02:00
ConstructorInfo constructor = null ;
Type parameterType = null ;
foreach ( var ctor in type . GetConstructors ( ) )
{
var parms = ctor . GetParameters ( ) ;
2017-09-27 21:16:09 +02:00
if ( parms . Length = = 1 & & typeof ( IPublishedElement ) . IsAssignableFrom ( parms [ 0 ] . ParameterType ) )
2017-07-21 17:19:00 +02:00
{
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." ) ;
2017-07-21 17:19:00 +02:00
constructor = ctor ;
parameterType = parms [ 0 ] . ParameterType ;
}
}
2014-05-19 13:01:49 +02:00
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." ) ;
2017-07-21 17:19:00 +02:00
2017-09-27 21:16:09 +02:00
var attribute = type . GetCustomAttribute < PublishedContentModelAttribute > ( false ) ;
2014-05-19 13:01:49 +02:00
var typeName = attribute = = null ? type . Name : attribute . ContentTypeAlias ;
2017-09-27 21:16:09 +02:00
if ( modelInfos . TryGetValue ( typeName , out var modelInfo ) )
2017-07-21 17:19:00 +02:00
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-09-27 21:16:09 +02:00
//exprs.Add(Expression.Lambda<Func<IPublishedElement, IPublishedElement>>(Expression.New(constructor)));
modelInfos [ typeName ] = new ModelInfo { ParameterType = parameterType , ModelType = type , Ctor = ReflectionUtilities . EmitCtor < Func < IPublishedElement , IPublishedElement > > ( constructor ) } ;
2017-07-21 17:19:00 +02:00
ModelTypeMap [ typeName ] = type ;
2014-05-19 13:01:49 +02:00
}
2017-09-27 21:16:09 +02:00
//var compiled = ReflectionUtilities.CompileToDelegates(exprs.ToArray());
//var i = 0;
//foreach (var modelInfo in modelInfos.Values)
// modelInfo.Ctor = compiled[i++];
2017-09-25 12:58:54 +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-09-26 14:57:50 +02:00
public IPublishedElement CreateModel ( IPublishedElement element )
2014-05-19 13:01:49 +02:00
{
// fail fast
2017-07-21 17:19:00 +02:00
if ( _modelInfos = = null )
2017-09-26 14:57:50 +02:00
return element ;
2014-05-19 13:01:49 +02:00
2017-09-26 14:57:50 +02:00
if ( _modelInfos . TryGetValue ( element . ContentType . Alias , out var modelInfo ) = = false )
return element ;
2014-05-19 13:01:49 +02:00
2017-07-21 17:19:00 +02:00
// ReSharper disable once UseMethodIsInstanceOfType
2017-09-26 14:57:50 +02:00
if ( modelInfo . ParameterType . IsAssignableFrom ( element . GetType ( ) ) = = false )
throw new InvalidOperationException ( $"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}." ) ;
2014-05-19 13:01:49 +02:00
2017-09-26 14:57:50 +02:00
return modelInfo . Ctor ( element ) ;
2016-11-23 10:33:12 +01:00
}
2014-05-19 13:01:49 +02:00
}
}