2016-08-16 10:00:14 +02:00
|
|
|
using System;
|
|
|
|
|
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
|
|
|
using LightInject;
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.DependencyInjection
|
|
|
|
|
{
|
|
|
|
|
/// <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 ServiceRegistration[] _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 service container.</param>
|
2016-08-16 10:25:19 +02:00
|
|
|
protected CollectionBuilderBase(IServiceContainer 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 service container.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected IServiceContainer Container { get; }
|
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
|
|
if (constructor == null) throw new InvalidOperationException();
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
// register the collection
|
|
|
|
|
Container.Register(_ => CreateCollection(), CollectionLifetime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the collection lifetime.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Return null for transient collections.</remarks>
|
|
|
|
|
protected virtual ILifetime CollectionLifetime => new PerContainerLifetime();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Registers the collection builder into a service container.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="container">The service container.</param>
|
|
|
|
|
/// <remarks>The collection builder is registered with a "per container" lifetime,
|
|
|
|
|
/// and the collection is registered wiht a lifetime that is "per container" by
|
|
|
|
|
/// default but can be overriden by each builder implementation.</remarks>
|
|
|
|
|
public static TBuilder Register(IServiceContainer container)
|
|
|
|
|
{
|
|
|
|
|
// register the builder - per container
|
|
|
|
|
var builderLifetime = new PerContainerLifetime();
|
|
|
|
|
container.Register<TBuilder>(builderLifetime);
|
|
|
|
|
|
|
|
|
|
// return the builder
|
|
|
|
|
// also initializes the builder
|
|
|
|
|
return container.GetInstance<TBuilder>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the collection builder from a service container.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="container">The service container.</param>
|
|
|
|
|
/// <returns>The collection builder.</returns>
|
|
|
|
|
public static TBuilder Get(IServiceContainer container)
|
|
|
|
|
{
|
|
|
|
|
return container.GetInstance<TBuilder>();
|
2016-08-16 10:00:14 +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>
|
|
|
|
|
protected virtual IEnumerable<Type> GetTypes(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-08-07 16:45:41 +02:00
|
|
|
var types = GetTypes(_types).ToArray();
|
|
|
|
|
foreach (var type in types)
|
|
|
|
|
{
|
|
|
|
|
if (typeof(TItem).IsAssignableFrom(type) == false)
|
|
|
|
|
throw new InvalidOperationException($"Cannot register type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}.");
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2016-07-29 10:58:57 +02:00
|
|
|
_registrations = Container.AvailableServices
|
|
|
|
|
.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))
|
|
|
|
|
.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
|
|
|
|
|
|
|
|
/// <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>
|
|
|
|
|
protected bool HasBase<T>()
|
|
|
|
|
where T : TItem
|
|
|
|
|
{
|
|
|
|
|
return _types.Contains(typeof (T));
|
|
|
|
|
}
|
2016-08-16 10:00:14 +02:00
|
|
|
}
|
|
|
|
|
}
|