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

问题描述 投票:0回答:6

我正在使用 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 与服务集合一起使用。

c# covariance contravariance mediator mediatr
6个回答
2
投票

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

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 等添加逻辑


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

您可以在此处找到一个很好的示例,说明如何为自定义命令/查询设置 MediatR: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
投票

如果您的处理程序位于单独的程序集中,您需要告诉 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));

0
投票

您需要在 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);
        }
    }

0
投票

有一个 NuGet 包 IGet 可以避免这些问题:获取处理程序然后调用它 - 就像这样:

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

本示例未使用通用处理程序,但请注意,获取和使用通用处理程序同样简单。

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

此外,

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

我特意在这个例子中添加了依赖项(随机选择

IConnectionFactory
ILogger<MyQueryHandler>
)来澄清 IGet 也为你注入了这些依赖项。

© www.soinside.com 2019 - 2024. All rights reserved.