Add project files.

This commit is contained in:
Alexandru Macocian
2020-11-12 13:03:56 +01:00
parent 2b6ad643c7
commit 1cd4c47b09
13 changed files with 489 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
namespace Slim.Tests.Models
{
public class DependentService : IDependentService
{
public DependentService(IIndependentService independentService)
{
this.IndependentService = independentService;
}
public IIndependentService IndependentService { get; }
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Slim.Tests.Models
{
public interface IDependentService
{
IIndependentService IndependentService { get; }
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Slim.Tests.Models
{
public interface IIndependentService
{
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Slim.Tests.Models
{
public class IndependentService : IIndependentService
{
}
}
+119
View File
@@ -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);
}
}
}
+21
View File
@@ -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>
+34
View File
@@ -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)
{
}
}
}
+31
View File
@@ -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);
}
}
+10
View File
@@ -0,0 +1,10 @@
using System;
namespace Slim
{
public interface IServiceProvider
{
TInterface GetService<TInterface>() where TInterface : class;
object GetService(Type type);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Slim
{
internal enum Lifetime
{
Transient,
Singleton
}
}
+202
View File
@@ -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();
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>