using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Threading; using LightInject; namespace Umbraco.Core.Composing.LightInject { /// /// Implements with LightInject. /// public class LightInjectContainer : IContainer { private int _disposed; /// /// Initializes a new instance of the with a LightInject container. /// protected LightInjectContainer(ServiceContainer container) { Container = container; } /// /// 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() => new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false }); /// /// Gets the LightInject container. /// protected ServiceContainer Container { get; } /// public object ConcreteContainer => Container; /// public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 1) return; Container.Dispose(); } #region Factory /// public object GetInstance(Type type) => Container.GetInstance(type); /// public object GetInstance(Type type, string name) => Container.GetInstance(type, name); /// 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); } /// public object TryGetInstance(Type type) => Container.TryGetInstance(type); /// public IEnumerable GetAllInstances() => Container.GetAllInstances(); /// public IEnumerable GetAllInstances(Type type) => Container.GetAllInstances(type); /// public IEnumerable GetRegistered(Type type) => Container.GetAvailableServices(type).Select(x => new Registration(x.ServiceType, x.ServiceName)); #endregion #region Registry /// 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."); } } /// 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."); } } /// 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."); } } /// public void Register(Func 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."); } } /// public void Register(Func factory) => Container.Register((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."); } } /// public void RegisterInstance(Type serviceType, object instance) => Container.RegisterInstance(serviceType, instance); /// 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); } /// public void RegisterOrdered(Type serviceType, Type[] implementingTypes, Lifetime lifetime = Lifetime.Transient) => Container.RegisterOrdered(serviceType, implementingTypes, _ => GetLifetime(lifetime)); // 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 // ///// //public void RegisterConstructorDependency(Func factory) // => Container.RegisterConstructorDependency((f, x) => factory(this, x)); // ///// //public void RegisterConstructorDependency(Func factory) // => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a)); #endregion #region Control /// public IDisposable BeginScope() => Container.BeginScope(); /// public IContainer ConfigureForUmbraco() { // 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 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(); // 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 this; } private class AssemblyScanner : IAssemblyScanner { //private readonly IAssemblyScanner _scanner; //public AssemblyScanner(IAssemblyScanner scanner) //{ // _scanner = scanner; //} public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider) { // nothing - we *could* scan non-Umbraco assemblies, though } public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) { // nothing - we *could* scan non-Umbraco assemblies, though } } /// public virtual IContainer ConfigureForWeb() { return this; } /// public IContainer EnablePerWebRequestScope() { if (!(Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp)) throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider."); smp.EnablePerWebRequestScope(); return this; } #endregion } }