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:
2021-10-25 14:26:05 +02:00
parent 4ce4d7fd5b
commit b70853b22a
4 changed files with 128 additions and 12 deletions
+49 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>