mirror of
https://github.com/AlexMacocian/Slim.git
synced 2026-07-25 08:22:12 +00:00
1129 lines
45 KiB
C#
1129 lines
45 KiB
C#
using Slim.Attributes;
|
|
using Slim.Exceptions;
|
|
using Slim.Resolvers;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace Slim;
|
|
|
|
/// <summary>
|
|
/// <see cref="ServiceManager"/> responsible with storing and resolving services.
|
|
/// </summary>
|
|
public sealed class ServiceManager : IServiceManager
|
|
{
|
|
private bool disposedValue;
|
|
private readonly bool scoped = false;
|
|
|
|
private Dictionary<Type, List<(Type, Lifetime)>> InterfaceMapping { get; } = [];
|
|
private Dictionary<Type, List<object>> Instances { get; } = [];
|
|
private Dictionary<Type, List<Delegate>> Factories { get; } = [];
|
|
private Dictionary<Type, Delegate> ExceptionHandlers { get; } = [];
|
|
private List<IDependencyResolver> Resolvers { get; } = [];
|
|
|
|
/// <summary>
|
|
/// Allow modifications to scoped <see cref="IServiceManager"/> created from this <see cref="IServiceManager"/>.
|
|
/// </summary>
|
|
public bool AllowScopedManagerModifications { get; set; }
|
|
/// <summary>
|
|
/// Returns true if <see cref="IServiceManager"/> is readonly.
|
|
/// </summary>
|
|
public bool IsReadOnly { get; }
|
|
/// <summary>
|
|
/// Gets the <see cref="IServiceManager"/> that created the current <see cref="IServiceManager"/>. Returns null in case there is no parent.
|
|
/// </summary>
|
|
public IServiceManager? ParentServiceManager { get; }
|
|
|
|
/// <summary>
|
|
/// Creates an instance of <see cref="IServiceManager"/>.
|
|
/// </summary>
|
|
public ServiceManager()
|
|
{
|
|
}
|
|
|
|
private ServiceManager(
|
|
Dictionary<Type, List<(Type, Lifetime)>> interfaceMapping,
|
|
Dictionary<Type, List<object>> singletons,
|
|
Dictionary<Type, List<Delegate>> factories,
|
|
Dictionary<Type, Delegate> exceptionHandlers,
|
|
List<IDependencyResolver> resolvers,
|
|
IServiceManager parent,
|
|
bool isReadOnly)
|
|
{
|
|
this.InterfaceMapping = interfaceMapping;
|
|
this.Instances = singletons;
|
|
this.Factories = factories;
|
|
this.ExceptionHandlers = exceptionHandlers;
|
|
this.Resolvers = resolvers;
|
|
this.scoped = true;
|
|
this.ParentServiceManager = parent;
|
|
this.IsReadOnly = isReadOnly;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if there exists a registration for <paramref name="tInterface"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of registered service.</param>
|
|
/// <returns>True if service <paramref name="tInterface"/> is registered. Otherwise returns false.</returns>
|
|
public bool IsRegistered(Type tInterface)
|
|
{
|
|
return this.IsMapped(tInterface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if there exists a registration for <typeparamref name="T"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of registered service.</typeparam>
|
|
/// <returns>True if service <typeparamref name="T"/> is registered. Otherwise returns false.</returns>
|
|
public bool IsRegistered<T>()
|
|
{
|
|
return this.IsMapped(typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient<TInterface, TClass>()
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Transient, typeof(TInterface));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for the implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Transient, typeof(TInterface), serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton<TInterface, TClass>()
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Singleton, typeof(TInterface));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Singleton, typeof(TInterface), serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped<TInterface, TClass>()
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Scoped, typeof(TInterface));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of interface.</typeparam>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
|
where TInterface : class
|
|
where TClass : TInterface
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Scoped, typeof(TInterface), serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient(Type tInterface, Type tClass)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Transient, tInterface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory)
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(tClass, Lifetime.Transient, tInterface, serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton(Type tInterface, Type tClass)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Singleton, tInterface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory)
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(tClass, Lifetime.Singleton, tInterface, serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped(Type tInterface, Type tClass)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Scoped, tInterface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// </summary>
|
|
/// <param name="tInterface">Type of interface.</param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory)
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(tClass, Lifetime.Scoped, tInterface, serviceFactory);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton<TClass>(bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Singleton, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for the implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton<TClass>(Func<IServiceProvider, TClass> serviceFactory, bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Singleton, null, serviceFactory, registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient<TClass>(bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Transient, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for the implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient<TClass>(Func<IServiceProvider, TClass> serviceFactory, bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Transient, null, serviceFactory, registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped<TClass>(bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
this.RegisterService(typeof(TClass), Lifetime.Scoped, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <typeparam name="TClass">Type of implementation.</typeparam>
|
|
/// <param name="serviceFactory">Factory for the implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped<TClass>(Func<IServiceProvider, TClass> serviceFactory, bool registerAllInterfaces = false) where TClass : class
|
|
{
|
|
if (serviceFactory is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(serviceFactory));
|
|
}
|
|
|
|
this.RegisterService(typeof(TClass), Lifetime.Scoped, null, serviceFactory, registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient(Type tClass, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Transient, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Transient"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterTransient(Type tClass, Func<IServiceProvider, object> serviceFactory, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Transient, null, serviceFactory, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton(Type tClass, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Singleton, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Singleton"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterSingleton(Type tClass, Func<IServiceProvider, object> serviceFactory, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Singleton, null, serviceFactory, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped(Type tClass, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Scoped, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a service into <see cref="ServiceManager"/> with <see cref="Lifetime.Scoped"/>.
|
|
/// Registers the service for all interfaces it implements.
|
|
/// </summary>
|
|
/// <param name="registerAllInterfaces">If true, <see cref="ServiceManager"/> will register all interfaces implemented by the provided class./></param>
|
|
/// <param name="tClass">Type of implementation.</param>
|
|
/// <param name="serviceFactory">Factory for implementation.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when the provided serviceFactory is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when <see cref="ServiceManager"/> contains an entry for the provided interface type.</exception>
|
|
public void RegisterScoped(Type tClass, Func<IServiceProvider, object> serviceFactory, bool registerAllInterfaces = false)
|
|
{
|
|
this.RegisterService(tClass, Lifetime.Scoped, null, serviceFactory, registerAllInterfaces: registerAllInterfaces);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a scoped <see cref="IServiceProvider"/>.
|
|
/// </summary>
|
|
/// <returns>Scoped <see cref="IServiceProvider"/>.</returns>
|
|
public IServiceProvider CreateScope()
|
|
{
|
|
return this.CreateScopeInner();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves and returns the required service.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Type of required service.</typeparam>
|
|
/// <returns>Required service.</returns>
|
|
/// <exception cref="DependencyInjectionException">Thrown when unable to resolve required service.</exception>
|
|
public TInterface? GetService<TInterface>() where TInterface : class
|
|
{
|
|
return this.PrepareAndGetService(typeof(TInterface)) as TInterface;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves and returns the required service.
|
|
/// </summary>
|
|
/// <param name="type">Type of required service.</param>
|
|
/// <returns>Required service.</returns>
|
|
/// <exception cref="DependencyInjectionException">Thrown when unable to resolve required service.</exception>
|
|
public object? GetService(Type type)
|
|
{
|
|
return this.PrepareAndGetService(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns all services that can be cast to provided type.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of the returned services.</typeparam>
|
|
public IEnumerable<T> GetServicesOfType<T>()
|
|
{
|
|
return this.EnumerateAndReturnServicesOfType(typeof(T)).OfType<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns all services that can be cast to provided type.
|
|
/// </summary>
|
|
/// <param name="type">Type of the returned services.</param>
|
|
public IEnumerable<object> GetServicesOfType(Type type)
|
|
{
|
|
return this.EnumerateAndReturnServicesOfType(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a type of exception to be caught and handled.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of exception to catch.</typeparam>
|
|
/// <param name="handle">Handler of the exception. Handler returns true if the exception should be thrown again.</param>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public void HandleException<T>(Func<IServiceProvider, T, bool> handle)
|
|
where T : Exception
|
|
{
|
|
this.ExceptionHandlers.Add(typeof(T), handle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a registered service.
|
|
/// </summary>
|
|
/// <param name="tInterface">Registered type.</param>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
public void Remove(Type tInterface)
|
|
{
|
|
_ = tInterface ?? throw new ArgumentNullException(nameof(tInterface));
|
|
|
|
this.RemoveService(tInterface);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove a registered service.
|
|
/// </summary>
|
|
/// <typeparam name="T">Registered interface.</typeparam>
|
|
public void Remove<T>()
|
|
{
|
|
this.RemoveService(typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all registered types, singletons, factories and exception handlers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Calls <see cref="IDisposable.Dispose"/> on all <see cref="IDisposable"/> singletons.
|
|
/// </remarks>
|
|
public void Clear()
|
|
{
|
|
if (this.IsReadOnly)
|
|
{
|
|
throw new InvalidOperationException($"Cannot clear {nameof(ServiceManager)}. {nameof(ServiceManager)} is readonly!");
|
|
}
|
|
|
|
this.InterfaceMapping.Clear();
|
|
foreach (var singleton in this.Instances.Values.SelectMany(instances => instances).Where(s => s is IDisposable).Select(s => (IDisposable)s))
|
|
{
|
|
singleton.Dispose();
|
|
}
|
|
|
|
this.Factories.Clear();
|
|
this.ExceptionHandlers.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build all registered singletons.
|
|
/// </summary>
|
|
public void BuildSingletons()
|
|
{
|
|
foreach(var mapping in this.InterfaceMapping)
|
|
{
|
|
foreach(var kvp in mapping.Value)
|
|
{
|
|
if (kvp.Item2 is Lifetime.Singleton)
|
|
{
|
|
this.GetService(mapping.Key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a <see cref="IDependencyResolver"/> that manually resolves dependencies.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This resolver will be called before auto-resolving a dependency. If resolver can handle the dependency, <see cref="IServiceManager"/> will return the resolved value.
|
|
/// </remarks>
|
|
/// <param name="dependencyResolver"><see cref="IDependencyResolver"/> that manually creates a dependency.</param>
|
|
public void RegisterResolver(IDependencyResolver dependencyResolver)
|
|
{
|
|
if (this.IsReadOnly)
|
|
{
|
|
throw new InvalidOperationException($"Cannot register resolver. {nameof(ServiceManager)} is readonly!");
|
|
}
|
|
|
|
this.Resolvers.Add(dependencyResolver);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up resources and disposes of all disposable singletons.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
this.Dispose(disposing: true);
|
|
}
|
|
|
|
private IEnumerable<object> EnumerateAndReturnServicesOfType(Type type)
|
|
{
|
|
foreach (var kvp in this.InterfaceMapping)
|
|
{
|
|
foreach(var registration in kvp.Value)
|
|
{
|
|
var objectType = registration.Item1;
|
|
if (type.IsAssignableFrom(objectType) &&
|
|
this.PrepareAndGetService(kvp.Key) is object obj)
|
|
{
|
|
yield return obj;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RegisterService(Type tClass, Lifetime lifetime, Type? tInterface = null, Delegate? serviceFactory = null, bool registerAllInterfaces = false)
|
|
{
|
|
if (this.IsReadOnly)
|
|
{
|
|
throw new InvalidOperationException($"Cannot register service. {nameof(ServiceManager)} is readonly!");
|
|
}
|
|
|
|
if (tInterface is not null)
|
|
{
|
|
this.Map(tInterface, tClass, lifetime);
|
|
if (serviceFactory is not null)
|
|
{
|
|
this.RegisterFactory(tInterface, serviceFactory);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (registerAllInterfaces)
|
|
{
|
|
foreach (var i in tClass.GetInterfaces())
|
|
{
|
|
this.Map(i, tClass, lifetime);
|
|
if (serviceFactory is not null)
|
|
{
|
|
this.RegisterFactory(i, serviceFactory);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.Map(tClass, tClass, lifetime);
|
|
if (serviceFactory is not null)
|
|
{
|
|
this.RegisterFactory(tClass, serviceFactory);
|
|
}
|
|
}
|
|
|
|
private object? PrepareAndGetService(Type tInterface)
|
|
{
|
|
return this.TryFunc(() =>
|
|
{
|
|
return this.GetObject(tInterface);
|
|
});
|
|
}
|
|
|
|
private void RegisterFactory(Type tinterface, Delegate factory)
|
|
{
|
|
this.TryAction(() =>
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!this.Factories.TryGetValue(tinterface, out var factories))
|
|
{
|
|
factories = [];
|
|
this.Factories[tinterface] = factories;
|
|
}
|
|
|
|
factories.Add(factory);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void RemoveService(Type tInterface)
|
|
{
|
|
this.TryAction(() =>
|
|
{
|
|
lock (this)
|
|
{
|
|
this.Factories.Remove(tInterface);
|
|
this.InterfaceMapping.Remove(tInterface);
|
|
if (this.Instances.TryGetValue(tInterface, out var service) &&
|
|
service is IDisposable disposableService)
|
|
{
|
|
disposableService.Dispose();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void Map(Type tinterface, Type tclass, Lifetime lifetime)
|
|
{
|
|
this.TryAction(() =>
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!this.InterfaceMapping.TryGetValue(tinterface, out var value))
|
|
{
|
|
value = [];
|
|
this.InterfaceMapping[tinterface] = value;
|
|
}
|
|
|
|
value.Add((tclass, lifetime));
|
|
}
|
|
});
|
|
}
|
|
|
|
private bool IsMapped(Type tinterface)
|
|
{
|
|
return this.TryFunc(() =>
|
|
{
|
|
if (IsServiceManagerDependency(tinterface))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
lock (this)
|
|
{
|
|
if (this.Resolvers.Any(resolver => resolver.CanResolve(tinterface)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return this.InterfaceMapping.ContainsKey(tinterface);
|
|
}
|
|
});
|
|
}
|
|
|
|
private object? GetObject(Type tInterface)
|
|
{
|
|
if (IsServiceManagerDependency(tInterface))
|
|
{
|
|
return this;
|
|
}
|
|
|
|
if (!this.InterfaceMapping.TryGetValue(tInterface, out var mappingTuples) &&
|
|
this.Resolvers.Any(resolver => resolver.CanResolve(tInterface)) is false)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var tuple = mappingTuples?.LastOrDefault();
|
|
if (tuple is not null &&
|
|
tuple.Value.Item2 is Lifetime.Singleton or Lifetime.Scoped)
|
|
{
|
|
if (this.Instances.TryGetValue(tuple.Value.Item1, out var objs) &&
|
|
objs.LastOrDefault() is object obj)
|
|
{
|
|
return obj;
|
|
}
|
|
else
|
|
{
|
|
if (this.TryImplementService(tInterface) is not object newObj)
|
|
{
|
|
throw new DependencyInjectionException($"No suitable constructor was found for type {tInterface.Name}");
|
|
}
|
|
|
|
if (objs is null)
|
|
{
|
|
objs = [];
|
|
this.Instances[tuple.Value.Item1] = objs;
|
|
}
|
|
|
|
objs.Add(newObj);
|
|
return newObj;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.TryImplementService(tInterface) is not object obj)
|
|
{
|
|
throw new DependencyInjectionException($"No suitable constructor was found for type {tInterface.Name}");
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
private object? TryImplementService(Type type)
|
|
{
|
|
foreach (var resolver in this.Resolvers)
|
|
{
|
|
if (resolver.CanResolve(type))
|
|
{
|
|
return resolver.Resolve(this, type);
|
|
}
|
|
}
|
|
|
|
var registrations = this.InterfaceMapping[type];
|
|
if (registrations is null ||
|
|
registrations.Count is 0)
|
|
{
|
|
throw new DependencyInjectionException($"No registered service for type {type.Name}.");
|
|
}
|
|
|
|
(var implementType, _) = registrations.LastOrDefault();
|
|
if (this.Factories.TryGetValue(type, out var factories) &&
|
|
factories is not null &&
|
|
factories.Count > 0)
|
|
{
|
|
return factories.LastOrDefault().DynamicInvoke(this);
|
|
}
|
|
|
|
return this.FindAndCallConstructors(implementType);
|
|
}
|
|
|
|
private object? FindAndCallConstructors(Type implementType)
|
|
{
|
|
/*
|
|
* Order constructors to give priority to constructors that have PrefferedConstructorAttribute decorator
|
|
*/
|
|
var constructors = implementType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.Where(c => c.GetCustomAttribute<DoNotInjectAttribute>() is null)
|
|
.OrderBy(c =>
|
|
c.GetCustomAttribute<PreferredConstructorAttribute>() is PreferredConstructorAttribute preferredConstructorAttribute ?
|
|
preferredConstructorAttribute.Priority :
|
|
int.MaxValue);
|
|
foreach (var constructor in constructors)
|
|
{
|
|
/*
|
|
* Filter by public constructors and try to find if the DI Service has
|
|
* implementations for the required parameters. If there are, add them to a list and call
|
|
* the constructor with the paramters.
|
|
* If the parameters are missing, try with other constructors.
|
|
*/
|
|
|
|
var parameters = this.GetParameters(constructor.GetParameters());
|
|
if (parameters is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return constructor.Invoke(parameters);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private object[]? GetParameters(ParameterInfo[] parameterInfos)
|
|
{
|
|
var parameterImplementationList = new object[parameterInfos.Length];
|
|
var index = 0;
|
|
foreach (var par in parameterInfos)
|
|
{
|
|
if (IsServiceManagerDependency(par.ParameterType))
|
|
{
|
|
parameterImplementationList[index] = this;
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
if (!this.InterfaceMapping.TryGetValue(par.ParameterType, out var registrations) ||
|
|
registrations is null ||
|
|
registrations.Count is 0)
|
|
{
|
|
object? resolvedParam = null;
|
|
foreach (var resolver in this.Resolvers)
|
|
{
|
|
if (resolver.CanResolve(par.ParameterType))
|
|
{
|
|
resolvedParam = resolver.Resolve(this, par.ParameterType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (resolvedParam is not null)
|
|
{
|
|
parameterImplementationList[index] = resolvedParam;
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
(var actualType, var lifetime) = registrations.LastOrDefault();
|
|
if (this.Instances.TryGetValue(par.ParameterType, out var instances) &&
|
|
instances is not null &&
|
|
instances.Count > 0)
|
|
{
|
|
parameterImplementationList[index] = instances.LastOrDefault();
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
if (this.Instances.TryGetValue(actualType, out var instances2) &&
|
|
instances2 is not null &&
|
|
instances2.Count > 0)
|
|
{
|
|
parameterImplementationList[index] = instances2.LastOrDefault();
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
var obj = this.TryImplementService(par.ParameterType);
|
|
if (obj is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (lifetime is Lifetime.Singleton or Lifetime.Scoped)
|
|
{
|
|
if (!this.Instances.TryGetValue(actualType, out var actualRegistrations) ||
|
|
actualRegistrations is null)
|
|
{
|
|
actualRegistrations = [];
|
|
this.Instances[actualType] = actualRegistrations;
|
|
}
|
|
|
|
actualRegistrations.Add(obj);
|
|
}
|
|
|
|
parameterImplementationList[index] = obj;
|
|
index++;
|
|
}
|
|
|
|
return parameterImplementationList;
|
|
}
|
|
|
|
private ServiceManager CreateScopeInner()
|
|
{
|
|
var interfaceMapping = new Dictionary<Type, List<(Type, Lifetime)>>(this.InterfaceMapping);
|
|
var factories = new Dictionary<Type, List<Delegate>>();
|
|
var exceptionHandlers = new Dictionary<Type, Delegate>(this.ExceptionHandlers);
|
|
var resolvers = new List<IDependencyResolver>(this.Resolvers);
|
|
/*
|
|
* For singletons, create a factory in scoped service manager that should call this manager
|
|
* to retrieve the singleton.
|
|
* This means that if the singleton has not yet been created in this service manager,
|
|
* the scoped service manager will not create a new one but instead ask the original service manager to
|
|
* create the singleton instead.
|
|
*/
|
|
foreach (var kvp in this.InterfaceMapping)
|
|
{
|
|
if (kvp.Value.Count is 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach(var registration in kvp.Value)
|
|
{
|
|
/*
|
|
* Singletons reference the original service manager while
|
|
* scoped or transient will reference the original factories.
|
|
*/
|
|
if (registration.Item2 is Lifetime.Singleton)
|
|
{
|
|
if (!factories.TryGetValue(kvp.Key, out var factoryRegistrations) ||
|
|
factoryRegistrations is null)
|
|
{
|
|
factoryRegistrations = [];
|
|
factories[kvp.Key] = factoryRegistrations;
|
|
}
|
|
|
|
factoryRegistrations.Add(new Func<IServiceProvider, object>((scopedManager) => this.ResolveSingletonWithScopedManager((ServiceManager)scopedManager, kvp.Key, registration.Item1)));
|
|
}
|
|
else if (this.Factories.TryGetValue(kvp.Key, out var originalFactories))
|
|
{
|
|
if (!factories.TryGetValue(kvp.Key, out var factoryRegistrations) ||
|
|
factoryRegistrations is null)
|
|
{
|
|
factoryRegistrations = [];
|
|
factories[kvp.Key] = factoryRegistrations;
|
|
}
|
|
|
|
foreach(var factory in originalFactories)
|
|
{
|
|
factoryRegistrations.Add(factory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return new ServiceManager(
|
|
interfaceMapping: interfaceMapping,
|
|
singletons: [],
|
|
factories: factories,
|
|
exceptionHandlers: exceptionHandlers,
|
|
resolvers: resolvers,
|
|
parent: this,
|
|
isReadOnly: this.AllowScopedManagerModifications is false);
|
|
}
|
|
|
|
private object ResolveSingletonWithScopedManager(ServiceManager scopedManager, Type registerType, Type implementedType)
|
|
{
|
|
try
|
|
{
|
|
if (this.PrepareAndGetService(registerType) is not object obj)
|
|
{
|
|
throw new DependencyInjectionException($"Unable to resolve singleton of type {registerType.Name}.");
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
catch (DependencyInjectionException)
|
|
{
|
|
if (scopedManager.IsReadOnly)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
/*
|
|
* Most probably scoped manager has been modified and contains different definitions
|
|
* than the parent manager.
|
|
* As a last ditch effort, let the scoped provider try to implement the singleton.
|
|
* If successful, update the parent provider as well.
|
|
*/
|
|
var obj = scopedManager.FindAndCallConstructors(implementedType);
|
|
if (obj is not null)
|
|
{
|
|
if (!scopedManager.Instances.TryGetValue(implementedType, out var scopedInstances) ||
|
|
scopedInstances is null)
|
|
{
|
|
scopedInstances = [];
|
|
scopedManager.Instances[implementedType] = scopedInstances;
|
|
}
|
|
|
|
scopedInstances.Add(obj);
|
|
|
|
if (!this.Instances.TryGetValue(implementedType, out var instances) ||
|
|
instances is null)
|
|
{
|
|
instances = [];
|
|
this.Instances[implementedType] = instances;
|
|
}
|
|
|
|
instances.Add(obj);
|
|
return obj;
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private void TryAction(Action action)
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.HandleException(ex);
|
|
}
|
|
}
|
|
|
|
private T? TryFunc<T>(Func<T> action)
|
|
{
|
|
try
|
|
{
|
|
return action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.HandleException(ex);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
|
|
private void HandleException(Exception exception)
|
|
{
|
|
if (exception is TargetInvocationException && exception.InnerException is not null)
|
|
{
|
|
exception = exception.InnerException;
|
|
}
|
|
|
|
if (this.ExceptionHandlers.TryGetValue(exception.GetType(), out var handler))
|
|
{
|
|
var shouldThrow = (bool)handler.DynamicInvoke(this, exception);
|
|
if (shouldThrow is false)
|
|
{
|
|
throw exception;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (!this.disposedValue)
|
|
{
|
|
if (disposing)
|
|
{
|
|
/*
|
|
* Dispose of all instances. If this instance of ServiceManager is scoped, ignore the Singleton instances as they are shared
|
|
* across multiple service managers.
|
|
*/
|
|
foreach(var kvp in this.InterfaceMapping)
|
|
{
|
|
foreach((var type, var lifetime) in kvp.Value)
|
|
{
|
|
if (lifetime is Lifetime.Scoped ||
|
|
this.scoped is false && lifetime is Lifetime.Singleton)
|
|
{
|
|
if (this.Instances.TryGetValue(kvp.Key, out var instances1))
|
|
{
|
|
foreach(var instance in instances1.OfType<IDisposable>())
|
|
{
|
|
instance.Dispose();
|
|
}
|
|
}
|
|
else if (this.Instances.TryGetValue(type, out var instances2))
|
|
{
|
|
foreach(var instance in instances2.OfType<IDisposable>())
|
|
{
|
|
instance.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.InterfaceMapping.Clear();
|
|
this.Instances.Clear();
|
|
this.Resolvers.Clear();
|
|
this.ExceptionHandlers.Clear();
|
|
this.Factories.Clear();
|
|
this.disposedValue = true;
|
|
}
|
|
}
|
|
|
|
private static bool IsServiceManagerDependency(Type type)
|
|
{
|
|
return type == typeof(IServiceManager) ||
|
|
type == typeof(IServiceProducer) ||
|
|
type == typeof(IServiceProvider) ||
|
|
type == typeof(ServiceManager) ||
|
|
type == typeof(System.IServiceProvider);
|
|
}
|
|
}
|