Initial commit of Dependify

This commit is contained in:
David Kaya
2017-09-09 22:45:06 +02:00
parent 456bcf1fa7
commit b5626dadb1
17 changed files with 602 additions and 0 deletions

View 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();
}
}
}

View 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);
}
}
}

View 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>

View 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;
}
}
}

View 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;
}
}
}

View 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) { }
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace Dependify.Attributes {
[AttributeUsage(AttributeTargets.Method)]
public class RegisterScopedFactory : RegisterFactory {
public RegisterScopedFactory(Type returnType) : base(returnType) { }
}
}

View 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) { }
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace Dependify.Attributes {
[AttributeUsage(AttributeTargets.Method)]
public class RegisterSingletonFactory : RegisterFactory {
public RegisterSingletonFactory(Type returnType) : base(returnType) { }
}
}

View 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) { }
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace Dependify.Attributes {
[AttributeUsage(AttributeTargets.Method)]
public class RegisterTransientFactory : RegisterFactory {
public RegisterTransientFactory(Type returnType) : base(returnType) { }
}
}

View 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>

View 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);
}
}
}
}
}
}

View 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());
}
}
}