using Unosquare.Swan.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Unosquare.Swan.Components {
///
/// The concrete implementation of a simple IoC container
/// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
///
///
public partial class DependencyContainer : IDisposable {
private readonly Object _autoRegisterLock = new Object();
private Boolean _disposed;
static DependencyContainer() {
}
///
/// Initializes a new instance of the class.
///
public DependencyContainer() {
this.RegisteredTypes = new TypesConcurrentDictionary(this);
_ = this.Register(this);
// Only register the TinyMessenger singleton if we are the root container
if(this.Parent == null) {
_ = this.Register();
}
}
private DependencyContainer(DependencyContainer parent)
: this() => this.Parent = parent;
///
/// Lazy created Singleton instance of the container for simple scenarios.
///
public static DependencyContainer Current { get; } = new DependencyContainer();
internal DependencyContainer Parent {
get;
}
internal TypesConcurrentDictionary RegisteredTypes {
get;
}
///
public void Dispose() {
if(this._disposed) {
return;
}
this._disposed = true;
foreach(IDisposable disposable in this.RegisteredTypes.Values.Select(item => item as IDisposable)) {
disposable?.Dispose();
}
GC.SuppressFinalize(this);
}
///
/// Gets the child container.
///
/// A new instance of the class.
public DependencyContainer GetChildContainer() => new DependencyContainer(this);
#region Registration
#if !NETSTANDARD1_3
///
/// Attempt to automatically register all non-generic classes and interfaces in the current app domain.
/// Types will only be registered if they pass the supplied registration predicate.
///
/// What action to take when encountering duplicate implementations of an interface/base class.
/// Predicate to determine if a particular type should be registered.
public void AutoRegister(
DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func registrationPredicate = null) => this.AutoRegister(
Runtime.GetAssemblies().Where(a => !IsIgnoredAssembly(a)),
duplicateAction,
registrationPredicate);
#endif
///
/// Attempt to automatically register all non-generic classes and interfaces in the specified assemblies
/// Types will only be registered if they pass the supplied registration predicate.
///
/// Assemblies to process.
/// What action to take when encountering duplicate implementations of an interface/base class.
/// Predicate to determine if a particular type should be registered.
public void AutoRegister(
IEnumerable assemblies,
DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func registrationPredicate = null) {
lock(this._autoRegisterLock) {
List types = assemblies
.SelectMany(a => a.GetAllTypes())
.Where(t => !IsIgnoredType(t, registrationPredicate))
.ToList();
List concreteTypes = types
.Where(type =>
type.IsClass() && !type.IsAbstract() &&
type != this.GetType() && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition())
.ToList();
foreach(Type type in concreteTypes) {
try {
_ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, type));
} catch(MethodAccessException) {
// Ignore methods we can't access - added for Silverlight
}
}
IEnumerable abstractInterfaceTypes = types.Where(
type =>
(type.IsInterface() || type.IsAbstract()) && type.DeclaringType != this.GetType() &&
!type.IsGenericTypeDefinition());
foreach(Type type in abstractInterfaceTypes) {
Type localType = type;
List implementations = concreteTypes
.Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
if(implementations.Skip(1).Any()) {
if(duplicateAction == DependencyContainerDuplicateImplementationActions.Fail) {
throw new DependencyContainerRegistrationException(type, implementations);
}
if(duplicateAction == DependencyContainerDuplicateImplementationActions.RegisterMultiple) {
_ = this.RegisterMultiple(type, implementations);
}
}
Type firstImplementation = implementations.FirstOrDefault();
if(firstImplementation == null) {
continue;
}
try {
_ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, firstImplementation));
} catch(MethodAccessException) {
// Ignore methods we can't access - added for Silverlight
}
}
}
}
///
/// Creates/replaces a named container class registration with default options.
///
/// Type to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(Type registerType, String name = "")
=> this.RegisteredTypes.Register(
registerType,
name,
GetDefaultObjectFactory(registerType, registerType));
///
/// Creates/replaces a named container class registration with a given implementation and default options.
///
/// Type to register.
/// Type to instantiate that implements RegisterType.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(Type registerType, Type registerImplementation, String name = "") =>
this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
///
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
///
/// Type to register.
/// Instance of RegisterType to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(Type registerType, Object instance, String name = "") =>
this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
///
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
///
/// Type to register.
/// Type of instance to register that implements RegisterType.
/// Instance of RegisterImplementation to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(
Type registerType,
Type registerImplementation,
Object instance,
String name = "")
=> this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
///
/// Creates/replaces a container class registration with a user specified factory.
///
/// Type to register.
/// Factory/lambda that returns an instance of RegisterType.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(
Type registerType,
Func, Object> factory,
String name = "")
=> this.RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
///
/// Creates/replaces a named container class registration with default options.
///
/// Type to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(String name = "")
where TRegister : class => this.Register(typeof(TRegister), name);
///
/// Creates/replaces a named container class registration with a given implementation and default options.
///
/// Type to register.
/// Type to instantiate that implements RegisterType.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(String name = "")
where TRegister : class
where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), name);
///
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
///
/// Type to register.
/// Instance of RegisterType to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(TRegister instance, String name = "")
where TRegister : class => this.Register(typeof(TRegister), instance, name);
///
/// Creates/replaces a named container class registration with a specific, strong referenced, instance.
///
/// Type to register.
/// Type of instance to register that implements RegisterType.
/// Instance of RegisterImplementation to register.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(TRegisterImplementation instance,
String name = "")
where TRegister : class
where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
///
/// Creates/replaces a named container class registration with a user specified factory.
///
/// Type to register.
/// Factory/lambda that returns an instance of RegisterType.
/// Name of registration.
/// RegisterOptions for fluent API.
public RegisterOptions Register(
Func, TRegister> factory, String name = "")
where TRegister : class {
if(factory == null) {
throw new ArgumentNullException(nameof(factory));
}
return this.Register(typeof(TRegister), factory, name);
}
///
/// Register multiple implementations of a type.
///
/// Internally this registers each implementation using the full name of the class as its registration name.
///
/// Type that each implementation implements.
/// Types that implement RegisterType.
/// MultiRegisterOptions for the fluent API.
public MultiRegisterOptions RegisterMultiple(IEnumerable implementationTypes) =>
this.RegisterMultiple(typeof(TRegister), implementationTypes);
///
/// Register multiple implementations of a type.
///
/// Internally this registers each implementation using the full name of the class as its registration name.
///
/// Type that each implementation implements.
/// Types that implement RegisterType.
/// MultiRegisterOptions for the fluent API.
public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable implementationTypes) {
if(implementationTypes == null) {
throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
}
foreach(Type type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type))) {
throw new ArgumentException(
$"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
}
if(implementationTypes.Count() != implementationTypes.Distinct().Count()) {
IEnumerable queryForDuplicatedTypes = implementationTypes
.GroupBy(i => i)
.Where(j => j.Count() > 1)
.Select(j => j.Key.FullName);
String fullNamesOfDuplicatedTypes = String.Join(",\n", queryForDuplicatedTypes.ToArray());
throw new ArgumentException(
$"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
}
List registerOptions = implementationTypes
.Select(type => this.Register(registrationType, type, type.FullName))
.ToList();
return new MultiRegisterOptions(registerOptions);
}
#endregion
#region Unregistration
///
/// Remove a named container class registration.
///
/// Type to unregister.
/// Name of registration.
/// true if the registration is successfully found and removed; otherwise, false.
public Boolean Unregister(String name = "") => this.Unregister(typeof(TRegister), name);
///
/// Remove a named container class registration.
///
/// Type to unregister.
/// Name of registration.
/// true if the registration is successfully found and removed; otherwise, false.
public Boolean Unregister(Type registerType, String name = "") =>
this.RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
#endregion
#region Resolution
///
/// Attempts to resolve a named type using specified options and the supplied constructor parameters.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
///
/// Type to resolve.
/// Name of registration.
/// Resolution options.
/// Instance of type.
/// Unable to resolve the type.
public Object Resolve(
Type resolveType,
String name = null,
DependencyContainerResolveOptions options = null)
=> this.RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
///
/// Attempts to resolve a named type using specified options and the supplied constructor parameters.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
///
/// Type to resolve.
/// Name of registration.
/// Resolution options.
/// Instance of type.
/// Unable to resolve the type.
public TResolveType Resolve(
String name = null,
DependencyContainerResolveOptions options = null)
where TResolveType : class => (TResolveType)this.Resolve(typeof(TResolveType), name, options);
///
/// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
/// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
///
/// Type to resolve.
/// The name.
/// Resolution options.
///
/// Bool indicating whether the type can be resolved.
///
public Boolean CanResolve(
Type resolveType,
String name = null,
DependencyContainerResolveOptions options = null) =>
this.RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
///
/// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
///
/// Parameters are used in conjunction with normal container resolution to find the most suitable constructor (if one exists).
/// All user supplied parameters must exist in at least one resolvable constructor of RegisterType or resolution will fail.
///
/// Note: Resolution may still fail if user defined factory registrations fail to construct objects when called.
///
/// Type to resolve.
/// Name of registration.
/// Resolution options.
/// Bool indicating whether the type can be resolved.
public Boolean CanResolve(
String name = null,
DependencyContainerResolveOptions options = null)
where TResolveType : class => this.CanResolve(typeof(TResolveType), name, options);
///
/// Attempts to resolve a type using the default options.
///
/// Type to resolve.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(Type resolveType, out Object resolvedType) {
try {
resolvedType = this.Resolve(resolveType);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = null;
return false;
}
}
///
/// Attempts to resolve a type using the given options.
///
/// Type to resolve.
/// Resolution options.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(Type resolveType, DependencyContainerResolveOptions options, out Object resolvedType) {
try {
resolvedType = this.Resolve(resolveType, options: options);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = null;
return false;
}
}
///
/// Attempts to resolve a type using the default options and given name.
///
/// Type to resolve.
/// Name of registration.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(Type resolveType, String name, out Object resolvedType) {
try {
resolvedType = this.Resolve(resolveType, name);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = null;
return false;
}
}
///
/// Attempts to resolve a type using the given options and name.
///
/// Type to resolve.
/// Name of registration.
/// Resolution options.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(
Type resolveType,
String name,
DependencyContainerResolveOptions options,
out Object resolvedType) {
try {
resolvedType = this.Resolve(resolveType, name, options);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = null;
return false;
}
}
///
/// Attempts to resolve a type using the default options.
///
/// Type to resolve.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(out TResolveType resolvedType)
where TResolveType : class {
try {
resolvedType = this.Resolve();
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = default;
return false;
}
}
///
/// Attempts to resolve a type using the given options.
///
/// Type to resolve.
/// Resolution options.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(DependencyContainerResolveOptions options, out TResolveType resolvedType)
where TResolveType : class {
try {
resolvedType = this.Resolve(options: options);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = default;
return false;
}
}
///
/// Attempts to resolve a type using the default options and given name.
///
/// Type to resolve.
/// Name of registration.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(String name, out TResolveType resolvedType)
where TResolveType : class {
try {
resolvedType = this.Resolve(name);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = default;
return false;
}
}
///
/// Attempts to resolve a type using the given options and name.
///
/// Type to resolve.
/// Name of registration.
/// Resolution options.
/// Resolved type or default if resolve fails.
/// true if resolved successfully, false otherwise.
public Boolean TryResolve(
String name,
DependencyContainerResolveOptions options,
out TResolveType resolvedType)
where TResolveType : class {
try {
resolvedType = this.Resolve(name, options);
return true;
} catch(DependencyContainerResolutionException) {
resolvedType = default;
return false;
}
}
///
/// Returns all registrations of a type.
///
/// Type to resolveAll.
/// Whether to include un-named (default) registrations.
/// IEnumerable.
public IEnumerable