using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace Umbraco.Core.Models.PublishedContent { /// /// Implements a strongly typed content model factory /// public class PublishedModelFactory : IPublishedModelFactory { private readonly Dictionary _modelInfos; private class ModelInfo { public Type ParameterType { get; set; } public Func Ctor { get; set; } public Type ModelType { get; set; } } public Dictionary ModelTypeMap { get; } /// /// Initializes a new instance of the class with types. /// /// The model types. /// /// Types must implement IPublishedContent and have a unique constructor that /// accepts one IPublishedContent as a parameter. /// To activate, /// /// var types = TypeLoader.Current.GetTypes{PublishedContentModel}(); /// var factory = new PublishedContentModelFactoryImpl(types); /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); /// /// public PublishedModelFactory(IEnumerable types) { var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); var exprs = new List>>(); ModelTypeMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (var type in types) { // 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>(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(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 ConstructorInfo constructor = null; Type parameterType = null; foreach (var ctor in type.GetConstructors()) { var parms = ctor.GetParameters(); if (parms.Length == 1 && typeof(IPublishedElement).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, IPublishedElement."); constructor = ctor; parameterType = parms[0].ParameterType; } } if (constructor == null) throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); var attribute = type.GetCustomAttribute(false); var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; if (modelInfos.TryGetValue(typeName, out var modelInfo)) throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); //exprs.Add(Expression.Lambda>(Expression.New(constructor))); modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = ReflectionUtilities.EmitCtor>(constructor) }; ModelTypeMap[typeName] = type; } //var compiled = ReflectionUtilities.CompileToDelegates(exprs.ToArray()); //var i = 0; //foreach (var modelInfo in modelInfos.Values) // modelInfo.Ctor = compiled[i++]; _modelInfos = modelInfos.Count > 0 ? modelInfos : null; } public IPublishedElement CreateModel(IPublishedElement element) { // fail fast if (_modelInfos == null) return element; if (_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo) == false) return element; // ReSharper disable once UseMethodIsInstanceOfType 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}."); return modelInfo.Ctor(element); } } }