namespace Swan.DependencyInjection { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; /// /// 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 bool _disposed; static DependencyContainer() { } /// /// Initializes a new instance of the class. /// public DependencyContainer() { RegisteredTypes = new TypesConcurrentDictionary(this); Register(this); } private DependencyContainer(DependencyContainer parent) : 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 (_disposed) return; _disposed = true; foreach (var disposable in 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 /// /// 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( DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) { AutoRegister( AppDomain.CurrentDomain.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), duplicateAction, registrationPredicate); } /// /// 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, DependencyContainerDuplicateImplementationAction duplicateAction = DependencyContainerDuplicateImplementationAction.RegisterSingle, Func registrationPredicate = null) { lock (_autoRegisterLock) { var types = assemblies .SelectMany(a => a.GetAllTypes()) .Where(t => !IsIgnoredType(t, registrationPredicate)) .ToList(); var concreteTypes = types .Where(type => type.IsClass && !type.IsAbstract && (type != GetType() && (type.DeclaringType != GetType()) && !type.IsGenericTypeDefinition)) .ToList(); foreach (var type in concreteTypes) { try { RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, type)); } catch (MethodAccessException) { // Ignore methods we can't access - added for Silverlight } } var abstractInterfaceTypes = types.Where( type => ((type.IsInterface || type.IsAbstract) && (type.DeclaringType != GetType()) && (!type.IsGenericTypeDefinition))); foreach (var type in abstractInterfaceTypes) { var localType = type; var implementations = concreteTypes .Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList(); if (implementations.Skip(1).Any()) { if (duplicateAction == DependencyContainerDuplicateImplementationAction.Fail) throw new DependencyContainerRegistrationException(type, implementations); if (duplicateAction == DependencyContainerDuplicateImplementationAction.RegisterMultiple) { RegisterMultiple(type, implementations); } } var firstImplementation = implementations.FirstOrDefault(); if (firstImplementation == null) continue; try { 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 = "") => 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 = "") => 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 = "") => 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 = "") => 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 = "") => 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 { return 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 { return 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 { return 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 { return 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 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) => 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 (var 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()) { var queryForDuplicatedTypes = implementationTypes .GroupBy(i => i) .Where(j => j.Count() > 1) .Select(j => j.Key.FullName); var 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}"); } var registerOptions = implementationTypes .Select(type => 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 bool Unregister(string name = "") => 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 bool Unregister(Type registerType, string name = "") => 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) => 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 { return (TResolveType)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 bool CanResolve( Type resolveType, string name = null, DependencyContainerResolveOptions options = null) => 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 bool CanResolve( string name = null, DependencyContainerResolveOptions options = null) where TResolveType : class { return 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 bool TryResolve(Type resolveType, out object resolvedType) { try { resolvedType = 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 bool TryResolve(Type resolveType, DependencyContainerResolveOptions options, out object resolvedType) { try { resolvedType = 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 bool TryResolve(Type resolveType, string name, out object resolvedType) { try { resolvedType = 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 bool TryResolve( Type resolveType, string name, DependencyContainerResolveOptions options, out object resolvedType) { try { resolvedType = 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 bool TryResolve(out TResolveType resolvedType) where TResolveType : class { try { resolvedType = 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 bool TryResolve(DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class { try { resolvedType = 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 bool TryResolve(string name, out TResolveType resolvedType) where TResolveType : class { try { resolvedType = 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 bool TryResolve( string name, DependencyContainerResolveOptions options, out TResolveType resolvedType) where TResolveType : class { try { resolvedType = 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 ResolveAll(Type resolveType, bool includeUnnamed = false) => RegisteredTypes.Resolve(resolveType, includeUnnamed); /// /// Returns all registrations of a type. /// /// Type to resolveAll. /// Whether to include un-named (default) registrations. /// IEnumerable. public IEnumerable ResolveAll(bool includeUnnamed = true) where TResolveType : class { return ResolveAll(typeof(TResolveType), includeUnnamed).Cast(); } /// /// Attempts to resolve all public property dependencies on the given object using the given resolve options. /// /// Object to "build up". /// Resolve options to use. public void BuildUp(object input, DependencyContainerResolveOptions resolveOptions = null) { if (resolveOptions == null) resolveOptions = DependencyContainerResolveOptions.Default; var properties = input.GetType() .GetProperties() .Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null && !property.PropertyType.IsValueType); foreach (var property in properties.Where(property => property.GetValue(input, null) == null)) { try { property.SetValue( input, RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions), null); } catch (DependencyContainerResolutionException) { // Catch any resolution errors and ignore them } } } #endregion #region Internal Methods internal static bool IsValidAssignment(Type registerType, Type registerImplementation) { if (!registerType.IsGenericTypeDefinition) { if (!registerType.IsAssignableFrom(registerImplementation)) return false; } else { if (registerType.IsInterface && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name)) return false; if (registerType.IsAbstract && registerImplementation.BaseType != registerType) return false; } return true; } private static bool IsIgnoredAssembly(Assembly assembly) { // TODO - find a better way to remove "system" assemblies from the auto registration var ignoreChecks = new List> { asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal), asm => asm.FullName.StartsWith("System,", StringComparison.Ordinal), asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.Ordinal), asm => asm.FullName.StartsWith("mscorlib,", StringComparison.Ordinal), asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.Ordinal), asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.Ordinal), asm => asm.FullName.StartsWith("xunit.", StringComparison.Ordinal), }; return ignoreChecks.Any(check => check(assembly)); } private static bool IsIgnoredType(Type type, Func registrationPredicate) { // TODO - find a better way to remove "system" types from the auto registration var ignoreChecks = new List>() { t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false, t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false, t => t.IsPrimitive, t => t.IsGenericTypeDefinition, t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) && !(t.IsInterface || t.IsAbstract), }; if (registrationPredicate != null) { ignoreChecks.Add(t => !registrationPredicate(t)); } return ignoreChecks.Any(check => check(type)); } private static ObjectFactoryBase GetDefaultObjectFactory(Type registerType, Type registerImplementation) => registerType.IsInterface || registerType.IsAbstract ? (ObjectFactoryBase)new SingletonFactory(registerType, registerImplementation) : new MultiInstanceFactory(registerType, registerImplementation); #endregion } }