diff --git a/Slim.Tests/Models/DependentOnPrivateCtrService.cs b/Slim.Tests/Models/DependentOnPrivateCtrService.cs new file mode 100644 index 0000000..f09e222 --- /dev/null +++ b/Slim.Tests/Models/DependentOnPrivateCtrService.cs @@ -0,0 +1,13 @@ +namespace Slim.Tests.Models +{ + public sealed class DependentOnPrivateCtrService : IDependentOnPrivateCtrService + { + private readonly IPrivateConstructorService privateConstructorService; + + public DependentOnPrivateCtrService( + IPrivateConstructorService privateConstructorService) + { + this.privateConstructorService = privateConstructorService; + } + } +} diff --git a/Slim.Tests/Models/DependentOnThrowingService.cs b/Slim.Tests/Models/DependentOnThrowingService.cs new file mode 100644 index 0000000..26f6b24 --- /dev/null +++ b/Slim.Tests/Models/DependentOnThrowingService.cs @@ -0,0 +1,12 @@ +namespace Slim.Tests.Models +{ + public sealed class DependentOnThrowingService : IDependentOnThrowingService + { + private readonly IThrowingService throwingService; + + public DependentOnThrowingService(IThrowingService throwingService) + { + this.throwingService = throwingService; + } + } +} diff --git a/Slim.Tests/Models/DependentService2.cs b/Slim.Tests/Models/DependentService2.cs new file mode 100644 index 0000000..4339803 --- /dev/null +++ b/Slim.Tests/Models/DependentService2.cs @@ -0,0 +1,12 @@ +namespace Slim.Tests.Models +{ + public sealed class DependentService2 : IDependentService2 + { + public IndependentService IndependentService { get; } + + public DependentService2(IndependentService independentService) + { + this.IndependentService = independentService; + } + } +} diff --git a/Slim.Tests/Models/IDependentOnPrivateCtrService.cs b/Slim.Tests/Models/IDependentOnPrivateCtrService.cs new file mode 100644 index 0000000..ff503b6 --- /dev/null +++ b/Slim.Tests/Models/IDependentOnPrivateCtrService.cs @@ -0,0 +1,6 @@ +namespace Slim.Tests.Models +{ + public interface IDependentOnPrivateCtrService + { + } +} diff --git a/Slim.Tests/Models/IDependentOnThrowingService.cs b/Slim.Tests/Models/IDependentOnThrowingService.cs new file mode 100644 index 0000000..c955b48 --- /dev/null +++ b/Slim.Tests/Models/IDependentOnThrowingService.cs @@ -0,0 +1,6 @@ +namespace Slim.Tests.Models +{ + public interface IDependentOnThrowingService + { + } +} diff --git a/Slim.Tests/Models/IDependentService2.cs b/Slim.Tests/Models/IDependentService2.cs new file mode 100644 index 0000000..853082b --- /dev/null +++ b/Slim.Tests/Models/IDependentService2.cs @@ -0,0 +1,7 @@ +namespace Slim.Tests.Models +{ + public interface IDependentService2 + { + public IndependentService IndependentService { get; } + } +} diff --git a/Slim.Tests/Models/IPrivateConstructorService.cs b/Slim.Tests/Models/IPrivateConstructorService.cs new file mode 100644 index 0000000..91a1c56 --- /dev/null +++ b/Slim.Tests/Models/IPrivateConstructorService.cs @@ -0,0 +1,6 @@ +namespace Slim.Tests.Models +{ + public interface IPrivateConstructorService + { + } +} diff --git a/Slim.Tests/Models/IThrowingService.cs b/Slim.Tests/Models/IThrowingService.cs new file mode 100644 index 0000000..189ad8e --- /dev/null +++ b/Slim.Tests/Models/IThrowingService.cs @@ -0,0 +1,6 @@ +namespace Slim.Tests.Models +{ + public interface IThrowingService + { + } +} diff --git a/Slim.Tests/Models/PrivateConstructorService.cs b/Slim.Tests/Models/PrivateConstructorService.cs new file mode 100644 index 0000000..4eb68b9 --- /dev/null +++ b/Slim.Tests/Models/PrivateConstructorService.cs @@ -0,0 +1,9 @@ +namespace Slim.Tests.Models +{ + public sealed class PrivateConstructorService : IPrivateConstructorService + { + private PrivateConstructorService() + { + } + } +} diff --git a/Slim.Tests/Models/ThrowingService.cs b/Slim.Tests/Models/ThrowingService.cs new file mode 100644 index 0000000..92c024f --- /dev/null +++ b/Slim.Tests/Models/ThrowingService.cs @@ -0,0 +1,12 @@ +using System; + +namespace Slim.Tests.Models +{ + public sealed class ThrowingService : IThrowingService + { + public ThrowingService() + { + throw new InvalidOperationException(); + } + } +} diff --git a/Slim.Tests/ServiceManagerTests.cs b/Slim.Tests/ServiceManagerTests.cs index 76ea1a7..b97c7ca 100644 --- a/Slim.Tests/ServiceManagerTests.cs +++ b/Slim.Tests/ServiceManagerTests.cs @@ -10,6 +10,66 @@ namespace Slim.Tests [TestClass] public class ServiceManagerTests { + [TestMethod] + public void RegisterAndRetrieveServiceUsesExistingImplementationSingleton() + { + var di = new ServiceManager(); + di.RegisterSingleton(); + di.RegisterSingleton(); + + var independentService = di.GetService(); + independentService.Should().NotBeNull(); + + var dependentService = di.GetService(); + dependentService.Should().NotBeNull(); + + dependentService.IndependentService.Should().Be(independentService); + } + + [TestMethod] + public void DependencyOnPrivateConstructorServiceThrows() + { + var di = new ServiceManager(); + di.RegisterSingleton(); + di.RegisterSingleton(); + + var action = new Action(() => + { + di.GetService(); + }); + + action.Should().Throw(); + } + + [TestMethod] + public void DependencyOnThrowingServiceThrows() + { + var di = new ServiceManager(); + di.RegisterSingleton(); + di.RegisterSingleton(); + + var action = new Action(() => + { + di.GetService(); + }); + + action.Should().Throw(); + } + + [TestMethod] + public void ThrowingConstructorThrows() + { + var di = new ServiceManager(); + di.RegisterSingleton(); + + var action = new Action(() => + { + di.GetService(); + }); + + action.Should().Throw(); + } + [TestMethod] public void RetrieveThrowsNotRegistered() { @@ -215,7 +275,7 @@ namespace Slim.Tests di.HandleException((sp, e) => { thrown = true; - return false; + return true; }); di.RegisterSingleton(); @@ -232,7 +292,7 @@ namespace Slim.Tests di.HandleException((sp, e) => { thrown = true; - return true; + return false; }); di.RegisterSingleton(); @@ -331,6 +391,18 @@ namespace Slim.Tests resolver.Called.Should().Be(1); } + [TestMethod] + public void UseResolverWithoutRegisteringServiceCallsResolver() + { + var di = new ServiceManager(); + var resolver = new IndependentServiceResolver(); + di.RegisterResolver(resolver); + + var service = di.GetService(); + service.Should().NotBeNull(); + service.Should().BeOfType(); + } + [TestMethod] public void UseResolverCalledOnceForSingleton() { diff --git a/Slim/ServiceManager.cs b/Slim/ServiceManager.cs index 6cd72f3..dace3bf 100644 --- a/Slim/ServiceManager.cs +++ b/Slim/ServiceManager.cs @@ -63,7 +63,10 @@ namespace Slim where TInterface : class where TClass : TInterface { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Transient, typeof(TInterface), serviceFactory); } @@ -91,7 +94,10 @@ namespace Slim where TInterface : class where TClass : TInterface { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Singleton, typeof(TInterface), serviceFactory); } @@ -119,7 +125,10 @@ namespace Slim where TInterface : class where TClass : TInterface { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Scoped, typeof(TInterface), serviceFactory); } @@ -143,7 +152,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterTransient(Type tInterface, Type tClass, Func serviceFactory) { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(tClass, Lifetime.Transient, tInterface, serviceFactory); } @@ -167,7 +179,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterSingleton(Type tInterface, Type tClass, Func serviceFactory) { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(tClass, Lifetime.Singleton, tInterface, serviceFactory); } @@ -191,7 +206,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterScoped(Type tInterface, Type tClass, Func serviceFactory) { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(tClass, Lifetime.Scoped, tInterface, serviceFactory); } @@ -215,7 +233,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterSingleton(Func serviceFactory) where TClass : class { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Singleton, null, serviceFactory); } @@ -239,7 +260,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterTransient(Func serviceFactory) where TClass : class { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Transient, null, serviceFactory); } @@ -263,7 +287,10 @@ namespace Slim /// Thrown when contains an entry for the provided interface type. public void RegisterScoped(Func serviceFactory) where TClass : class { - if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory)); + if (serviceFactory is null) + { + throw new ArgumentNullException(nameof(serviceFactory)); + } this.RegisterService(typeof(TClass), Lifetime.Scoped, null, serviceFactory); } @@ -412,6 +439,7 @@ namespace Slim { singleton.Dispose(); } + this.Factories.Clear(); this.ExceptionHandlers.Clear(); } @@ -478,58 +506,24 @@ namespace Slim } private object PrepareAndGetService(Type tInterface) { - try + return this.TryFunc(() => { - lock (this) - { - return this.GetObject(tInterface); - } - } - catch(Exception e) - { - if (this.ExceptionHandlers.TryGetValue(e.GetType(), out var handler)) - { - var shouldThrow = (bool)handler.DynamicInvoke(this, e); - if (shouldThrow) - { - throw; - } - } - else - { - throw; - } - return null; - } + return this.GetObject(tInterface); + }); } private void RegisterFactory(Type tinterface, Delegate factory) { - try + this.TryAction(() => { lock (this) { this.Factories[tinterface] = factory; } - } - catch(Exception e) - { - if (this.ExceptionHandlers.TryGetValue(e.GetType(), out var handler)) - { - var shouldThrow = (bool)handler.DynamicInvoke(this, e); - if (shouldThrow) - { - throw; - } - } - else - { - throw; - } - } + }); } private void Map(Type tinterface, Type tclass, Lifetime lifetime) { - try + this.TryAction(() => { lock (this) { @@ -537,24 +531,10 @@ namespace Slim { throw new InvalidOperationException($"{nameof(ServiceManager)} already contains an entry for type {tinterface}"); } + this.InterfaceMapping[tinterface] = (tclass, lifetime); } - } - catch(Exception e) - { - if (this.ExceptionHandlers.TryGetValue(e.GetType(), out var handler)) - { - var shouldThrow = (bool)handler.DynamicInvoke(this, e); - if (shouldThrow) - { - throw; - } - } - else - { - throw; - } - } + }); } private object GetObject(Type tInterface) { @@ -584,6 +564,7 @@ namespace Slim { this.Instances[type] = obj; } + return obj; } @@ -603,7 +584,7 @@ namespace Slim } } - (var implementType, _) = InterfaceMapping[type]; + (var implementType, _) = this.InterfaceMapping[type]; if (this.Factories.TryGetValue(type, out var factory)) { return factory.DynamicInvoke(this); @@ -619,25 +600,13 @@ namespace Slim * If the parameters are missing, try with other constructors. */ - if (!constructor.IsPublic) + var parameters = this.GetParameters(constructor.GetParameters()); + if (parameters is null) { continue; } - try - { - var parameters = this.GetParameters(constructor.GetParameters()); - if (parameters is null) - { - continue; - } - - return constructor.Invoke(parameters); - } - catch(DependencyInjectionException exception) - { - throw new DependencyInjectionException($"Could not instantiate service of type {implementType}!", exception); - } + return constructor.Invoke(parameters); } return null; @@ -697,19 +666,6 @@ namespace Slim return parameterImplementationList.ToArray(); } - private Dictionary GetSingletons() - { - var dictionary = new Dictionary(); - foreach (var kvp in this.Instances) - { - if (this.InterfaceMapping.First(s => s.Value.Item1 == kvp.Key).Value.Item2 is Lifetime.Singleton) - { - dictionary.Add(kvp.Key, kvp.Value); - } - } - - return dictionary; - } private ServiceManager CreateScopeInner() { var interfaceMapping = new Dictionary(this.InterfaceMapping); @@ -733,5 +689,49 @@ namespace Slim return new ServiceManager(interfaceMapping, new Dictionary(), factories, exceptionHandlers, resolvers); } + private void TryAction(Action action) + { + try + { + action(); + } + catch (Exception ex) + { + this.HandleException(ex); + } + } + private T TryFunc(Func 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; + } + } } } \ No newline at end of file diff --git a/Slim/Slim.csproj b/Slim/Slim.csproj index 9013b29..41f0fe7 100644 --- a/Slim/Slim.csproj +++ b/Slim/Slim.csproj @@ -8,10 +8,10 @@ Service lifetime management and dependency injection framework. - 1.5.1 - 1.5.1 + 1.5.2 + 1.5.2 https://github.com/AlexMacocian/Slim - 1.5.1 + 1.5.2 true true LICENSE