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
}
}