using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Umbraco.Core.Composing { /// /// Provides extension methods to the class. /// public static class ContainerExtensions { private static readonly ConcurrentDictionary>> ArgumentPropertyGetters = new ConcurrentDictionary>>(); /// /// Gets an instance of a service. /// /// The type of the service. /// The container. /// An instance of the specified type. /// Throws an exception if the container failed to get an instance of the specified type. public static T GetInstance(this IContainer container) => (T) container.GetInstance(typeof(T)); /// /// Gets an instance of a named service. /// /// The type of the service. /// The container. /// The name of the service. /// An instance of the specified type and name. /// Throws an exception if the container failed to get an instance of the specified type. public static T GetInstance(this IContainer container, string name) => (T) container.GetInstance(typeof(T), name); /// /// Tries to get an instance of a service. /// /// The type of the service. /// An instance of the specified type, or null. /// Returns null if the container does not know how to get an instance /// of the specified type. Throws an exception if the container does know how /// to get an instance of the specified type, but failed to do so. public static T TryGetInstance(this IContainer container) => (T) container.TryGetInstance(typeof(T)); /// /// Gets registrations for a service. /// /// The type of the service. /// The registrations for the service. public static IEnumerable GetRegistered(this IContainer container) => container.GetRegistered(typeof(TService)); /// /// Creates an instance with arguments. /// /// The type of the instance. /// The container. /// Arguments. /// An instance of the specified type. /// /// Throws an exception if the container failed to get an instance of the specified type. /// The arguments are used as dependencies by the container. /// public static T CreateInstance(this IContainer container, IDictionary args) => (T) container.CreateInstance(typeof(T), args); /// /// Creates an instance with arguments. /// /// The type of the instance. /// The container. /// Arguments. /// An instance of the specified type. /// /// Throws an exception if the container failed to get an instance of the specified type. /// The arguments are used as dependencies by the container. /// public static T CreateInstance(this IContainer container, object args) { var typeOfArgs = args.GetType(); var getters = ArgumentPropertyGetters.GetOrAdd(typeOfArgs, type => args.GetType() .GetProperties() .ToDictionary(x => x.Name, x => ReflectionUtilities.EmitMethodUnsafe>(x.GetMethod))); var argsDictionary = new Dictionary(); foreach (var (name, getter) in getters) argsDictionary[name] = getter(args); return (T) container.CreateInstance(typeof(T), argsDictionary); } /// /// Registers a service with an implementation type. /// public static void Register(this IContainer container, Lifetime lifetime = Lifetime.Transient) => container.Register(typeof(TService), typeof(TImplementing), lifetime); /// /// Registers a named service with an implementation type. /// public static void Register(this IContainer container, string name, Lifetime lifetime = Lifetime.Transient) => container.Register(typeof(TService), typeof(TImplementing), name, lifetime); /// /// Registers a named service with an implementation factory. /// public static void Register(this IContainer container, string name, Func factory, Lifetime lifetime = Lifetime.Transient) => container.Register(factory, name, lifetime); /// /// Registers a service as its own implementation. /// public static void Register(this IContainer container, Lifetime lifetime = Lifetime.Transient) => container.Register(typeof(TService), lifetime); /// /// Registers a singleton service as its own implementation. /// public static void RegisterSingleton(this IContainer container) => container.Register(typeof(TService), Lifetime.Singleton); /// /// Registers a singleton service with an implementation type. /// public static void RegisterSingleton(this IContainer container) => container.Register(typeof(TService), typeof(TImplementing), Lifetime.Singleton); /// /// Registers a singleton service with an implementation factory. /// public static void RegisterSingleton(this IContainer container, Func factory) => container.Register(factory, Lifetime.Singleton); /// /// Registers a named singleton service with an implementation factory. /// public static void RegisterSingleton(this IContainer container, string name, Func factory) => container.Register(factory, name, Lifetime.Singleton); /// /// Registers a service with an implementing instance. /// public static void RegisterInstance(this IContainer container, TService instance) => container.RegisterInstance(typeof(TService), instance); /// /// Registers a base type for auto-registration. /// public static void RegisterAuto(this IContainer container) => container.RegisterAuto(typeof(TServiceBase)); /// /// Registers and instantiates a collection builder. /// /// The type of the collection builder. /// A collection builder of the specified type. public static TBuilder RegisterCollectionBuilder(this IContainer container) { // make sure it's not already registered // we just don't want to support re-registering collection builders if (container.GetRegistered().Any()) throw new InvalidOperationException("Collection builders should be registered only once."); // register the builder // use a factory so we don't have to self-register the container container.RegisterSingleton(factory => factory.CreateInstance(new Dictionary {{ "container", container }} )); // initialize and return the builder return container.GetInstance(); } /// /// Creates an instance of a service, with arguments. /// /// /// The type of the instance. /// Named arguments. /// An instance of the specified type. /// /// The instance type does not need to be registered into the container. /// The arguments are used as dependencies by the container. Other dependencies /// are retrieved from the container. /// public static object CreateInstance(this IContainer container, Type type, IDictionary args) { // LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities // including the most annoying one, which is that it does not work on singletons (hard to fix) //return Container.GetInstance(type, args); // this method is essentially used to build singleton instances, so it is assumed that it would be // more expensive to build and cache a dynamic method ctor than to simply invoke the ctor, as we do // here - this can be discussed 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?.Values.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); ctorArgs[i++] = arg ?? container.GetInstance(parameter.ParameterType); } return ctor.Invoke(ctorArgs); } } }