using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Swan.DependencyInjection { /// /// 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); } 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 /// /// 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) => this.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(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 == DependencyContainerDuplicateImplementationAction.Fail) { throw new DependencyContainerRegistrationException(type, implementations); } if(duplicateAction == DependencyContainerDuplicateImplementationAction.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 ResolveAll(Type resolveType, Boolean includeUnnamed = false) => this.RegisteredTypes.Resolve(resolveType, includeUnnamed); /// /// Returns all registrations of a type. /// /// Type to resolveAll. /// Whether to include un-named (default) registrations. /// IEnumerable. public IEnumerable ResolveAll(Boolean includeUnnamed = true) where TResolveType : class => this.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; } IEnumerable properties = input.GetType().GetProperties().Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null && !property.PropertyType.IsValueType); foreach(PropertyInfo property in properties.Where(property => property.GetValue(input, null) == null)) { try { property.SetValue(input, this.RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions), null); } catch(DependencyContainerResolutionException) { // Catch any resolution errors and ignore them } } } #endregion #region Internal Methods internal static Boolean 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 Boolean IsIgnoredAssembly(Assembly assembly) { // TODO - find a better way to remove "system" assemblies from the auto registration List> 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 Boolean IsIgnoredType(Type type, Func registrationPredicate) { // TODO - find a better way to remove "system" types from the auto registration List> 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 } }