具有泛型处理程序和查询的中介



我正在使用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.

我花了几个小时尝试了很多东西,但没有一个奏效。我什至厌倦了在服务集合旁边使用 Autofac,遵循调解器 github 存储库中提供的示例。

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

编辑

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

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

所以例如的例子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 等添加逻辑。

interface IQuery<out TResult> : IRequest<TResult>
{
}
interface IQueryHandler<in TQuery, TResult> : IRequestHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
}

您可以在此处找到有关如何为自定义命令/查询设置 MediatR 的良好示例:https://github.com/LeftTwixWand/ModernCQRS

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

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

Startup类的ConfigureServices中,您添加 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));

您需要在程序中添加Autofac.cs

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

使用 IGet,您可以避免以下问题: 获取处理程序,然后调用它 - 如下所示:

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

此示例不使用泛型处理程序,但请注意,使用该库(NuGet 包)获取和使用泛型处理程序同样简单。

现在,如果请求类型与处理程序的方法不匹配,编译器会立即发出警告。可能找不到处理程序的问题不存在。

此外,MyQueryHandler类不需要实现任何接口:

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

我特意在此示例中添加了依赖项(随机选择IConnectionFactoryILogger<MyQueryHandler>),以澄清 IGet 也为您注入了这些依赖项。