Files
Umbraco-CMS/src/Umbraco.Core/Composing/CollectionBuilderBase.cs

193 lines
7.7 KiB
C#
Raw Normal View History

2017-07-20 11:21:28 +02:00
using System;
2016-08-16 10:00:14 +02:00
using System.Collections.Generic;
using System.Linq;
2016-07-29 10:58:57 +02:00
using System.Linq.Expressions;
2016-08-16 10:00:14 +02:00
2017-05-30 15:46:25 +02:00
namespace Umbraco.Core.Composing
2016-08-16 10:00:14 +02:00
{
/// <summary>
2016-08-16 10:25:19 +02:00
/// Provides a base class for collection builders.
2016-08-16 10:00:14 +02:00
/// </summary>
2016-07-29 10:58:57 +02:00
/// <typeparam name="TBuilder">The type of the builder.</typeparam>
2016-08-16 10:00:14 +02:00
/// <typeparam name="TCollection">The type of the collection.</typeparam>
/// <typeparam name="TItem">The type of the items.</typeparam>
2016-07-29 10:58:57 +02:00
public abstract class CollectionBuilderBase<TBuilder, TCollection, TItem> : ICollectionBuilder<TCollection, TItem>
where TBuilder: CollectionBuilderBase<TBuilder, TCollection, TItem>
2016-08-16 10:25:19 +02:00
where TCollection : IBuilderCollection<TItem>
2016-08-16 10:00:14 +02:00
{
private readonly List<Type> _types = new List<Type>();
private readonly object _locker = new object();
2016-07-29 10:58:57 +02:00
private Func<IEnumerable<TItem>, TCollection> _collectionCtor;
private Registration[] _registrations;
2016-08-16 10:00:14 +02:00
/// <summary>
2016-07-29 10:58:57 +02:00
/// Initializes a new instance of the <see cref="CollectionBuilderBase{TBuilder, TCollection,TItem}"/>
2016-08-16 10:00:14 +02:00
/// class with a service container.
/// </summary>
/// <param name="container">A container.</param>
protected CollectionBuilderBase(IContainer container)
2016-08-16 10:00:14 +02:00
{
2016-07-29 10:58:57 +02:00
Container = container;
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
Initialize();
}
/// <summary>
/// Gets the container.
2016-07-29 10:58:57 +02:00
/// </summary>
protected IContainer Container { get; }
2016-07-29 10:58:57 +02:00
2016-09-23 20:18:25 +02:00
/// <summary>
/// Gets the internal list of types as an IEnumerable (immutable).
/// </summary>
public IEnumerable<Type> GetTypes() => _types;
2016-07-29 10:58:57 +02:00
/// <summary>
/// Initializes a new instance of the builder.
/// </summary>
/// <remarks>This is called by the constructor and, by default, registers the
/// collection automatically.</remarks>
protected virtual void Initialize()
{
// compile the auto-collection constructor
var argType = typeof(IEnumerable<TItem>);
var ctorArgTypes = new[] { argType };
var constructor = typeof(TCollection).GetConstructor(ctorArgTypes);
2016-12-19 17:37:18 +01:00
if (constructor != null)
{
var exprArg = Expression.Parameter(argType, "items");
var exprNew = Expression.New(constructor, exprArg);
var expr = Expression.Lambda<Func<IEnumerable<TItem>, TCollection>>(exprNew, exprArg);
_collectionCtor = expr.Compile();
}
// else _collectionCtor remains null, assuming CreateCollection has been overriden
2016-07-29 10:58:57 +02:00
2016-11-08 17:38:05 +01:00
// we just don't want to support re-registering collections here
if (Container.GetRegistered<TCollection>().Any())
2016-11-08 17:38:05 +01:00
throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered.");
2016-07-29 10:58:57 +02:00
// register the collection
Container.Register(_ => CreateCollection(), CollectionLifetime);
}
/// <summary>
/// Gets the collection lifetime.
/// </summary>
protected virtual Lifetime CollectionLifetime => Lifetime.Singleton;
2016-07-29 10:58:57 +02:00
2016-08-16 10:25:19 +02:00
/// <summary>
/// Configures the internal list of types.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <remarks>Throws if the types have already been registered.</remarks>
protected void Configure(Action<List<Type>> action)
2016-08-16 10:00:14 +02:00
{
lock (_locker)
{
2016-07-29 10:58:57 +02:00
if (_registrations != null)
throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved.");
2016-08-16 10:25:19 +02:00
action(_types);
2016-08-16 10:00:14 +02:00
}
}
2016-08-16 10:25:19 +02:00
/// <summary>
/// Gets the types.
/// </summary>
/// <param name="types">The internal list of types.</param>
/// <returns>The list of types to register.</returns>
2016-09-23 20:18:25 +02:00
/// <remarks>Used by implementations to add types to the internal list, sort the list, etc.</remarks>
protected virtual IEnumerable<Type> GetRegisteringTypes(IEnumerable<Type> types)
2016-08-16 10:00:14 +02:00
{
return types;
}
private void RegisterTypes()
{
lock (_locker)
{
2016-07-29 10:58:57 +02:00
if (_registrations != null) return;
2016-08-16 10:00:14 +02:00
2016-09-23 20:18:25 +02:00
var types = GetRegisteringTypes(_types).ToArray();
2016-08-07 16:45:41 +02:00
foreach (var type in types)
2016-09-23 20:18:25 +02:00
EnsureType(type, "register");
2016-08-07 16:45:41 +02:00
2016-08-16 10:00:14 +02:00
var prefix = GetType().FullName + "_";
var i = 0;
2016-08-07 16:45:41 +02:00
foreach (var type in types)
2016-08-16 10:00:14 +02:00
{
var name = $"{prefix}{i++:00000}";
2016-07-29 10:58:57 +02:00
Container.Register(typeof(TItem), type, name);
2016-08-16 10:00:14 +02:00
}
// note: we do this, because we don't want to get "all",
// because other types implementing TItem may be registered,
// and we only want those for *this* builder
_registrations = Container.GetRegistered(typeof(TItem))
2016-07-29 10:58:57 +02:00
.Where(x => x.ServiceName.StartsWith(prefix))
.OrderBy(x => x.ServiceName)
.ToArray();
2016-08-16 10:00:14 +02:00
}
}
/// <summary>
2016-07-29 10:58:57 +02:00
/// Creates the collection items.
2016-08-16 10:00:14 +02:00
/// </summary>
2016-07-29 10:58:57 +02:00
/// <param name="args">The arguments.</param>
/// <returns>The collection items.</returns>
protected virtual IEnumerable<TItem> CreateItems(/*params object[] args*/)
2016-08-16 10:00:14 +02:00
{
RegisterTypes(); // will do it only once
2016-07-29 10:58:57 +02:00
var type = typeof (TItem);
return _registrations
.Select(x => (TItem) Container.GetInstance(type, x.ServiceName/*, args*/))
2016-07-29 10:58:57 +02:00
.ToArray(); // safe
2016-08-16 10:00:14 +02:00
}
/// <summary>
2016-07-29 10:58:57 +02:00
/// Creates a collection.
2016-08-16 10:00:14 +02:00
/// </summary>
2016-07-29 10:58:57 +02:00
/// <returns>A collection.</returns>
/// <remarks>Creates a new collection each time it is invoked.</remarks>
public virtual TCollection CreateCollection()
{
if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible.");
return _collectionCtor(CreateItems());
}
2016-08-22 11:40:45 +02:00
2016-09-23 20:18:25 +02:00
protected Type EnsureType(Type type, string action)
{
if (typeof(TItem).IsAssignableFrom(type) == false)
throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}.");
return type;
}
2016-08-22 11:40:45 +02:00
/// <summary>
/// Gets a value indicating whether the collection contains a type.
/// </summary>
/// <typeparam name="T">The type to look for.</typeparam>
/// <returns>A value indicating whether the collection contains the type.</returns>
/// <remarks>Some builder implementations may use this to expose a public Has{T}() method,
/// when it makes sense. Probably does not make sense for lazy builders, for example.</remarks>
2016-09-23 20:18:25 +02:00
public virtual bool Has<T>()
2016-08-22 11:40:45 +02:00
where T : TItem
{
return _types.Contains(typeof (T));
}
2016-09-23 20:18:25 +02:00
/// <summary>
/// Gets a value indicating whether the collection contains a type.
/// </summary>
/// <param name="type">The type to look for.</param>
/// <returns>A value indicating whether the collection contains the type.</returns>
/// <remarks>Some builder implementations may use this to expose a public Has{T}() method,
/// when it makes sense. Probably does not make sense for lazy builders, for example.</remarks>
public virtual bool Has(Type type)
{
EnsureType(type, "find");
return _types.Contains(type);
}
2016-08-16 10:00:14 +02:00
}
2017-07-20 11:21:28 +02:00
}