mirror of
https://github.com/adusak/Dependify.git
synced 2026-01-15 21:53:40 +01:00
Initial commit of Dependify
This commit is contained in:
67
src/Dependify.Test/AutoRegisterTestCases.cs
Normal file
67
src/Dependify.Test/AutoRegisterTestCases.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Dependify;
|
||||
using Dependify.Attributes;
|
||||
using ShouldRegisterScoped;
|
||||
using ShouldRegisterSingleton;
|
||||
using ShouldRegisterTransient;
|
||||
|
||||
public interface IInterface { }
|
||||
|
||||
public interface IInterface2 { }
|
||||
|
||||
namespace ShouldRegisterTransient {
|
||||
[RegisterTransient]
|
||||
public class ImplementationTransient : IInterface { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterScoped {
|
||||
[RegisterScoped]
|
||||
public class ImplementationScoped : IInterface { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterSingleton {
|
||||
[RegisterSingleton]
|
||||
public class ImplementationSingleton : IInterface { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterOneTransient {
|
||||
[RegisterTransient(typeof(IInterface2))]
|
||||
public class ImplementationTransientOneInterface : IInterface, IInterface2 { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterOneScoped {
|
||||
[RegisterScoped(typeof(IInterface2))]
|
||||
public class ImplementationScopedOneInterface : IInterface, IInterface2 { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterOneSingleton {
|
||||
[RegisterSingleton(typeof(IInterface2))]
|
||||
public class ImplementationSingletonOneInterface : IInterface, IInterface2 { }
|
||||
}
|
||||
|
||||
namespace ShouldRegisterFactoryTransient {
|
||||
public class TransientFactory {
|
||||
[RegisterTransientFactory(typeof(IInterface))]
|
||||
public static ImplementationTransient CreateImplementationTransient(IServiceProvider provider) {
|
||||
return new ImplementationTransient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShouldRegisterFactoryScoped {
|
||||
public class ScopedFactory {
|
||||
[RegisterScopedFactory(typeof(IInterface))]
|
||||
public static ImplementationScoped CreateImplementationTransient(IServiceProvider provider) {
|
||||
return new ImplementationScoped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShouldRegisterFactorySingleton {
|
||||
public class SingletonFactory {
|
||||
[RegisterSingletonFactory(typeof(IInterface))]
|
||||
public static ImplementationSingleton CreateImplementationTransient(IServiceProvider provider) {
|
||||
return new ImplementationSingleton();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/Dependify.Test/AutoRegisterTests.cs
Normal file
45
src/Dependify.Test/AutoRegisterTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using ShouldRegisterOneScoped;
|
||||
using ShouldRegisterOneSingleton;
|
||||
using ShouldRegisterOneTransient;
|
||||
using ShouldRegisterScoped;
|
||||
using ShouldRegisterSingleton;
|
||||
using ShouldRegisterTransient;
|
||||
|
||||
namespace Dependify.Test {
|
||||
[TestFixture]
|
||||
public class RegisterAttributeTests {
|
||||
[TestCase(nameof(ShouldRegisterTransient), typeof(ImplementationTransient), typeof(IInterface), ServiceLifetime.Transient)]
|
||||
[TestCase(nameof(ShouldRegisterSingleton), typeof(ImplementationSingleton), typeof(IInterface), ServiceLifetime.Singleton)]
|
||||
[TestCase(nameof(ShouldRegisterScoped), typeof(ImplementationScoped), typeof(IInterface), ServiceLifetime.Scoped)]
|
||||
[TestCase(nameof(ShouldRegisterOneTransient), typeof(ImplementationTransientOneInterface), typeof(IInterface2), ServiceLifetime.Transient)]
|
||||
[TestCase(nameof(ShouldRegisterOneSingleton), typeof(ImplementationSingletonOneInterface), typeof(IInterface2), ServiceLifetime.Singleton)]
|
||||
[TestCase(nameof(ShouldRegisterOneScoped), typeof(ImplementationScopedOneInterface), typeof(IInterface2), ServiceLifetime.Scoped)]
|
||||
public void RegisterAttribute_RegistersClass_WhenDefined(string @namespace, Type classType, Type interfaceType, ServiceLifetime serviceLifetime) {
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
services.AutoRegister(@namespace);
|
||||
var service = services.First();
|
||||
Assert.AreEqual(1, services.Count);
|
||||
Assert.AreEqual(interfaceType, service.ServiceType);
|
||||
Assert.AreEqual(classType, service.ImplementationType);
|
||||
Assert.AreEqual(serviceLifetime, service.Lifetime);
|
||||
}
|
||||
|
||||
|
||||
[TestCase(nameof(ShouldRegisterFactoryTransient), typeof(ImplementationTransient), typeof(IInterface), ServiceLifetime.Transient)]
|
||||
[TestCase(nameof(ShouldRegisterFactorySingleton), typeof(ImplementationSingleton), typeof(IInterface), ServiceLifetime.Singleton)]
|
||||
[TestCase(nameof(ShouldRegisterFactoryScoped), typeof(ImplementationScoped), typeof(IInterface), ServiceLifetime.Scoped)]
|
||||
public void RegisterFactoryAttribute_RegistersFactoryForInterface_WhenDefined(string @namespace, Type classType, Type interfaceType, ServiceLifetime serviceLifetime) {
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
services.AutoRegister(@namespace);
|
||||
var service = services.First();
|
||||
Assert.AreEqual(1, services.Count);
|
||||
Assert.AreEqual(interfaceType, service.ServiceType);
|
||||
Assert.NotNull(service.ImplementationFactory);
|
||||
Assert.AreEqual(serviceLifetime, service.Lifetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Dependify.Test/Dependify.Test.csproj
Normal file
16
src/Dependify.Test/Dependify.Test.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dotnet-test-nunit" Version="3.4.0-beta-3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
|
||||
<PackageReference Include="NUnit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dependify\Dependify.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
15
src/Dependify/Attributes/Register.cs
Normal file
15
src/Dependify/Attributes/Register.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public abstract class Register : Attribute {
|
||||
public IEnumerable<Type> InterfaceTypes { get; }
|
||||
|
||||
protected Register() { }
|
||||
|
||||
protected Register(params Type[] interfaceTypes) {
|
||||
InterfaceTypes = interfaceTypes;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Dependify/Attributes/RegisterFactory.cs
Normal file
12
src/Dependify/Attributes/RegisterFactory.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public abstract class RegisterFactory : Attribute {
|
||||
public Type ReturnType { get; }
|
||||
|
||||
protected RegisterFactory(Type returnType) {
|
||||
ReturnType = returnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/Dependify/Attributes/RegisterScoped.cs
Normal file
10
src/Dependify/Attributes/RegisterScoped.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterScoped : Register {
|
||||
public RegisterScoped() { }
|
||||
|
||||
public RegisterScoped(params Type[] interfaceTypes) : base(interfaceTypes) { }
|
||||
}
|
||||
}
|
||||
8
src/Dependify/Attributes/RegisterScopedFactory.cs
Normal file
8
src/Dependify/Attributes/RegisterScopedFactory.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RegisterScopedFactory : RegisterFactory {
|
||||
public RegisterScopedFactory(Type returnType) : base(returnType) { }
|
||||
}
|
||||
}
|
||||
10
src/Dependify/Attributes/RegisterSingleton.cs
Normal file
10
src/Dependify/Attributes/RegisterSingleton.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterSingleton : Register {
|
||||
public RegisterSingleton() { }
|
||||
|
||||
public RegisterSingleton(params Type[] interfaceTypes) : base(interfaceTypes) { }
|
||||
}
|
||||
}
|
||||
8
src/Dependify/Attributes/RegisterSingletonFactory.cs
Normal file
8
src/Dependify/Attributes/RegisterSingletonFactory.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RegisterSingletonFactory : RegisterFactory {
|
||||
public RegisterSingletonFactory(Type returnType) : base(returnType) { }
|
||||
}
|
||||
}
|
||||
10
src/Dependify/Attributes/RegisterTransient.cs
Normal file
10
src/Dependify/Attributes/RegisterTransient.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterTransient : Register {
|
||||
public RegisterTransient() { }
|
||||
|
||||
public RegisterTransient(params Type[] interfaceTypes) : base(interfaceTypes) { }
|
||||
}
|
||||
}
|
||||
8
src/Dependify/Attributes/RegisterTransientFactory.cs
Normal file
8
src/Dependify/Attributes/RegisterTransientFactory.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Dependify.Attributes {
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RegisterTransientFactory : RegisterFactory {
|
||||
public RegisterTransientFactory(Type returnType) : base(returnType) { }
|
||||
}
|
||||
}
|
||||
11
src/Dependify/Dependify.csproj
Normal file
11
src/Dependify/Dependify.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Helpers" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
67
src/Dependify/IServiceCollectionExtensions.cs
Normal file
67
src/Dependify/IServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dependify.Attributes;
|
||||
using Dependify.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Dependify {
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IServiceCollectionExtensions {
|
||||
public static IServiceCollection AutoRegister(this IServiceCollection services) {
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
services.AddFactories(DependifyUtils.GetFactoryMethods(assemblies));
|
||||
services.AddClasses(DependifyUtils.GetClassTypes(assemblies));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AutoRegister(this IServiceCollection services, params Assembly[] assemblies) {
|
||||
services.AddFactories(DependifyUtils.GetFactoryMethods(assemblies));
|
||||
services.AddClasses(DependifyUtils.GetClassTypes(assemblies));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AutoRegister(this IServiceCollection services, string @namespace) {
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
services.AddFactories(DependifyUtils.GetFactoryMethodsFromNamespace(assemblies, @namespace));
|
||||
services.AddClasses(DependifyUtils.GetClassTypesFromNamespace(assemblies, @namespace));
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddFactories(this IServiceCollection services, IEnumerable<MethodInfo> methodInfos) {
|
||||
foreach (var methodInfo in methodInfos) {
|
||||
var factoryAttribute = methodInfo.GetCustomAttributes<RegisterFactory>(true).First();
|
||||
var factoryAttributeType = factoryAttribute.GetType();
|
||||
var factoryReturnType = factoryAttribute.ReturnType;
|
||||
|
||||
if (factoryAttributeType == typeof(RegisterTransientFactory))
|
||||
services.AddTransient(factoryReturnType, DependifyUtils.GetFactoryMethod(methodInfo));
|
||||
if (factoryAttributeType == typeof(RegisterScopedFactory))
|
||||
services.AddScoped(factoryReturnType, DependifyUtils.GetFactoryMethod(methodInfo));
|
||||
if (factoryAttributeType == typeof(RegisterSingletonFactory))
|
||||
services.AddSingleton(factoryReturnType, DependifyUtils.GetFactoryMethod(methodInfo));
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static void AddClasses(this IServiceCollection services, IEnumerable<Type> classTypes) {
|
||||
foreach (var classType in classTypes) {
|
||||
var classAttributes = classType.GetCustomAttributes<Register>(true);
|
||||
foreach (var classAttribute in classAttributes) {
|
||||
var registrationType = classAttribute.GetType();
|
||||
var interfaceTypes = classAttribute.InterfaceTypes == null || !classAttribute.InterfaceTypes.Any() ? classType.GetInterfaces() : classAttribute.InterfaceTypes;
|
||||
|
||||
foreach (var interfaceType in interfaceTypes) {
|
||||
if (registrationType == typeof(RegisterTransient))
|
||||
services.AddTransient(interfaceType, classType);
|
||||
if (registrationType == typeof(RegisterScoped))
|
||||
services.AddScoped(interfaceType, classType);
|
||||
if (registrationType == typeof(RegisterSingleton))
|
||||
services.AddSingleton(interfaceType, classType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Dependify/Utilities/DependifyUtils.cs
Normal file
47
src/Dependify/Utilities/DependifyUtils.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dependify.Attributes;
|
||||
|
||||
namespace Dependify.Utilities {
|
||||
internal static class DependifyUtils {
|
||||
internal static IEnumerable<MethodInfo> GetFactoryMethods(IEnumerable<Assembly> assemblies) {
|
||||
return assemblies
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => type.IsClass)
|
||||
.SelectMany(MethodsWithAttribute);
|
||||
}
|
||||
|
||||
internal static IEnumerable<MethodInfo> GetFactoryMethodsFromNamespace(IEnumerable<Assembly> assemblies, string @namespace) {
|
||||
return assemblies
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => type.IsClass && (type.Namespace?.StartsWith(@namespace)).GetValueOrDefault())
|
||||
.SelectMany(MethodsWithAttribute);
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> MethodsWithAttribute(Type type) {
|
||||
return type.GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||
.Where(methodInfo => methodInfo.GetCustomAttributes<RegisterFactory>(true).Any());
|
||||
}
|
||||
|
||||
internal static Func<IServiceProvider, object> GetFactoryMethod(MethodInfo factoryMethodInfo) {
|
||||
if (!IsFactoryMethod(factoryMethodInfo))
|
||||
throw new ArgumentException($"{factoryMethodInfo.Name} is not a factory method. ");
|
||||
return (Func<IServiceProvider, object>)Delegate.CreateDelegate(typeof(Func<IServiceProvider, object>), factoryMethodInfo);
|
||||
|
||||
bool IsFactoryMethod(MethodInfo methodInfo) {
|
||||
var methodParameters = methodInfo.GetParameters();
|
||||
return methodParameters.Length == 1 && methodParameters.First().ParameterType == typeof(IServiceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Type> GetClassTypes(IEnumerable<Assembly> assemblies) {
|
||||
return assemblies.SelectMany(assembly => assembly.GetTypes()).Where(type => type.IsClass);
|
||||
}
|
||||
|
||||
internal static IEnumerable<Type> GetClassTypesFromNamespace(IEnumerable<Assembly> assemblies, string @namespace) {
|
||||
return assemblies.SelectMany(assembly => assembly.GetTypes()).Where(type => type.IsClass && (type.Namespace?.StartsWith(@namespace)).GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user