using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using LightInject; namespace Umbraco.Core.Composing.LightInject { /// /// Implements DI with LightInject. /// public class LightInjectContainer : IRegister, IFactory, IDisposable { private int _disposed; /// /// Initializes a new instance of the with a LightInject container. /// public LightInjectContainer(ServiceContainer container) { Container = ConfigureContainer(container); } //TODO: The Create methods can die when net framework is gone /// /// Creates a new instance of the class. /// public static LightInjectContainer Create() => new LightInjectContainer(CreateServiceContainer()); /// /// Creates a new instance of the LightInject service container. /// protected static ServiceContainer CreateServiceContainer() { var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false }); ConfigureContainer(container); return container; } private static ServiceContainer ConfigureContainer(ServiceContainer container) { // note: the block below is disabled, as it is too LightInject-specific // // supports annotated constructor injections // eg to specify the service name on some services //container.EnableAnnotatedConstructorInjection(); // 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 // we explicitly 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(); // 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().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(this); // // instead, we use an explicit GetInstance with arguments implementation return container; } /// /// Gets the LightInject container. /// public ServiceContainer Container { get; } /// /// public object Concrete => Container; /// public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 1) return; Container.Dispose(); } /// public IFactory CreateFactory() => this; private static string GetTargetedServiceName() => "TARGET:" + typeof(TTarget).FullName; #region Factory /// public object GetInstance(Type type) => Container.GetInstance(type); /// public TService GetInstanceFor() => Container.GetInstance(GetTargetedServiceName()); /// public object TryGetInstance(Type type) => Container.TryGetInstance(type); /// public IEnumerable GetAllInstances() where T : class => Container.GetAllInstances(); /// public IEnumerable GetAllInstances(Type type) => Container.GetAllInstances(type); /// public void Release(object instance) { // nothing to release with LightInject } // 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 #endregion #region Registry /// public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient) => Container.Register(serviceType, GetLifetime(lifetime)); /// public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient) { switch (lifetime) { case Lifetime.Transient: Container.Register(serviceType, implementingType, implementingType.Name); break; case Lifetime.Request: case Lifetime.Scope: case Lifetime.Singleton: Container.Register(serviceType, implementingType, GetLifetime(lifetime)); break; default: throw new NotSupportedException($"Lifetime {lifetime} is not supported."); } } /// public void Register(Func factory, Lifetime lifetime = Lifetime.Transient) where TService : class { Container.Register(f => factory(this), GetLifetime(lifetime)); } /// public void Register(Type serviceType, object instance) => Container.RegisterInstance(serviceType, instance); /// public void RegisterFor(Lifetime lifetime = Lifetime.Transient) where TService : class => RegisterFor(typeof(TService), lifetime); /// public void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient) where TService : class { // note that there can only be one implementation or instance registered "for" a service Container.Register(typeof(TService), implementingType, GetTargetedServiceName(), GetLifetime(lifetime)); } /// public void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient) where TService : class { // note that there can only be one implementation or instance registered "for" a service Container.Register(f => factory(this), GetTargetedServiceName(), GetLifetime(lifetime)); } /// public void RegisterFor(TService instance) where TService : class => Container.RegisterInstance(typeof(TService), instance, GetTargetedServiceName()); 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."); } } /// 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); } #endregion #region Control /// public IDisposable BeginScope() => Container.BeginScope(); /// public virtual void ConfigureForWeb() { } /// public virtual void EnablePerWebRequestScope() { if (!(Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp)) throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider."); smp.EnablePerWebRequestScope(new PerLogicalCallContextScopeManagerProvider()); } private class AssemblyScanner : IAssemblyScanner { public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) { // nothing - we don't want LightInject to scan } public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) { // nothing - we don't want LightInject to scan } } #endregion } }