Files
Umbraco-CMS/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs

129 lines
5.9 KiB
C#
Raw Normal View History

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace Umbraco.Cms.Core.Models.PublishedContent
{
/// <summary>
/// Implements a strongly typed content model factory
/// </summary>
2017-09-26 14:57:50 +02:00
public class PublishedModelFactory : IPublishedModelFactory
{
2022-01-21 11:43:58 +01:00
private readonly Dictionary<string, ModelInfo>? _modelInfos;
private readonly Dictionary<string, Type> _modelTypeMap;
private readonly IPublishedValueFallback _publishedValueFallback;
private class ModelInfo
{
2022-01-21 11:43:58 +01:00
public Type? ParameterType { get; set; }
2022-01-21 11:43:58 +01:00
public Func<object, IPublishedValueFallback, object>? Ctor { get; set; }
2022-01-21 11:43:58 +01:00
public Type? ModelType { get; set; }
2022-01-21 11:43:58 +01:00
public Func<IList>? ListCtor { get; set; }
}
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>
public PublishedModelFactory(IEnumerable<Type> types, IPublishedValueFallback publishedValueFallback)
{
var modelInfos = new Dictionary<string, ModelInfo>(StringComparer.InvariantCultureIgnoreCase);
var modelTypeMap = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
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
2022-01-21 11:43:58 +01:00
ConstructorInfo? constructor = null;
Type? parameterType = null;
foreach (var ctor in type.GetConstructors())
{
var parms = ctor.GetParameters();
if (parms.Length == 2 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType) && typeof(IPublishedValueFallback).IsAssignableFrom(parms[1].ParameterType))
{
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.");
constructor = ctor;
parameterType = parms[0].ParameterType;
}
}
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.");
var attribute = type.GetCustomAttribute<PublishedModelAttribute>(false);
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
2017-09-27 21:16:09 +02:00
if (modelInfos.TryGetValue(typeName, out var modelInfo))
2022-01-21 11:43:58 +01:00
throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType?.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\".");
2017-09-29 15:51:33 +02:00
// have to use an unsafe ctor because we don't know the types, really
var modelCtor = ReflectionUtilities.EmitConstructorUnsafe<Func<object, IPublishedValueFallback, object>>(constructor);
2017-09-29 15:51:33 +02:00
modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor };
modelTypeMap[typeName] = type;
}
_modelInfos = modelInfos.Count > 0 ? modelInfos : null;
_modelTypeMap = modelTypeMap;
_publishedValueFallback = publishedValueFallback;
}
/// <inheritdoc />
2017-09-26 14:57:50 +02:00
public IPublishedElement CreateModel(IPublishedElement element)
{
// fail fast
if (_modelInfos == null)
2017-09-26 14:57:50 +02:00
return element;
2022-01-21 11:43:58 +01:00
if (element.ContentType.Alias is null || !_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo))
2017-09-26 14:57:50 +02:00
return element;
// ReSharper disable once UseMethodIsInstanceOfType
2022-01-21 11:43:58 +01:00
if (modelInfo.ParameterType?.IsAssignableFrom(element.GetType()) == false)
2017-09-26 14:57:50 +02:00
throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}.");
2017-09-29 15:51:33 +02:00
// can cast, because we checked when creating the ctor
2022-01-21 11:43:58 +01:00
return (IPublishedElement)modelInfo.Ctor!(element, _publishedValueFallback);
}
/// <inheritdoc />
public IList CreateModelList(string alias)
{
// fail fast
if (_modelInfos == null)
return new List<IPublishedElement>();
2022-01-21 11:43:58 +01:00
if (!_modelInfos.TryGetValue(alias, out var modelInfo) || modelInfo.ModelType is null)
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);
return ctor();
}
/// <inheritdoc />
public Type MapModelType(Type type)
=> ModelType.Map(type, _modelTypeMap);
}
}