2018-06-16 13:13:29 +02:00
|
|
|
|
using System;
|
2018-07-20 15:45:01 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Reflection;
|
2018-07-20 16:36:46 +02:00
|
|
|
|
using System.Linq;
|
2018-07-21 10:47:29 +02:00
|
|
|
|
using System.Threading;
|
2018-06-16 13:13:29 +02:00
|
|
|
|
using LightInject;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Composing.LightInject
|
|
|
|
|
|
{
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// <summary>
|
2018-07-06 18:52:23 +02:00
|
|
|
|
/// Implements <see cref="IContainer"/> with LightInject.
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// </summary>
|
2018-07-06 18:52:23 +02:00
|
|
|
|
public class LightInjectContainer : IContainer
|
2018-06-16 13:13:29 +02:00
|
|
|
|
{
|
2018-07-21 10:47:29 +02:00
|
|
|
|
private int _disposed;
|
|
|
|
|
|
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// <summary>
|
2018-07-06 18:52:23 +02:00
|
|
|
|
/// Initializes a new instance of the <see cref="LightInjectContainer"/> with a LightInject container.
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// </summary>
|
2018-07-21 10:47:29 +02:00
|
|
|
|
protected LightInjectContainer(ServiceContainer container)
|
2018-06-16 13:13:29 +02:00
|
|
|
|
{
|
2018-07-20 15:45:01 +02:00
|
|
|
|
Container = container;
|
2018-06-16 13:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-21 10:47:29 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Creates a new instance of the <see cref="LightInjectContainer"/> class.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static LightInjectContainer Create()
|
|
|
|
|
|
=> new LightInjectContainer(CreateServiceContainer());
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Creates a new instance of the LightInject service container.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected static ServiceContainer CreateServiceContainer()
|
|
|
|
|
|
=> new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false });
|
|
|
|
|
|
|
2018-07-20 15:45:01 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the LightInject container.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected ServiceContainer Container { get; }
|
|
|
|
|
|
|
2018-07-06 18:52:23 +02:00
|
|
|
|
/// <inheritdoc />
|
2018-07-20 15:45:01 +02:00
|
|
|
|
public object ConcreteContainer => Container;
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void Dispose()
|
2018-07-21 10:47:29 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (Interlocked.Exchange(ref _disposed, 1) == 1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
Container.Dispose();
|
|
|
|
|
|
}
|
2018-07-20 15:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
#region Factory
|
2018-06-29 13:17:46 +02:00
|
|
|
|
|
2018-07-20 09:49:05 +02:00
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public object GetInstance(Type type)
|
2018-07-20 15:45:01 +02:00
|
|
|
|
=> Container.GetInstance(type);
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public object GetInstance(Type type, string name)
|
|
|
|
|
|
=> Container.GetInstance(type, name);
|
2018-07-20 09:49:05 +02:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-07-23 08:56:08 +02:00
|
|
|
|
public object GetInstance(Type type, params object[] args)
|
|
|
|
|
|
{
|
|
|
|
|
|
// LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities
|
|
|
|
|
|
//return Container.GetInstance(type, args);
|
|
|
|
|
|
|
|
|
|
|
|
var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).OrderByDescending(x => x.GetParameters().Length).FirstOrDefault();
|
|
|
|
|
|
if (ctor == null) throw new InvalidOperationException($"Could not find a public constructor for type {type.FullName}.");
|
|
|
|
|
|
|
|
|
|
|
|
var ctorParameters = ctor.GetParameters();
|
|
|
|
|
|
var ctorArgs = new object[ctorParameters.Length];
|
|
|
|
|
|
var i = 0;
|
|
|
|
|
|
foreach (var parameter in ctorParameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
// no! IsInstanceOfType is not ok here
|
|
|
|
|
|
// ReSharper disable once UseMethodIsInstanceOfType
|
|
|
|
|
|
var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
|
|
|
|
|
|
ctorArgs[i++] = arg ?? GetInstance(parameter.ParameterType);
|
|
|
|
|
|
}
|
|
|
|
|
|
return ctor.Invoke(ctorArgs);
|
|
|
|
|
|
}
|
2018-07-20 09:49:05 +02:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public object TryGetInstance(Type type)
|
2018-07-20 15:45:01 +02:00
|
|
|
|
=> Container.TryGetInstance(type);
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public IEnumerable<T> GetAllInstances<T>()
|
|
|
|
|
|
=> Container.GetAllInstances<T>();
|
2018-07-20 09:49:05 +02:00
|
|
|
|
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// <inheritdoc />
|
2018-07-20 15:45:01 +02:00
|
|
|
|
public IEnumerable<object> GetAllInstances(Type type)
|
|
|
|
|
|
=> Container.GetAllInstances(type);
|
|
|
|
|
|
|
2018-07-20 16:36:46 +02:00
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public IEnumerable<Registration> GetRegistered(Type type)
|
|
|
|
|
|
=> Container.GetAvailableServices(type).Select(x => new Registration(x.ServiceType, x.ServiceName));
|
|
|
|
|
|
|
2018-07-20 15:45:01 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Registry
|
2018-06-29 13:17:46 +02:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-07-20 15:45:01 +02:00
|
|
|
|
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Lifetime.Transient:
|
|
|
|
|
|
case Lifetime.Request:
|
|
|
|
|
|
case Lifetime.Scope:
|
|
|
|
|
|
Container.Register(serviceType, GetLifetime(lifetime));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Lifetime.Singleton:
|
|
|
|
|
|
Container.RegisterSingleton(serviceType);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Lifetime.Transient:
|
|
|
|
|
|
case Lifetime.Request:
|
|
|
|
|
|
case Lifetime.Scope:
|
|
|
|
|
|
Container.Register(serviceType, implementingType, GetLifetime(lifetime));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Lifetime.Singleton:
|
|
|
|
|
|
Container.RegisterSingleton(serviceType, implementingType);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void Register(Type serviceType, Type implementingType, string name, Lifetime lifetime = Lifetime.Transient)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Lifetime.Transient:
|
|
|
|
|
|
case Lifetime.Request:
|
|
|
|
|
|
case Lifetime.Scope:
|
|
|
|
|
|
Container.Register(serviceType, implementingType, name, GetLifetime(lifetime));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Lifetime.Singleton:
|
|
|
|
|
|
Container.RegisterSingleton(serviceType, implementingType, name);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void Register<TService>(Func<IContainer, TService> factory, Lifetime lifetime = Lifetime.Transient)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Lifetime.Transient:
|
|
|
|
|
|
case Lifetime.Request:
|
|
|
|
|
|
case Lifetime.Scope:
|
|
|
|
|
|
Container.Register(f => factory(this), GetLifetime(lifetime));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Lifetime.Singleton:
|
|
|
|
|
|
Container.RegisterSingleton(f => factory(this));
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-06-16 13:13:29 +02:00
|
|
|
|
|
2018-06-29 13:17:46 +02:00
|
|
|
|
/// <inheritdoc />
|
2018-06-28 22:38:14 +02:00
|
|
|
|
public void Register<T, TService>(Func<IContainer, T, TService> factory)
|
2018-07-20 15:45:01 +02:00
|
|
|
|
=> Container.Register<T, TService>((f, x) => factory(this, x));
|
|
|
|
|
|
|
|
|
|
|
|
private ILifetime GetLifetime(Lifetime lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (lifetime)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Lifetime.Transient:
|
|
|
|
|
|
return null;
|
|
|
|
|
|
case Lifetime.Request:
|
|
|
|
|
|
return new PerRequestLifeTime();
|
|
|
|
|
|
case Lifetime.Scope:
|
|
|
|
|
|
return new PerScopeLifetime();
|
|
|
|
|
|
case Lifetime.Singleton:
|
|
|
|
|
|
return new PerContainerLifetime();
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void RegisterInstance(Type serviceType, object instance)
|
|
|
|
|
|
=> Container.RegisterInstance(serviceType, instance);
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void RegisterAuto(Type serviceBaseType)
|
|
|
|
|
|
{
|
|
|
|
|
|
Container.RegisterFallback((serviceType, serviceName) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// https://github.com/seesharper/LightInject/issues/173
|
|
|
|
|
|
if (serviceBaseType.IsAssignableFromGtd(serviceType))
|
|
|
|
|
|
Container.Register(serviceType);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void RegisterOrdered(Type serviceType, Type[] implementingTypes, Lifetime lifetime = Lifetime.Transient)
|
|
|
|
|
|
=> Container.RegisterOrdered(serviceType, implementingTypes, _ => GetLifetime(lifetime));
|
2018-06-28 22:38:14 +02:00
|
|
|
|
|
2018-07-23 08:56:08 +02:00
|
|
|
|
// was the Light-Inject specific way of dealing with args, but we've replaced it with our own
|
|
|
|
|
|
// beware! does NOT work on singletons, see https://github.com/seesharper/LightInject/issues/294
|
|
|
|
|
|
//
|
|
|
|
|
|
///// <inheritdoc />
|
|
|
|
|
|
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, TDependency> factory)
|
|
|
|
|
|
// => Container.RegisterConstructorDependency((f, x) => factory(this, x));
|
|
|
|
|
|
//
|
|
|
|
|
|
///// <inheritdoc />
|
|
|
|
|
|
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, object[], TDependency> factory)
|
|
|
|
|
|
// => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a));
|
2018-07-20 16:36:46 +02:00
|
|
|
|
|
2018-07-20 15:45:01 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Control
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public IDisposable BeginScope()
|
|
|
|
|
|
=> Container.BeginScope();
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-07-20 16:36:46 +02:00
|
|
|
|
public IContainer ConfigureForUmbraco()
|
2018-07-20 15:45:01 +02:00
|
|
|
|
{
|
|
|
|
|
|
// supports annotated constructor injections
|
|
|
|
|
|
// eg to specify the service name on some services
|
|
|
|
|
|
Container.EnableAnnotatedConstructorInjection();
|
|
|
|
|
|
|
2018-07-21 10:47:29 +02:00
|
|
|
|
// note: the block below is disabled, we do not allow property injection at all anymore
|
|
|
|
|
|
// (see options in CreateServiceContainer)
|
|
|
|
|
|
//
|
2018-07-20 15:45:01 +02:00
|
|
|
|
// from the docs: "LightInject considers all read/write properties a dependency, but implements
|
|
|
|
|
|
// a loose strategy around property dependencies, meaning that it will NOT throw an exception
|
|
|
|
|
|
// in the case of an unresolved property dependency."
|
|
|
|
|
|
//
|
|
|
|
|
|
// in Umbraco we do NOT want to do property injection by default, so we have to disable it.
|
|
|
|
|
|
// from the docs, the following line will cause the container to "now only try to inject
|
|
|
|
|
|
// dependencies for properties that is annotated with the InjectAttribute."
|
|
|
|
|
|
//
|
|
|
|
|
|
// could not find it documented, but tests & code review shows that LightInject considers a
|
|
|
|
|
|
// property to be "injectable" when its setter exists and is not static, nor private, nor
|
|
|
|
|
|
// it is an index property. which means that eg protected or internal setters are OK.
|
2018-07-21 10:47:29 +02:00
|
|
|
|
//Container.EnableAnnotatedPropertyInjection();
|
2018-07-20 15:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
// ensure that we do *not* scan assemblies
|
|
|
|
|
|
// we explicitely RegisterFrom our own composition roots and don't want them scanned
|
|
|
|
|
|
Container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/);
|
|
|
|
|
|
|
|
|
|
|
|
// see notes in MixedLightInjectScopeManagerProvider
|
|
|
|
|
|
Container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider();
|
2018-07-20 16:36:46 +02:00
|
|
|
|
|
2018-07-21 10:47:29 +02:00
|
|
|
|
// note: the block below is disabled, because it does not work, because collection builders
|
|
|
|
|
|
// are singletons, and constructor dependencies don't work on singletons, see
|
|
|
|
|
|
// https://github.com/seesharper/LightInject/issues/294
|
|
|
|
|
|
//
|
|
|
|
|
|
// if looking for a IContainer, and one was passed in args, use it
|
|
|
|
|
|
// this is for collection builders which require the IContainer
|
|
|
|
|
|
//Container.RegisterConstructorDependency((c, i, a) => a.OfType<IContainer>().FirstOrDefault());
|
2018-07-23 08:56:08 +02:00
|
|
|
|
//
|
|
|
|
|
|
// and, the block below is also disabled, because it is ugly
|
|
|
|
|
|
//
|
|
|
|
|
|
//// which means that the only way to inject the container into builders is to register it
|
|
|
|
|
|
//Container.RegisterInstance<IContainer>(this);
|
|
|
|
|
|
//
|
|
|
|
|
|
// instead, we use an explicit GetInstance with arguments implementation
|
2018-07-21 10:47:29 +02:00
|
|
|
|
|
2018-07-20 16:36:46 +02:00
|
|
|
|
return this;
|
2018-07-20 15:45:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class AssemblyScanner : IAssemblyScanner
|
|
|
|
|
|
{
|
|
|
|
|
|
//private readonly IAssemblyScanner _scanner;
|
|
|
|
|
|
|
|
|
|
|
|
//public AssemblyScanner(IAssemblyScanner scanner)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// _scanner = scanner;
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func<ILifetime> lifetime, Func<Type, Type, bool> shouldRegister, Func<Type, Type, string> serviceNameProvider)
|
|
|
|
|
|
{
|
|
|
|
|
|
// nothing - we *could* scan non-Umbraco assemblies, though
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry)
|
|
|
|
|
|
{
|
|
|
|
|
|
// nothing - we *could* scan non-Umbraco assemblies, though
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-07-20 16:36:46 +02:00
|
|
|
|
public virtual IContainer ConfigureForWeb()
|
|
|
|
|
|
{
|
|
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2018-07-20 15:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-07-20 16:36:46 +02:00
|
|
|
|
public IContainer EnablePerWebRequestScope()
|
2018-07-20 15:45:01 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (!(Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp))
|
|
|
|
|
|
throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider.");
|
|
|
|
|
|
smp.EnablePerWebRequestScope();
|
2018-07-20 16:36:46 +02:00
|
|
|
|
return this;
|
2018-07-20 15:45:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2018-06-16 13:13:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|