mirror of
https://github.com/AlexMacocian/Slim.git
synced 2026-07-15 15:19:59 +00:00
Introduced ReadOnly for scoped managers.
Ability to override IsReadOnly when creating scope. Introduced method to deal with singletons when scoped manager implements them instead of master manager.
This commit is contained in:
@@ -534,7 +534,7 @@ namespace Slim.Tests
|
||||
[TestMethod]
|
||||
public void ScopedManagerShouldHaveItsOwnLists()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
var di = new ServiceManager() { AllowScopedManagerModifications = true };
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
var scopedDi = di.CreateScope();
|
||||
|
||||
@@ -668,5 +668,53 @@ namespace Slim.Tests
|
||||
isRegistered1.Should().BeFalse();
|
||||
isRegistered2.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ScopedServiceManagerWithDisallowedModificationsIsReadOnly()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
var scopedDi1 = di.CreateScope() as ServiceManager;
|
||||
|
||||
scopedDi1.IsReadOnly.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ScopedServiceManagerWithAllowedModificationsIsNotReadOnly()
|
||||
{
|
||||
var di = new ServiceManager() { AllowScopedManagerModifications = true };
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
var scopedDi1 = di.CreateScope() as ServiceManager;
|
||||
|
||||
scopedDi1.IsReadOnly.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ModifyingScopedManagerThrowsInvalidOperationException()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
var scopedDi1 = di.CreateScope() as ServiceManager;
|
||||
|
||||
var action = new Action(() => scopedDi1.RegisterSingleton<IIndependentService, IndependentService>());
|
||||
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ModifyingScopedManagerStillResolvesSingleton()
|
||||
{
|
||||
var di = new ServiceManager() { AllowScopedManagerModifications = true };
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
var scopedDi1 = di.CreateScope() as ServiceManager;
|
||||
var scopedDi2 = di.CreateScope() as ServiceManager;
|
||||
scopedDi1.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
|
||||
var dependentService1 = scopedDi1.GetService<IDependentService>();
|
||||
var dependentService2 = scopedDi2.GetService<IDependentService>();
|
||||
|
||||
dependentService1.Should().Be(dependentService2);
|
||||
dependentService1.IndependentService.Should().Be(dependentService2.IndependentService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -9,28 +9,36 @@ namespace Slim
|
||||
/// </summary>
|
||||
public interface IServiceManager : IServiceProvider, IServiceProducer
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow modifications to scoped <see cref="IServiceManager"/> created from this <see cref="IServiceManager"/>.
|
||||
/// </summary>
|
||||
bool AllowScopedManagerModifications { get; set; }
|
||||
/// <summary>
|
||||
/// Returns true if <see cref="IServiceManager"/> is readonly.
|
||||
/// </summary>
|
||||
bool IsReadOnly { get; }
|
||||
/// <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);
|
||||
bool IsRegistered(Type 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>();
|
||||
bool IsRegistered<T>();
|
||||
/// <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>();
|
||||
IEnumerable<T> GetServicesOfType<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);
|
||||
IEnumerable<object> GetServicesOfType(Type type);
|
||||
/// <summary>
|
||||
/// Register the current service manager as a valid dependency.
|
||||
/// </summary>
|
||||
|
||||
+64
-4
@@ -21,6 +21,15 @@ namespace Slim
|
||||
private Dictionary<Type, Delegate> ExceptionHandlers { get; } = new Dictionary<Type, Delegate>();
|
||||
private List<IDependencyResolver> Resolvers { get; } = new List<IDependencyResolver>();
|
||||
|
||||
/// <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>
|
||||
/// Creates an instance of <see cref="IServiceManager"/>.
|
||||
/// </summary>
|
||||
@@ -33,7 +42,8 @@ namespace Slim
|
||||
Dictionary<Type, object> singletons,
|
||||
Dictionary<Type, Delegate> factories,
|
||||
Dictionary<Type, Delegate> exceptionHandlers,
|
||||
List<IDependencyResolver> resolvers)
|
||||
List<IDependencyResolver> resolvers,
|
||||
bool isReadOnly)
|
||||
{
|
||||
this.InterfaceMapping = interfaceMapping;
|
||||
this.Instances = singletons;
|
||||
@@ -41,6 +51,7 @@ namespace Slim
|
||||
this.ExceptionHandlers = exceptionHandlers;
|
||||
this.Resolvers = resolvers;
|
||||
this.scoped = true;
|
||||
this.IsReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -471,6 +482,11 @@ namespace Slim
|
||||
/// </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.Where(s => s is IDisposable).Select(s => (IDisposable)s))
|
||||
{
|
||||
@@ -499,6 +515,11 @@ namespace Slim
|
||||
/// <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>
|
||||
@@ -522,6 +543,11 @@ namespace Slim
|
||||
}
|
||||
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);
|
||||
@@ -615,7 +641,7 @@ namespace Slim
|
||||
obj = this.TryImplementService(tInterface);
|
||||
}
|
||||
|
||||
if (obj is object)
|
||||
if (obj is not null)
|
||||
{
|
||||
if (lifetime == Lifetime.Singleton || lifetime == Lifetime.Scoped)
|
||||
{
|
||||
@@ -647,6 +673,10 @@ namespace Slim
|
||||
return factory.DynamicInvoke(this);
|
||||
}
|
||||
|
||||
return this.FindAndCallConstructors(implementType);
|
||||
}
|
||||
private object FindAndCallConstructors(Type implementType)
|
||||
{
|
||||
var constructors = implementType.GetConstructors();
|
||||
foreach (var constructor in constructors)
|
||||
{
|
||||
@@ -740,11 +770,41 @@ namespace Slim
|
||||
{
|
||||
if (kvp.Value.Item2 is Lifetime.Singleton)
|
||||
{
|
||||
factories[kvp.Key] = new Func<IServiceProvider, object>((_) => this.PrepareAndGetService(kvp.Key));
|
||||
factories[kvp.Key] = new Func<IServiceProvider, object>((scopedManager) => this.ResolveSingletonWithScopedManager(scopedManager as ServiceManager, kvp.Key, kvp.Value.Item1));
|
||||
}
|
||||
}
|
||||
|
||||
return new ServiceManager(interfaceMapping, new Dictionary<Type, object>(), factories, exceptionHandlers, resolvers);
|
||||
return new ServiceManager(interfaceMapping, new Dictionary<Type, object>(), factories, exceptionHandlers, resolvers, this.AllowScopedManagerModifications is false);
|
||||
}
|
||||
private object ResolveSingletonWithScopedManager(ServiceManager scopedManager, Type registerType, Type implementedType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.PrepareAndGetService(registerType);
|
||||
}
|
||||
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)
|
||||
{
|
||||
scopedManager.Instances[implementedType] = obj;
|
||||
this.Instances[implementedType] = obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
private void TryAction(Action action)
|
||||
{
|
||||
|
||||
+3
-3
@@ -8,10 +8,10 @@
|
||||
<Company />
|
||||
<Product></Product>
|
||||
<Description>Service lifetime management and dependency injection framework.</Description>
|
||||
<AssemblyVersion>1.5.5</AssemblyVersion>
|
||||
<FileVersion>1.5.5</FileVersion>
|
||||
<AssemblyVersion>1.6</AssemblyVersion>
|
||||
<FileVersion>1.6</FileVersion>
|
||||
<PackageProjectUrl>https://github.com/AlexMacocian/Slim</PackageProjectUrl>
|
||||
<Version>1.5.5</Version>
|
||||
<Version>1.6</Version>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
|
||||
Reference in New Issue
Block a user