具有通用处理程序和查询的 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<T>
的目标是什么
在处理程序中,我使用自动映射器投影来限制从相关数据库表中查询的内容。 每个查询都应该有一个具体的类型/平面结构,以便它的处理程序可以在运行时由依赖注入容器轻松注册。我相信注册通用查询处理程序作为示例是不可能的,因为 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 用于在同一程序集中查找处理程序的类型。
在 ConfigureServices
的 Startup
类中,您添加 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的主要内容,如果未能解决你的问题,请参考以下文章