mirror of
https://github.com/AlexMacocian/Slim.git
synced 2026-07-15 15:19:59 +00:00
Add project files.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
namespace Slim.Tests.Models
|
||||
{
|
||||
public class DependentService : IDependentService
|
||||
{
|
||||
public DependentService(IIndependentService independentService)
|
||||
{
|
||||
this.IndependentService = independentService;
|
||||
}
|
||||
|
||||
public IIndependentService IndependentService { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Slim.Tests.Models
|
||||
{
|
||||
public interface IDependentService
|
||||
{
|
||||
IIndependentService IndependentService { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Slim.Tests.Models
|
||||
{
|
||||
public interface IIndependentService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Slim.Tests.Models
|
||||
{
|
||||
public class IndependentService : IIndependentService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Slim.Exceptions;
|
||||
using Slim.Tests.Models;
|
||||
|
||||
namespace Slim.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void RetrieveThrowsNotRegistered()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
Assert.ThrowsException<DependencyInjectionException>(() => { di.GetService<IDependentService>(); });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RegisterAndRetrieveThrowsNoSuitableConstructorFound()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
Assert.ThrowsException<DependencyInjectionException>(() => { di.GetService<IDependentService>(); });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RegisterMultipleServices()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RegisterAndRetrieveService()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IDependentService, DependentService>();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
|
||||
var dependentService = di.GetService<IDependentService>();
|
||||
dependentService.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TransientAreDifferent()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterTransient<IIndependentService, IndependentService>();
|
||||
|
||||
var independentService = di.GetService<IIndependentService>();
|
||||
var independentService2 = di.GetService<IIndependentService>();
|
||||
|
||||
independentService.Should().NotBeSameAs(independentService2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SingletonAreTheSame()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
|
||||
var independentService = di.GetService<IIndependentService>();
|
||||
var independentService2 = di.GetService<IIndependentService>();
|
||||
|
||||
independentService.Should().BeSameAs(independentService2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RegisterServiceWithFactoryAndRetrieve()
|
||||
{
|
||||
var calledFactory = false;
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
di.RegisterSingleton<IDependentService, DependentService>(sp =>
|
||||
{
|
||||
calledFactory = true;
|
||||
return new DependentService(sp.GetService<IIndependentService>());
|
||||
});
|
||||
|
||||
var dependentService = di.GetService<IDependentService>();
|
||||
|
||||
dependentService.Should().NotBeNull();
|
||||
calledFactory.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TransientDependentAndSingletonIndependentRespectLifetime()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
di.RegisterTransient<IDependentService, DependentService>();
|
||||
|
||||
var independentService = di.GetService<IIndependentService>();
|
||||
var dependentService = di.GetService<IDependentService>();
|
||||
var dependentService2 = di.GetService<IDependentService>();
|
||||
|
||||
dependentService.Should().NotBeSameAs(dependentService2);
|
||||
dependentService.IndependentService.Should().BeSameAs(dependentService2.IndependentService);
|
||||
independentService.Should().BeSameAs(dependentService.IndependentService);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TransientDependentWithFactoryAndSingletonIndependentRespectLifetime()
|
||||
{
|
||||
var di = new ServiceManager();
|
||||
di.RegisterSingleton<IIndependentService, IndependentService>();
|
||||
di.RegisterTransient<IDependentService, DependentService>(sp => new DependentService(sp.GetService<IIndependentService>()));
|
||||
|
||||
var independentService = di.GetService<IIndependentService>();
|
||||
var dependentService = di.GetService<IDependentService>();
|
||||
var dependentService2 = di.GetService<IDependentService>();
|
||||
|
||||
dependentService.Should().NotBeSameAs(dependentService2);
|
||||
dependentService.IndependentService.Should().BeSameAs(dependentService2.IndependentService);
|
||||
independentService.Should().BeSameAs(dependentService.IndependentService);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.0.0-alpha0001" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Slim\Slim.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30104.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Slim", "Slim\Slim.csproj", "{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Slim.Tests", "Slim.Tests\Slim.Tests.csproj", "{F03F7353-0BD5-4691-A318-BC6FC93AB00E}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088} = {04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04F435A2-BEF9-4F49-9D1A-EC1AEFCC3088}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F03F7353-0BD5-4691-A318-BC6FC93AB00E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F03F7353-0BD5-4691-A318-BC6FC93AB00E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F03F7353-0BD5-4691-A318-BC6FC93AB00E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F03F7353-0BD5-4691-A318-BC6FC93AB00E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7AFB862F-628C-42A2-8499-A9C28E3BBBA7}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace Slim.Exceptions
|
||||
{
|
||||
public class DependencyInjectionException : Exception
|
||||
{
|
||||
public DependencyInjectionException()
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyInjectionException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyInjectionException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected DependencyInjectionException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Slim
|
||||
{
|
||||
public interface IServiceManager : IServiceProvider
|
||||
{
|
||||
void RegisterTransient<TInterface, TClass>()
|
||||
where TInterface : class
|
||||
where TClass : TInterface;
|
||||
|
||||
void RegisterTransient<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
||||
where TInterface : class
|
||||
where TClass : TInterface;
|
||||
|
||||
void RegisterSingleton<TInterface, TClass>()
|
||||
where TInterface : class
|
||||
where TClass : TInterface;
|
||||
|
||||
void RegisterSingleton<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
||||
where TInterface : class
|
||||
where TClass : TInterface;
|
||||
|
||||
void RegisterTransient(Type tInterface, Type tClass);
|
||||
|
||||
void RegisterTransient(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory);
|
||||
|
||||
void RegisterSingleton(Type tInterface, Type tClass);
|
||||
|
||||
void RegisterSingleton<TInterface, TClass>(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Slim
|
||||
{
|
||||
public interface IServiceProvider
|
||||
{
|
||||
TInterface GetService<TInterface>() where TInterface : class;
|
||||
object GetService(Type type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Slim
|
||||
{
|
||||
internal enum Lifetime
|
||||
{
|
||||
Transient,
|
||||
Singleton
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using Slim.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Slim
|
||||
{
|
||||
public class ServiceManager : IServiceManager, IServiceProvider
|
||||
{
|
||||
private Dictionary<Type, (Type, Lifetime)> InterfaceMapping { get; } = new Dictionary<Type, (Type, Lifetime)>();
|
||||
private Dictionary<Type, object> Singletons { get; } = new Dictionary<Type, object>();
|
||||
private Dictionary<Type, Delegate> Factories { get; } = new Dictionary<Type, Delegate>();
|
||||
|
||||
public void RegisterTransient<TInterface, TClass>()
|
||||
where TInterface : class
|
||||
where TClass : TInterface
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[typeof(TInterface)] = (typeof(TClass), Lifetime.Transient);
|
||||
}
|
||||
}
|
||||
public void RegisterTransient<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
||||
where TInterface : class
|
||||
where TClass : TInterface
|
||||
{
|
||||
if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory));
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[typeof(TInterface)] = (typeof(TClass), Lifetime.Transient);
|
||||
this.Factories[typeof(TInterface)] = serviceFactory;
|
||||
}
|
||||
}
|
||||
public void RegisterSingleton<TInterface, TClass>()
|
||||
where TInterface : class
|
||||
where TClass : TInterface
|
||||
{
|
||||
this.InterfaceMapping[typeof(TInterface)] = (typeof(TClass), Lifetime.Singleton);
|
||||
}
|
||||
public void RegisterSingleton<TInterface, TClass>(Func<IServiceProvider, TClass> serviceFactory)
|
||||
where TInterface : class
|
||||
where TClass : TInterface
|
||||
{
|
||||
if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory));
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[typeof(TInterface)] = (typeof(TClass), Lifetime.Singleton);
|
||||
this.Factories[typeof(TInterface)] = serviceFactory;
|
||||
}
|
||||
}
|
||||
public void RegisterTransient(Type tInterface, Type tClass)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[tInterface] = (tClass, Lifetime.Transient);
|
||||
}
|
||||
}
|
||||
public void RegisterTransient(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory)
|
||||
{
|
||||
if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory));
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[tInterface] = (tClass, Lifetime.Transient);
|
||||
this.Factories[tInterface] = serviceFactory;
|
||||
}
|
||||
}
|
||||
public void RegisterSingleton(Type tInterface, Type tClass)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[tInterface] = (tClass, Lifetime.Singleton);
|
||||
}
|
||||
}
|
||||
public void RegisterSingleton<TInterface, TClass>(Type tInterface, Type tClass, Func<IServiceProvider, object> serviceFactory)
|
||||
{
|
||||
if (serviceFactory is null) throw new ArgumentNullException(nameof(serviceFactory));
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.InterfaceMapping[tInterface] = (tClass, Lifetime.Singleton);
|
||||
this.Factories[tInterface] = serviceFactory;
|
||||
}
|
||||
}
|
||||
|
||||
public TInterface GetService<TInterface>() where TInterface : class
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return GetObject(typeof(TInterface)) as TInterface;
|
||||
}
|
||||
}
|
||||
public object GetService(Type type)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
return GetObject(type);
|
||||
}
|
||||
}
|
||||
|
||||
private object GetObject(Type tInterface)
|
||||
{
|
||||
if (!this.InterfaceMapping.TryGetValue(tInterface, out var mappingTuple))
|
||||
{
|
||||
throw new DependencyInjectionException($"No registered service for type {tInterface.Name}.");
|
||||
}
|
||||
|
||||
(var type, var lifetime) = mappingTuple;
|
||||
object obj = null;
|
||||
if (lifetime == Lifetime.Singleton)
|
||||
{
|
||||
if (!this.Singletons.TryGetValue(tInterface, out obj))
|
||||
{
|
||||
obj = TryImplementService(tInterface);
|
||||
this.Singletons[tInterface] = obj;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = TryImplementService(tInterface);
|
||||
}
|
||||
|
||||
if (obj is object)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* If no constructors were able to be called succesfully, throw exception.
|
||||
*/
|
||||
|
||||
throw new DependencyInjectionException($"No suitable constructor was found for type {tInterface.Name}");
|
||||
}
|
||||
private object TryImplementService(Type type)
|
||||
{
|
||||
(var implementType, _) = InterfaceMapping[type];
|
||||
if (this.Factories.TryGetValue(type, out var factory))
|
||||
{
|
||||
return factory.DynamicInvoke(this);
|
||||
}
|
||||
|
||||
var constructors = implementType.GetConstructors();
|
||||
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.
|
||||
*/
|
||||
|
||||
if (!constructor.IsPublic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = GetParameters(constructor.GetParameters());
|
||||
if (parameters is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return constructor.Invoke(parameters);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private object[] GetParameters(ParameterInfo[] parameterInfos)
|
||||
{
|
||||
var parameterImplementationList = new List<object>();
|
||||
foreach (var par in parameterInfos)
|
||||
{
|
||||
if (!InterfaceMapping.TryGetValue(par.ParameterType, out var mappingTuple))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
(_, var lifetime) = mappingTuple;
|
||||
if (!Singletons.TryGetValue(par.ParameterType, out var obj))
|
||||
{
|
||||
obj = TryImplementService(par.ParameterType);
|
||||
}
|
||||
|
||||
if (obj is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lifetime == Lifetime.Singleton)
|
||||
{
|
||||
this.Singletons[par.ParameterType] = obj;
|
||||
}
|
||||
|
||||
parameterImplementationList.Add(obj);
|
||||
}
|
||||
|
||||
return parameterImplementationList.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user