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();
}
}
}