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