5

I am having trouble with asp.net core dependency injection, I cannot resolve generic interface from IServiceProvider. Here is my setup:

Generic interfaces:

public interface IRequest<out TResponse> {...}

public interface IRequestHandler<TRequest, TResult> 
    where TRequest : IRequest<TResult> {...}

Concrete implementation:

public class GetUsersQuery : IRequest<IEnumerable<GetUsersResult>> {...}

public abstract class RequestHandler<TRequest, TResult> 
    : IRequestHandler<TRequest, TResult>
    where TRequest : IRequest<TResult> {...}

public class GetUsersQueryHandler
    : RequestHandler<GetUsersQuery, IEnumerable<GetUsersResult>> {...}

Then I have a service factory were I register dependency injection like this:

public static void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IRequestHandler<GetUsersQuery, 
    IEnumerable<GetUsersResult>>, GetUsersQueryHandler>();
}

I can successfully resolve my handler like this:

var handler = 
_services.GetService<IRequestHandler<GetUsersQuery, IEnumerable<GetUsersResult>>>();

However, I would like to have a generic method in this factory that receives concrete implementation of IRequest and returns the appropriate handler without knowing the exact type beforehand, something like this:

public Task<TResult> Execute<TResult>(IRequest<TResult> request)
{
    var handler =
        _services.GetService<IRequestHandler<IRequest<TResult>, TResult>>();
    return handler.ExecuteAsync(request);
}

And call this method like this:

_serviceFactory.Execute(new GetUsersQuery(){});

Unfortunately this doesn't work, handler is not resolved and is null. I feel like this should be possible though.

Could you please tell me what I am doing wrong and how to achieve this?

ekad
  • 14,436
  • 26
  • 44
  • 46
Taras
  • 205
  • 3
  • 8

2 Answers2

4

This design might originate from this blog post. That same blog post shows a solution for your exact problem:

public TResult Process<TResult>(IQuery<TResult> query)
{
    var handlerType = typeof(IQueryHandler<,>)
        .MakeGenericType(query.GetType(), typeof(TResult));

    dynamic handler = container.GetInstance(handlerType);

    return handler.Handle((dynamic)query);
}

This translates, in your case, to the following:

public Task<TResult> Execute<TResult>(IRequest<TResult> request)
{
    var handlerType = typeof(IRequestHandler<,>)
        .MakeGenericType(request.GetType(), typeof(TResult));

    dynamic handler = _services.GetRequiredService(handlerType);

    return handler.ExecuteAsync((dynamic)query);
}

About the use of dynamic, the blog post states:

Unfortunately we need to call the Handle method using reflection (which is done, in this case, using the C# 4.0 dymamic keyword), because at this point it is impossible to cast the handler instance, since the generic TQuery argument is not available at compile time.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thank you! This works, I think this approach is better than the one in my answer. I haven't seen this blog post before, your implementation is indeed almost the same. – Taras Nov 14 '18 at 15:47
  • Also, I wonder - is it possible to register all handlers in ASP.NET Core like in your example: container.Register(typeof(IQueryHandler<,>), typeof(IQueryHandler<,>).Assembly); – Taras Nov 14 '18 at 15:50
  • 1
    You will have to do it manually by iterating through the assembly types, filter out non query handlers, and register them by their interface. There's no built-in support for auto-registration. – Steven Nov 15 '18 at 14:37
  • Thank you Steven. Would you agree with the commenter below my answer that such design could be considered a service locator anti pattern? I see that you also replied to similar question, and considering that this is basically your architecture too, I would like to know your opinion, thanks. – Taras Nov 19 '18 at 11:35
  • Hi @Taras, see my response under your own answer. – Steven Nov 19 '18 at 11:47
  • Hi Steven, thanks, I asked another question in that thread, sorry for bothering you that much :) – Taras Nov 26 '18 at 13:05
0

I think I have found one way of doing this, execute method could be declared like this:

public Task<TResult> Execute<TRequest, TResult>(TRequest request)
        where TRequest : IRequest<TResult> 
{
    var handler = _services.GetService<IRequestHandler<TRequest, TResult>>();
    return handler.ExecuteAsync(request);
}

And used like this:

_serviceFactory.Execute<GetUsersQuery, IEnumerable<GetUsersResult>>(query);

This is a bit ugly, because I have to specify both request and result types for Execute method, would be better to just use _serviceFactory.Execute(query), but I suppose it might not be possible?

Taras
  • 205
  • 3
  • 8
  • In your original example, you are executing this call `GetService>, IEnumerable>>` which is not the same as `GetService>>` – Alexander Pope Nov 09 '18 at 19:53
  • I think you should reconsider your design. You are attempting to implement the ServiceLocator pattern through the DI container. The sane way of solving your problem is to use factories when instead: https://stackoverflow.com/questions/31950362/factory-method-with-di-and-ioc. – Alexander Pope Nov 09 '18 at 20:07
  • Hi, thank you for your feedback. This design seems simple and flexible enough, and it requires very little code, especially with the approach that Steven suggested. What are the downsides in your opinion? – Taras Nov 14 '18 at 15:57
  • First, ServiceLocator is considered by many to be an antipatern. Secondly, will you ever depend on other parameters for dependency resolution? Right now you only care about generic type parameters, but what if you start depending on bools, integers or strings? Refactoring this design would be difficult because of explicitly using the service locator instead of relying on constructor injection and factories. Maybe try asking on https://softwareengineering.stackexchange.com/ for feedback on your design. – Alexander Pope Nov 15 '18 at 17:31
  • See also this question and the linked questions from it https://stackoverflow.com/questions/47843787/asp-net-core-di-constructor-vs-requestservices – Alexander Pope Nov 15 '18 at 17:41
  • Thank you. Not sure at the moment what potential troubles with refactoring I would have, but I will think about it. – Taras Nov 19 '18 at 11:36
  • Although I do agree with @AlexanderPope that Service Locator _is_ an [anti-pattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), his comment does not take the context into consideration. As Mark Seemann explains [clearly](https://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/), not all usage of a Container is an implementation of the Service Locator anti-pattern. In short, it is fine to take a direct dependency on your Container from _within_ the [Composition Root](https://mng.bz/0W7l) (CR). Using it outside the CR is an anti-pattern. – Steven Nov 19 '18 at 11:43
  • In other words, as long as the code that resolves services is _part of_ the Composition Root, there is no problem. This is exactly what I describe in the [source](https://cuttingedge.it/blogs/steven/pivot/entry.php?id=92) article, where I define an `IQueryProcessor` abstraction (which can be seen as some sort of factory), while its `QueryProcessor` implementation (that contains the call to the container) is _part of_ the Composition Root. Chapter 5 of [DI: PPP](https://manning.com/seemann2) describes this in much greater detail. – Steven Nov 19 '18 at 11:46
  • Steven, I am not sure I understand - are you saying that using my service factory to resolve requests from within controllers is fine, but injecting it to request handlers (if one request handler has to execute another request) would be wrong, or am I missing the point? In general, if there is a need to execute another command from some command handler, would you inject this service factory to its constructor, or a specific IRequestHandler (or maybe neither is good)? – Taras Nov 26 '18 at 13:03
  • Hi @Taras, it is fine to inject your `IServiceFactory` abstraction into both controllers and handlers, and let it (internally) resolve and execute handlers. There is no problem in that, as long as the `IServiceFactory` _implementation_ is part of the Composition Root. You can inject this `IServiceFactory` 'dispatcher' or request handlers directly into consumers; neither is bad. – Steven Nov 26 '18 at 13:19