Files
Umbraco-CMS/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs

342 lines
13 KiB
C#
Raw Normal View History

using System;
2018-07-20 15:45:01 +02:00
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Threading;
using LightInject;
namespace Umbraco.Core.Composing.LightInject
{
2018-06-29 13:17:46 +02:00
/// <summary>
/// Implements <see cref="IContainer"/> with LightInject.
2018-06-29 13:17:46 +02:00
/// </summary>
public class LightInjectContainer : IContainer
{
private int _disposed;
2018-06-29 13:17:46 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="LightInjectContainer"/> with a LightInject container.
2018-06-29 13:17:46 +02:00
/// </summary>
protected LightInjectContainer(ServiceContainer container)
{
2018-07-20 15:45:01 +02:00
Container = container;
}
/// <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-20 15:45:01 +02:00
/// <summary>
/// Gets the LightInject container.
/// </summary>
protected ServiceContainer Container { get; }
/// <inheritdoc />
2018-07-20 15:45:01 +02:00
public object ConcreteContainer => Container;
/// <inheritdoc />
public void Dispose()
{
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 />
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));
2018-10-25 15:29:29 +02:00
/// <inheritdoc />
public void Release(object instance)
{
// nothing to release with LightInject
}
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:
Container.Register(serviceType);
break;
2018-07-20 15:45:01 +02:00
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:
Container.Register(serviceType, implementingType, implementingType.Name);
break;
2018-07-20 15:45:01 +02:00
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:
Container.Register(serviceType, implementingType, name);
break;
2018-07-20 15:45:01 +02:00
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:
Container.Register(f => factory(this));
break;
2018-07-20 15:45:01 +02:00
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-09-02 22:09:37 +02:00
/// <inheritdoc />
public void Register<TService>(Func<IContainer, TService> factory, string name, Lifetime lifetime = Lifetime.Transient)
{
switch (lifetime)
{
case Lifetime.Transient:
Container.Register(f => factory(this), name);
break;
2018-09-02 22:09:37 +02:00
case Lifetime.Request:
case Lifetime.Scope:
Container.Register(f => factory(this), name, GetLifetime(lifetime));
break;
case Lifetime.Singleton:
Container.RegisterSingleton(f => factory(this), name);
break;
default:
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
}
}
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);
2018-10-23 21:31:20 +02:00
/// <inheritdoc />
public void RegisterInstance(Type serviceType, object instance, string name)
=> Container.RegisterInstance(serviceType, instance, name);
2018-07-20 15:45:01 +02:00
/// <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-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 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-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();
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
}
}