2014-05-19 13:01:49 +02:00
using System ;
2017-10-17 09:07:01 +02:00
using System.Collections ;
2014-05-19 13:01:49 +02:00
using System.Collections.Generic ;
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 ;
2017-10-17 09:07:01 +02:00
private readonly Dictionary < string , Type > _modelTypeMap ;
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-29 15:51:33 +02:00
public Func < object , object > Ctor { get ; set ; }
2017-07-21 17:19:00 +02:00
public Type ModelType { get ; set ; }
2017-10-17 09:07:01 +02:00
public Func < IList > ListCtor { get ; set ; }
2017-07-21 17:19:00 +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-10-17 09:07:01 +02:00
var 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
// 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-10-26 11:25:01 +02:00
var attribute = type . GetCustomAttribute < PublishedModelAttribute > ( 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-29 15:51:33 +02:00
// have to use an unsafe ctor because we don't know the types, really
2018-10-16 10:58:17 +02:00
var modelCtor = ReflectionUtilities . EmitConstructorUnsafe < Func < object , object > > ( constructor ) ;
2017-09-29 15:51:33 +02:00
modelInfos [ typeName ] = new ModelInfo { ParameterType = parameterType , ModelType = type , Ctor = modelCtor } ;
2017-10-17 09:07:01 +02:00
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 ;
2017-10-17 09:07:01 +02:00
_modelTypeMap = modelTypeMap ;
2014-05-19 13:01:49 +02:00
}
2017-10-17 09:07:01 +02:00
/// <inheritdoc />
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-10-17 09:07:01 +02:00
if ( ! _modelInfos . TryGetValue ( element . ContentType . Alias , out var modelInfo ) )
2017-09-26 14:57:50 +02:00
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-29 15:51:33 +02:00
// can cast, because we checked when creating the ctor
return ( IPublishedElement ) modelInfo . Ctor ( element ) ;
2016-11-23 10:33:12 +01:00
}
2017-10-17 09:07:01 +02:00
/// <inheritdoc />
public IList CreateModelList ( string alias )
{
// fail fast
if ( _modelInfos = = null )
return new List < IPublishedElement > ( ) ;
if ( ! _modelInfos . TryGetValue ( alias , out var modelInfo ) )
return new List < IPublishedElement > ( ) ;
var ctor = modelInfo . ListCtor ;
if ( ctor ! = null ) return ctor ( ) ;
var listType = typeof ( List < > ) . MakeGenericType ( modelInfo . ModelType ) ;
2018-11-18 11:05:14 +01:00
ctor = modelInfo . ListCtor = ReflectionUtilities . EmitConstructor < Func < IList > > ( declaring : listType ) ;
2017-10-17 09:07:01 +02:00
return ctor ( ) ;
}
/// <inheritdoc />
public Type MapModelType ( Type type )
= > ModelType . Map ( type , _modelTypeMap ) ;
2014-05-19 13:01:49 +02:00
}
}