具有通用处理程序和查询的 Mediatr

Posted

技术标签:

【中文标题】具有通用处理程序和查询的 Mediatr【英文标题】:Mediatr with generic handler and query 【发布时间】:2019-12-21 13:58:35 【问题描述】:

我正在使用 Mediatr 开发 ASP.NET Core 2.2 Web API 应用程序。

我有一个看起来像 -

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

这是查询 -

public class MyQuery<T> : IRequest<IQueryable<T>>

   //some properties

当我尝试提出这样的请求时 -

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

我得到一个例外 -

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.

我花了好几个小时尝试了很多东西,但都没有奏效。按照 Mediator github repo 中提供的示例,我什至厌倦了在服务集合旁边使用 Autofac。

【问题讨论】:

我认为每个 Query 对象都应该有一个像Dtos 这样的扁平结构,所以它的处理程序可以很容易地在运行时注册。如果您想创建一些通用查询处理程序,为什么不只使用 Behaviors? 你能告诉我MyQuery&lt;T&gt;的目标是什么 在处理程序中,我使用自动映射器投影来限制从相关数据库表中查询的内容。 让调用者告诉查询,然后处理程序需要的数据形状。 我更新了我的答案。这是您问题的答案还是您想要实现不同的目标?如果没有,请给我更多详细信息,以便我可以尝试提供帮助。我花了几个小时在这个库上苦苦挣扎,所以我知道你的痛苦:D 我对我的具体问题有一个解决方案,我会尽快写一个答案。 【参考方案1】:

每个查询都应该有一个具体的类型/平面结构,以便它的处理程序可以在运行时由依赖注入容器轻松注册。我相信注册通用查询处理程序作为示例是不可能的,因为 DI 容器可能会遇到注册通用类型的问题。 我相信创建Behavior 是您应该做的正确事情。它可以让您在一个地方处理所有查询或命令,因此您可以在点击给定Query/Command 的处理程序之前运行一些额外/通用逻辑,如日志记录等。

编辑

在处理程序中,我使用自动映射器投影来限制查询的内容 从有问题的 db 表中。让调用者告诉查询和 反过来处理程序所需的数据形状。

为了限制从数据库中查询的内容,我将使用一种为每个实体创建查询和查询处理程序的方法。我认为进行这样的分离是有意义的,因为从安全角度来看,您可能希望仅授予特定用户组以运行给定查询。

因此,例如Order 实体看起来像。

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

这只是一个简单的例子,展示了如何过滤给定的实体并返回过滤对象的列表。您还可以为分页、OrderBy 等添加逻辑。

【讨论】:

【参考方案2】:
//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);
   

【讨论】:

【参考方案3】:

如果您将处理程序放在单独的程序集中,则需要告诉 MediatR 在哪里查找它。 AddMediatR 方法采用程序集列表或 MediatR 用于在同一程序集中查找处理程序的类型。

ConfigureServicesStartup 类中,您添加 MediatR 可能是这样的:

services.AddMediatR(typeof(Startup));

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

两者都给出相同的结果 - MediatR 在 Startup 类所在的程序集中查找处理程序。

如果您的处理程序在另一个程序集中,您可以像这样将它添加到 MediatR:

services.AddMediatR(typeof(Startup),
                    typeof(FooBar),
                    typeof(Some.Other.Class.In.Another.Assembly));

【讨论】:

【参考方案4】:

您需要在 Program.cs 中添加 Autofac

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

在启动结束时,您需要添加如下内容:

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

【讨论】:

以上是关于具有通用处理程序和查询的 Mediatr的主要内容,如果未能解决你的问题,请参考以下文章

该程序无法找到 MediatR 查询 ASP.Net Core 的处理程序

中介模式和简单注入器的逆变

MediatR - 处理查询中的当前用户

MediatR 在 IIS 上找不到处理程序

如何取消 MediatR 发送的查询?

CQRS 命令/查询装饰器