using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using LightInject; using Umbraco.Core.Exceptions; namespace Umbraco.Core.Composing { /// /// Provides extensions to LightInject. /// internal static class LightInjectExtensions { /// /// Configure the container for Umbraco Core usage and assign to Current. /// /// The container. /// The container is now the unique application container and is now accessible via Current.Container. public static void ConfigureUmbracoCore(this ServiceContainer container) { // supports annotated constructor injections // eg to specify the service name on some services container.EnableAnnotatedConstructorInjection(); // 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(); // self-register container.Register(_ => container); // configure the current container Current.Container = container; } 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) { // nothing - we *could* scan non-Umbraco assemblies, though } public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) { // nothing - we *could* scan non-Umbraco assemblies, though } } /// /// Registers a service implementation with a specified lifetime. /// /// The type of the service. /// The type of the implementation. /// The type of the lifetime. /// The container. public static void Register(this IServiceContainer container) where TImplementation : TService where TLifetime : ILifetime, new() { container.Register(new TLifetime()); } /// /// Registers a service implementation with a specified lifetime. /// /// The type of the service. /// The type of the lifetime. /// The container. /// A factory. public static void Register(this IServiceContainer container, Func factory) where TLifetime : ILifetime, new() { container.Register(factory, new TLifetime()); } /// /// Registers several service implementations with a specified lifetime. /// /// The type of the service. /// The type of the lifetime. /// The container. /// The types of the implementations. public static void RegisterMany(this IServiceContainer container, IEnumerable implementations) where TLifeTime : ILifetime, new() { foreach (var implementation in implementations) { // if "typeof (TService)" is there then "implementation.FullName" MUST be there too container.Register(typeof(TService), implementation, implementation.FullName, new TLifeTime()); } } /// /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. /// public static void RegisterSingleton(this IServiceRegistry container, Func factory, string serviceName) { var registration = container.GetAvailableService(serviceName); if (registration == null) { container.Register(factory, serviceName, new PerContainerLifetime()); } else { if (registration.Lifetime is PerContainerLifetime == false) throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); UpdateRegistration(registration, null, factory); } } /// /// Registers the TService with the TImplementation as a singleton. /// public static void RegisterSingleton(this IServiceRegistry container) where TImplementation : TService { var registration = container.GetAvailableService(); if (registration == null) { container.Register(new PerContainerLifetime()); } else { if (registration.Lifetime is PerContainerLifetime == false) throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); UpdateRegistration(registration, typeof(TImplementation), null); } } /// /// Registers a concrete type as a singleton service. /// public static void RegisterSingleton(this IServiceRegistry container) { var registration = container.GetAvailableService(); if (registration == null) { container.Register(new PerContainerLifetime()); } else { if (registration.Lifetime is PerContainerLifetime == false) throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); UpdateRegistration(registration, typeof(TImplementation), null); } } /// /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. /// /// /// /// public static void RegisterSingleton(this IServiceRegistry container, Func factory) { var registration = container.GetAvailableService(); if (registration == null) container.Register(factory, new PerContainerLifetime()); else UpdateRegistration(registration, null, factory); } // note - what's below ALSO applies to non-singleton ie transient services // // see https://github.com/seesharper/LightInject/issues/133 // // note: we *could* use tracking lifetimes for singletons to ensure they have not been resolved // already but that would not work for transient as the transient lifetime is null (and that is // optimized in LightInject) // // also, RegisterSingleton above is dangerous because ppl could still use Register with a // PerContainerLifetime and it will not work + the default Register would not work either for other // lifetimes // // all in all, not sure we want to let ppl have direct access to the container // we might instead want to expose some methods in UmbracoComponentBase or whatever? /// /// Updates a registration. /// private static void UpdateRegistration(Registration registration, Type implementingType, Delegate factoryExpression) { // if the container has compiled already then the registrations have been captured, // and re-registering - although updating available services - does not modify the // output of GetInstance // // so we have to rely on different methods // // assuming the service has NOT been resolved, both methods below work, but the first // one looks simpler. it would be good to check whether the service HAS been resolved // but I am not sure how to do it right now, depending on the lifetime // // if the service HAS been resolved then updating is probably a bad idea // not sure which is best? that one works, though, and looks simpler registration.ImplementingType = implementingType; registration.FactoryExpression = factoryExpression; //container.Override( // r => r.ServiceType == typeof (TService) && (registration.ServiceName == null || r.ServiceName == registration.ServiceName), // (f, r) => // { // r.ImplementingType = implementingType; // r.FactoryExpression = factoryExpression; // return r; // }); } /// /// Gets the available service registrations for a service type. /// /// The service type. /// The container. /// The service registrations for the service type. public static IEnumerable GetAvailableServices(this IServiceRegistry container) { var typeofTService = typeof(TService); return container.AvailableServices.Where(x => x.ServiceType == typeofTService); } /// /// Gets the unique available service registration for a service type. /// /// The service type. /// The container. /// The unique service registration for the service type. /// Can return null, but throws if more than one registration exist for the service type. public static ServiceRegistration GetAvailableService(this IServiceRegistry container) { var typeofTService = typeof(TService); return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService); } /// /// Gets the unique available service registration for a service type and a name. /// /// The service type. /// The container. /// The name. /// The unique service registration for the service type and the name. /// Can return null, but throws if more than one registration exist for the service type and the name. public static ServiceRegistration GetAvailableService(this IServiceRegistry container, string name) { var typeofTService = typeof(TService); return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); } /// /// Gets an instance of a TService or throws a meaningful exception. /// /// The service type. /// The container. /// The instance. public static TService GetInstanceOrThrow(this IServiceFactory factory) { if (factory == null) throw new ArgumentNullException(nameof(factory)); try { return factory.GetInstance(); } catch (Exception e) { LightInjectException.TryThrow(e); throw; } } /// /// Gets an instance of a TService or throws a meaningful exception. /// /// The container. /// The type of the service. /// The name of the service. /// The implementing type. /// Arguments. /// The instance. internal static object GetInstanceOrThrow(this IServiceFactory factory, Type tService, string serviceName, Type implementingType, object[] args) { if (factory == null) throw new ArgumentNullException(nameof(factory)); try { return factory.GetInstance(tService, serviceName, args); } catch (Exception e) { LightInjectException.TryThrow(e, implementingType); throw; } } /// /// Registers a base type for auto-registration. /// /// The base type. /// The container. /// /// Any type that inherits/implements the base type will be auto-registered on-demand. /// This methods works with actual types. Use the other overload for eg generic definitions. /// public static void RegisterAuto(this IServiceContainer container) { container.RegisterFallback((serviceType, serviceName) => { //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); // https://github.com/seesharper/LightInject/issues/173 if (typeof(T).IsAssignableFrom(serviceType)) container.Register(serviceType); return false; }, null); } /// /// Registers a base type for auto-registration. /// /// The container. /// The base type. /// /// Any type that inherits/implements the base type will be auto-registered on-demand. /// This methods works with actual types, as well as generic definitions eg typeof(MyBase{}). /// public static void RegisterAuto(this IServiceContainer container, Type type) { container.RegisterFallback((serviceType, serviceName) => { //Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}."); // https://github.com/seesharper/LightInject/issues/173 if (type.IsAssignableFromGtd(serviceType)) container.Register(serviceType); return false; }, null); } /// /// Registers and instanciates a collection builder. /// /// The type of the collection builder. /// The container. /// The collection builder. public static TBuilder RegisterCollectionBuilder(this IServiceContainer container) { // make sure it's not already registered // we just don't want to support re-registering collection builders var registration = container.GetAvailableService(); if (registration != null) throw new InvalidOperationException("Collection builders should be registered only once."); // register the builder - per container var builderLifetime = new PerContainerLifetime(); container.Register(builderLifetime); // return the builder // (also initializes the builder) return container.GetInstance(); } } }