Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving type based on Generic Interface at runtime with autofac keyed services

i have found a number of questions and responses similar to the issue i'm facing; but i haven't been able to piece together a working solution.

I have the following:

public interface IProcessing<V,W> where W: TaskResponse where V: TaskRequest
    {
         W process(V req);
    }

where TaskRequest & TaskResponse are abstract base classes

also this concrete class is defined:

public class MathProcessor : IProcessing<MathRequest, MathResponse>
    {
         public MathResponse process(MathRequest req) {
            // do stuff
            // create a MathResponse instance

            return resp;
        }
    }

where MathRequest & MathResponse are derived classes the respective abstract classes used as the constraints in the interface definition.

Registering the concrete type as per its interface and resolving with autofac is ok.
But the difficulty arises with the intended usage of trying to resolve the concrete type at runtime based on another type (in this case the request object e.g. MathRequest). This is basically to achieve the commonly found message handler pattern (pseudo-code follows) where a message is received and dispatched off to the appropriate handler:

TaskRequest req = getNextRequest();

var proc = container.Resolve(req.getType().Name)

proc.process(req);

based on the related topics in the forum the basic suggestion is to define a factory and register it with the container and then use the factory at runtime to create the object i need based on a supplied parameter. That seems right.

i'm also seeing the related suggestion to use the IIndex<K,V> feature in autofac to do a lookup for the appropriate type/service by key.

i'm having trouble registering and the resolving the MathProcessor type by key where the key is a type (in this case MathRequest). The error may be more related to the generic interface definition and what's allowed.

registering:

builder.RegisterType<MathProcessor>().As<IProcessing<MathRequest, MathResponse>>().Keyed<IProcessing<MathRequest, MathResponse>>(strTypeName);

is ok, but

builder.RegisterType<MathProcessor>().As<IProcessing<TaskRequest, TaskResponse >>().Keyed<IProcessing< TaskRequest, TaskResponse >>(strTypeName);

is not.

note: based on the all the posts around generic types and co & contravariance in .Net i changed the interface to:

public interface IProcessing<in V, out W> where W: TaskResponse where V: TaskRequest
    {
         W process(V req);
    }

but that's merely guessing without a good understanding. in my naive view MathProcessor is a type of IProcessing but that doesn't seem to be the case.

like image 423
50missionCap Avatar asked Dec 05 '12 03:12

50missionCap


1 Answers

I don't think that type of registering will be possible, however, given that you seem to be invoking the actual processing in a generic way, I assume your main objective is to implement discrete processors for each type of request.

In that case, you can modify your model slightly, by creating a BaseProcessing<TV,TW> type. For example:

public abstract class BaseProcessing<TV, TW> : IProcessing
    where TV : TaskRequest
    where TW : TaskResponse
{
    protected abstract TW DoProcess(TV req);

    public TaskResponse Process(TaskRequest req)
    {
        return DoProcess((TV)req);
    }
}

Which implements a very basic IProcessing interface, that is the one we will be registering in AutoFac:

public interface IProcessing
{
    TaskResponse Process(TaskRequest req);
}

Using that as a base, you can still create your processors, such as:

public class MathProcessor : BaseProcessing<MathRequest, MathResponse>
{
    protected override MathResponse DoProcess(MathRequest req)
    {
        return new MathResponse();
    }
}

And one way to register them may be using keyed services, for example:

builder.RegisterType<MathProcessor>().AsImplementedInterfaces().Keyed<IProcessing>(typeof(MathRequest));
builder.RegisterType<OtherProcessor>().AsImplementedInterfaces().Keyed<IProcessing>(typeof(OtherRequest));

In this case I'm not actually using the name of the type, but rather the type itself, but both would work.

Now, having the keyed registration means you can have another component consume it as a dependency:

public class SomeConsumer : IConsumer
{
    private readonly IIndex<Type, IProcessing> _processors;

    public SomeConsumer(IIndex<Type, IProcessing> processors)
    {
        _processors = processors;
    }

    public void DoStuff()
    {
        var someRequest = new MathRequest();
        var someResponse = _processors[someRequest.GetType()].Process(someRequest);
    }
}
like image 98
Pablo Romeo Avatar answered Nov 13 '22 02:11

Pablo Romeo