6

I am working on a ASP.NET Core 2.2 Web API application with Mediatr.

I have a handler that looks like -

public class MyQueryHandler<T> : IRequestHanlder<MyQuery<T>, IQueryable<T>>
{
   public Task<IQueryable<T>> Handle(MyQuery<T> myquery, CancellationToken cancellationToken)
   {
       //perform query

       IQueryable<T> models = someDatabaseQuery.ProjectTo<T>();
   }       
}

This is the query -

public class MyQuery<T> : IRequest<IQueryable<T>>
{
   //some properties
}

When I try to make a request like this -

var result = await _mediator.Send(new MyQuery<SomeModel> {/* set the properties on the query */})

I get an exception -

An unhandled exception occurred while processing the request.

InvalidOperationException: Handler was not found for request of type MediatR.IRequestHandler`2[MyQuery`1[SomeModel],System.Linq.IQueryable`1[SomeModel]]. Register your handlers with the container. See the samples in GitHub for examples.

I have spent quite a few hours tried a many things but none have worked. I even tired using Autofac along side the service collection, following the example provided in the Mediator github repo.

Bryan
  • 5,065
  • 10
  • 51
  • 68
  • I think each Query object should have a flat structure like `Dtos`, so handler for it can be easily registered at the runtime. If you want to create some generic query handler why not just using Behaviors? – GoldenAge Aug 15 '19 at 08:43
  • Can you tell me please what would be the goal of the `MyQuery` – GoldenAge Aug 15 '19 at 08:45
  • In the handler I use automapper projections to limit what is queried from the db table in question. The lets the caller tell query and in turn the handler the shape of data wanted. – Bryan Aug 15 '19 at 13:12
  • I updated my answer. Is this the answer to your question or you wanted to achieve something different? If no please give me some more details, so I can try to help. I spent hours struggling with this library so I know your pain :D – GoldenAge Aug 15 '19 at 21:33
  • I have a solution for my specific problem, I'll try to write up an answer soon. – Bryan Aug 30 '19 at 17:40
  • Did you find a solution to this problem, @Bryan? – MorganR Jan 03 '20 at 00:39
  • Yes, I created a base handler and base query with all the properties I needed, then I created created specific empty queries for the variations I needed. DI worked fine after that. – Bryan Jan 09 '20 at 18:53

6 Answers6

2

Each Query should have a concrete type/flat structure so the handler for it can be easily registered by the Dependency Injection container at the runtime. I believe registering the generic query handler as you gave as an example is just impossible as the DI container can have problems with registration generic types. I believe creating a Behavior is the right thing you should do. It can give you a possibility to handle all queries or commands in one place, so you can run some extra/generic logic like logging stuff, etc. before you hit the handler of given Query/Command.

EDIT

In the handler I use automapper projections to limit what is queried from the db table in question. The lets the caller tell query and in turn the handler the shape of data wanted.

To limit what is queried from the database I would use an approach of creating a query and query handler per entity. I think it makes sense to have such a separation as from security perspective you may want to give access to only a specific group of users for running given queries.

So the example for e.g. Order entity would look like.

public class OrderDto
{
    public string Name { get; set; }

    public int Amount { get; set; }
}

public class FilterOrdersQuery : IRequest<List<OrderDto>>
{
    public string Filter { get; set; }
}

public class FilterOrdersQueryHandler : IRequestHandler<FilterOrdersQuery, List<OrderDto>>
{
    public Task<List<OrderDto>> Handle(FilterOrdersQuery notification, CancellationToken cancellationToken)
    {
        var dataSource = new List<OrderDto>(){
            new OrderDto()
            {
                Name = "blah",
                Amount = 65
            },
            new OrderDto()
            {
                Name = "foo",
                Amount = 12
            },
        };

        var result = dataSource
            .Where(x => x.Name.Contains(notification.Filter))              
            .ToList();

        return Task.FromResult(result);
    }
}

This is only a simple example which shows how to filter the given entity and return List of filtered objects. You can also add logic for pagination, OrderBy, etc. etc.

GoldenAge
  • 2,918
  • 5
  • 25
  • 63
1
interface IQuery<out TResult> : IRequest<TResult>
{
}
interface IQueryHandler<in TQuery, TResult> : IRequestHandler<TQuery, TResult>
    where TQuery : IQuery<TResult>
{
}

You can find a good example of how to set up MediatR for custom Commands/Queries here: https://github.com/LeftTwixWand/ModernCQRS

0
//You can try below. Copy to your startup
var builder = new ContainerBuilder();
builder.Populate(services);
var entityTypes = typeof(SomeModel).Assembly.GetTypes();
var handerType = typeof(MyQueryHandler<>);
foreach (var entityType in entityTypes)
{
   var handlerGenericType = (TypeInfo)handerType.MakeGenericType(entityType);
   foreach (var genericType in handlerGenericType.ImplementedInterfaces)
   {
      builder.RegisterType(handlerGenericType).As(genericType);
   }
}
0

If you have your handler in a separate assembly you need to tell MediatR where to look for it. The AddMediatR method takes a list of assemblies, or types which MediatR uses to look for handlers in the same Assembly.

In ConfigureServices in your Startup class, you add MediatR probably like this:

services.AddMediatR(typeof(Startup));

or

services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);

Both give the same result - MediatR looks for handlers in the assembly where the Startup class is.

If your handler is in another assembly you can add it to MediatR like this:

services.AddMediatR(typeof(Startup),
                    typeof(FooBar),
                    typeof(Some.Other.Class.In.Another.Assembly));
Peter Hedberg
  • 3,487
  • 2
  • 28
  • 36
0

You need to add Autofac in your Program.cs

// In your Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacServiceProviderFactory())
        .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });

And at the end of your startup you need to add something like this :

public void ConfigureContainer(ContainerBuilder builder)
    {
        Type[] entityTypes =
        {
            typeof(SomeModel)
            // add all the models you want
        };

        var handlerTypes = new List<Type>
        {
            typeof(MyQuery<>)
            // add all the handlers you want
        };

        foreach (Type entityType in entityTypes)
        foreach (Type handlerType in handlerTypes)
        {
            var handlerGenericType = (TypeInfo) handlerType.MakeGenericType(entityType);
            foreach (Type genericType in handlerGenericType.ImplementedInterfaces)
                builder.RegisterType(handlerGenericType).As(genericType);
        }
    }
pdouelle
  • 129
  • 1
  • 5
0

With IGet you can avoid these problems: get the handler and then call it - like this:

var result = await i.Get<MyQueryHandler>().Handle(myQuery, cancellationToken);

This example does not use a generic handler, but note that getting and using generic handlers is just as simple with that library (NuGet package).

The compiler now warns you immediately if the request type does not match the handler's method. The problem that a handler might not be found does not exist.

Also, the MyQueryHandler class doesn't need to implement any interface:

public class MyQueryHandler
{
    private readonly IConnectionFactory _connectionFactory;
    private readonly ILogger<MyQueryHandler> _logger;

    public MyQueryHandler(
        IConnectionFactory connectionFactory,
        ILogger<MyQueryHandler> logger)
    {
        _connectionFactory = connectionFactory;
        _logger = logger;
    }
    public async Task<IQueryable<SomeModel>> Handle(
        MyQuery myQuery,
        CancellationToken cancellationToken)
    {
        // perform query
        return result;
    }       
}

I deliberately added dependencies (randomly chosen IConnectionFactory and ILogger<MyQueryHandler>) to this example to clarify that IGet also injects those dependencies for you.

SymboLinker
  • 884
  • 6
  • 15