Autofac:在通用方法中解析通用接口

时间:2016-09-13 07:09:05

标签: c# generics dependency-injection autofac factory

通用界面:

public interface IGeneric<T>{}

客户:

public class ClientClass
{
    public void DoSomething<T>()
    {
        //what to inject in constructor 
        //to get an implementation of the IGeneric<T> from autofac?
    }
}

知道怎么做吗?

2 个答案:

答案 0 :(得分:1)

假设您有一个实现界面的类

public class MyGeneric<T> : IGeneric<T>
{
}

您已在容器中注册

builder.RegisterGeneric(typeof(MyGeneric<>)).As(typeof(IGeneric<>));

然后您可以在DoSomething方法

中解决此问题
public class ClientClass
{
    private readonly ILifetimeScope _scope;

    public ClientClass(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public void DoSomething<T>()
    {
        var myGeneric = _scope.Resolve<IGeneric<T>>();
    }
}

如您所见,您需要ILifetimeScope方法中的Autofac范围实例(DoSomething)。您可以使用构造函数注入它。据我所知,没有别的方法,因为你的ClientClass本身并不是通用的。您无法使用构造函数或属性注入来获取IGeneric<T>实例,因为在创建T实例时您不知道ClientClass的类型。

我认为你有两种选择:

  • 将范围注入ClientClass并使用它来解析IGeneric<T>实例(如上所示)
  • 使ClientClass泛型并在构造函数中注入IGeneric<T>实例

答案 1 :(得分:0)

我遇到了同样的问题,我提出的解决方案涉及方法拦截。考虑以下课程:

public class InjectionInterceptor : IInterceptor {
    private readonly ILifetimeScope _Scope;
    public InjectionInterceptor(ILifetimeScope scope) {
        _Scope = scope;
    }

    public void Intercept(IInvocation invocation) {
        using (var lifetime = _Scope.BeginLifetimeScope(string.Format("InjectionInterceptor {0}", Guid.NewGuid()))) {
            InjectDependencyIfNecessary(invocation, lifetime);
            invocation.Proceed();
        }
    }

    private void InjectDependencyIfNecessary(IInvocation invocation, ILifetimeScope lifetime) {
        int indexOfDependencyArgument = FindDependencyArgument(invocation.Method);
        if (indexOfDependencyArgument >= 0 && invocation.GetArgumentValue(indexOfDependencyArgument) == null) {
            SetDependencyArgument(invocation, indexOfDependencyArgument, lifetime);
        }
    }

    private static int FindDependencyArgument(System.Reflection.MethodInfo method) {
        var allArgs = method.GetParameters();
        return Array.FindIndex(allArgs, param =>
            param.ParameterType.IsInterface &&
            param.ParameterType.IsGenericType &&
            param.ParameterType.GetGenericTypeDefinition() == typeof(IGeneric<>));
    }

    private void SetDependencyArgument(IInvocation invocation, int indexOfDependencyArgument, ILifetimeScope lifetime) {
        var methodArg = invocation.Method.GetGenericArguments().Single();
        var dependency = lifetime.Resolve(typeof(IGeneric<>).MakeGenericType(methodArg));
        invocation.SetArgumentValue(indexOfDependencyArgument, dependency);
    }
}

注册您的客户类以便被此类拦截:

var builder = new ContainerBuilder();
builder.RegisterType<InjectionInterceptor>();
builder.RegisterType<ClientClass>()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(InjectionInterceptor));

更改您的方法以接受您的IGeneric实例:

public class ClientClass
{
    public virtual void DoSomething<T>(IGeneric<T> dependency = null) //must be virtual to be intercepted
    {
        if (dependency == null) throw new ArgumentNullException(nameof(dependency));
        //use dependency here
    }
}

假设您的ClientClass由Autofac解析,则每个方法(标记为虚拟)将被此类拦截。它将检查方法参数并尝试找到一个IGeneric。如果传入的参数为null,那么它将检查被调用方法的泛型类型参数,并解析IGeneric的实例。然后它会将参数设置为该已解析的值。

您不必将依赖项设置为默认参数,但它允许您像平常一样调用方法,同时仍然可以根据需要选择注入特定类型:

client.DoSomething<int>(); //injected by the interceptor
client.DoSomething(new Generic<int>()); // resolved manually; interceptor does nothing

这种方法的一大缺点是,如果您只使用空参数列表调用DoSomething()方法,那么可能很难理解可能正在处理代码和/或调试代码的任何其他人。