1

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?

Victor Mukherjee
  • 10,487
  • 16
  • 54
  • 97
  • @downvoter, care to comment? – Victor Mukherjee Sep 13 '16 at 07:13
  • You can´t create an instance of an interface. You will need a class that implements this interface, so you have to configure your container to *use* that class if it detects the interface, don´t you? Maybe this may help ypu: http://stackoverflow.com/questions/1189519/resolving-generic-interface-with-autofac – MakePeaceGreatAgain Sep 13 '16 at 07:13
  • The downvote probably appeared because ypur question doesn´t show much own effort. Is your question on *getting* the instance or on *registering* the class to the interface? – MakePeaceGreatAgain Sep 13 '16 at 07:17

2 Answers2

2

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
fknx
  • 1,775
  • 13
  • 19
  • 2
    What's important to add here is that you should only injecy `ILifetimeScope` instances in classes that are part of your composition root (to prevent the rest of the application from taking a dependency on the container). This means that the `ClientClass` should implement an abstraction. This `IClientClass` abstraction can be placed central to the application and referable to other code, while the `ClientClass` implementation can be placed inside the Composition Root. – Steven Sep 14 '16 at 15:45
0

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.

Marc Chu
  • 201
  • 3
  • 17