简体   繁体   中英

Autofac: Resolving generic interface in a generic method

The generic interface:

public interface IGeneric<T>{}

The client:

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

Any idea how to do this?

Assuming you have a class implementing your interface

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

and you have registered it in your container

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

then you can resolve it like this in your DoSomething method

public class ClientClass
{
    private readonly ILifetimeScope _scope;

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

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

As you can see you'll need an instance of the Autofac scope ( ILifetimeScope ) in the DoSomething method. You can inject it using the constructor. As far as I know there is no other way, since your ClientClass is not generic itself. You can't use constructor or property injection to obtain the IGeneric<T> instance since you don't know the type of T while creating the ClientClass instance.

As I see it you have two options:

  • Inject the scope into your ClientClass and use it to resolve the IGeneric<T> instance (as shown above)
  • Make the ClientClass generic and inject the IGeneric<T> instance in the constructor

I had this same problem, and the solution I came up with involves method interception. Consider the following class:

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

Register your client class to be intercepted by this class:

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

Change your method to accept an instance of your 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
    }
}

Assuming your ClientClass is resolved by Autofac, every method (that is marked as virtual) will be intercepted by this class. It will examine the method arguments and attempt to find one that is IGeneric. If the passed in argument is null, then it will examine the called method's generic type parameter, and resolve an instance of IGeneric. It will then set the argument to that resolved value.

You don't have to make the dependency a default parameter, but it allows you call your method as you would have normally, while still giving you the option of injecting a specific type if desired:

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

The one big drawback about this method is that it will probably difficult to understand to anyone else who might be working on the code and/or debugging it, if you only ever call your DoSomething() method with an empty argument list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM