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 ( )
2018-07-23 09:21:55 +02:00
{
var container = new ServiceContainer ( new ContainerOptions { EnablePropertyInjection = false } ) ;
2018-08-27 18:09:10 +02:00
// note: the block below is disabled, as it is too LightInject-specific
//
// supports annotated constructor injections
2018-07-23 09:21:55 +02:00
// eg to specify the service name on some services
2018-08-27 18:09:10 +02:00
//container.EnableAnnotatedConstructorInjection();
2018-07-23 09:21:55 +02:00
// note: the block below is disabled, we do not allow property injection at all anymore
// (see options in CreateServiceContainer)
//
// 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.
//Container.EnableAnnotatedPropertyInjection();
// ensure that we do *not* scan assemblies
2018-08-27 18:09:10 +02:00
// we explicitly RegisterFrom our own composition roots and don't want them scanned
2018-07-23 09:21:55 +02:00
container . AssemblyScanner = new AssemblyScanner ( /*container.AssemblyScanner*/ ) ;
// see notes in MixedLightInjectScopeManagerProvider
container . ScopeManagerProvider = new MixedLightInjectScopeManagerProvider ( ) ;
// 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());
//
// 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
return container ;
}
2018-07-21 10:47:29 +02:00
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
2018-08-29 18:50:08 +02:00
/// <inheritdoc />
public object TryGetInstance ( Type type )
= > Container . TryGetInstance ( type ) ;
2018-07-23 09:21:55 +02:00
/// <inheritdoc />
public IEnumerable < T > GetAllInstances < T > ( )
= > Container . GetAllInstances < T > ( ) ;
/// <inheritdoc />
public IEnumerable < object > GetAllInstances ( Type type )
= > Container . GetAllInstances ( type ) ;
/// <inheritdoc />
public IEnumerable < Registration > GetRegistered ( Type type )
= > Container . AvailableServices . Where ( x = > x . ServiceType = = type ) . Select ( x = > new Registration ( x . ServiceType , x . ServiceName ) ) ;
/// <inheritdoc />
public object CreateInstance ( Type type , params object [ ] args )
2018-07-23 08:56:08 +02:00
{
// LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities
2018-07-23 09:21:55 +02:00
// including the most annoying one, which is that it does not work on singletons (hard to fix)
2018-07-23 08:56:08 +02:00
//return Container.GetInstance(type, args);
2018-07-23 09:21:55 +02:00
// this method is essentially used to build singleton instances, so it is assumed that it would be
// more expensive to build and cache a dynamic method ctor than to simply invoke the ctor, as we do
// here - this can be discussed
2018-07-23 08:56:08 +02:00
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
2018-07-23 12:19:26 +02:00
// notes:
// we may want to look into MS code, eg:
// TypeActivatorCache in MVC at https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs
// which relies onto
// ActivatorUtilities at https://github.com/aspnet/DependencyInjection/blob/master/shared/Microsoft.Extensions.ActivatorUtilities.Sources/ActivatorUtilities.cs
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-07-20 15:45:01 +02:00
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-23 09:21:55 +02:00
public virtual IContainer ConfigureForWeb ( )
2018-07-20 15:45:01 +02:00
{
2018-07-23 09:21:55 +02:00
return this ;
}
2018-07-21 10:47:29 +02:00
2018-07-23 09:21:55 +02:00
/// <inheritdoc />
public IContainer EnablePerWebRequestScope ( )
{
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
}
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
}
}
#endregion
2018-06-16 13:13:29 +02:00
}
}