【问题标题】:IMediatR - Generic Request Handler for generic RequestsMediatR - 通用请求的通用请求处理程序
【发布时间】:2020-02-07 18:06:57
【问题描述】:

我目前正面临 IMediatR IRequestHandlers 和 IRequest 的问题。为了提供一点上下文,在我们的工具上,用户可以通过填写几个字段来请求访问该工具。目前,我们有两个访问级别,这意味着我们应该有两个 IRequestHandler,每个一个。由于我们要做的只是将访问请求注册到数据库并发送电子邮件,因此我认为可以创建一个通用的 IRequestHandler 来处理这些情况(如果我们创建移动访问级别,它将在未来有所帮助)。

我对如何通用注入这些类型的处理程序进行了一些研究,但我只能找到非通用注入。我想知道是否有一种方法可以为我在下面展示的那些 IRequests 注入这些类型的通用 IRequestHandlers?

这是我为 IRequest 创建的代码。

public class CreateAccessRequest<TViewModel> : IRequest<OperationResult<long>>, IValidatable
    where TViewModel : AccessRequestViewModel
{
    public TViewModel Request { get; set; }
}

这是请求处理程序,我可以创建更通用的处理程序。

public class CreateAccessRequestHandler<TViewModel, TNotificationModel, TEntity> : IRequestHandler<CreateAccessRequest<TViewModel>, OperationResult<long>>
    where TViewModel : AccessRequestViewModel
    where TEntity : Entity
    where TNotificationModel : INotification
{
    private readonly IRepository<TEntity> _accessRequestRepository;
    private readonly IMediator _mediator;
    private readonly IMapper _mapper;

    public CreateAccessRequestHandler(IRepository<TEntity> accessRequestRepository, IMediator mediator, IMapper mapper)
    {
       // initalization ...
    }

    public async Task<OperationResult<long>> Handle(CreateAccessRequest<TViewModel> request, CancellationToken cancellationToken)
    {
       // mapping, saving to database and sending notifications...
    }
}

最后,这是处理程序的注入代码

void ConfigureMediatR(Container container)
{
    var assemblies = GetAssemblies().ToArray();
    container.RegisterSingleton<IMediator, Mediator>();
    container.Register(typeof(IRequestHandler<,>), assemblies);
}

我正在这样创建请求:

// CreateApplicationAccessRequestViewModel inherits AccessRequestViewModel
var request = new CreateAccessRequest<CreateApplicationAccessRequestViewModel>();

MediatR 未找到请求的请求处理程序并触发此异常:

Error constructing handler for the request of type MediatR.IRequestHandler<CreateAccessRequest<CreateApplicationAccessRequestViewModel>, OperationResult<long>>. Register your handlers with the container. See the samples in GitHub for example.

我不能只做那个实现,我必须创建一个继承自 CreateAccessRequestHandler 的具有正确泛型类型的类,然后它才起作用:

public sealed class CreateApplicationAccessRequestHandler : CreateAccessRequestHandler<CreateApplicationAccessRequestViewModel, ApplicationAccessRequestNotificationModel, ApplicationAccessRequest>
    {
        public CreateApplicationAccessRequestHandler(IRepository<ApplicationAccessRequest> accessRequestRepository, IMediator mediator, IMapper mapper)
            : base(accessRequestRepository, mediator, mapper)
        { }
    }

我的想法是只使用通用处理程序,而不需要为每个访问请求类型创建派生类。有办法吗?

--- 编辑: ---

我使用了 Steven 在他的答案中放置的代码,它几乎适用于我的情况。我将在下面解释这个几乎:

var types = container.GetTypesToRegister(typeof(IRequestHandler<,>), assemblies, 
new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });

container.Register(typeof(IRequestHandler<,>), types.Where(t => !t.IsGenericTypeDefinition));

foreach(var openGenericType in types.Where(t => t.IsGenericTypeDefinition)) {
    container.Register(typeof(IRequestHandler<,>), openGenericType);
}

我分别注册了泛型和非泛型类型,它在大多数情况下都有效,除了当你有一个无法解析的类型时,我的情况就是 TEntity。

SimpleInjector 不知道如何解析 TEntity,因为(我猜)我没有在定义中将它提供给 IRequestHandler。

如果我在请求中稍微放置了 TEntity(即CreateAccessRequest&lt;TViewModel, TEntity&gt;),它会起作用,但它会破坏项目的架构。所以我选择了我在这里提到的备份实现(从那些通用的创建具体的处理程序)至少保持代码集中。

【问题讨论】:

    标签: .net asp.net-core generics simple-injector mediatr


    【解决方案1】:

    MediatR 未找到请求的请求处理程序

    MediatR 只是一堆接口;它不负责查找请求处理程序——Simple Injector 负责。

    您目前正在使用以下代码来注册您的处理程序:

    container.Register(typeof(IRequestHandler<,>), assemblies);
    

    这将注册:

    给定的assemblies 集合中的所有具体的、非泛型的、公共的和内部的类型,它们以容器的默认生活方式实现给定的openGenericServiceType

    (如 XML 文档所述)

    您要做的不仅是注册非泛型,还要注册 generic 类型。这可以通过多种方式实现。例如,通过附加这些泛型类型:

    container.Register(typeof(IRequestHandler<,>), assemblies);
    container.Register(typeof(IRequestHandler<,>), typeof(CreateAccessRequestHandler<,>));
    

    但是,如果您有许多开放通用的实现,那么批量注册它们可能会更有效:

    var types =
        container.GetTypesToRegister(typeof(IRequestHandler<,>), assemblies,
            new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });
    
    container.Register(typeof(IRequestHandler<,>), types);
    

    【讨论】:

    • 感谢您的回答。我通过泛型和非泛型类型定义将types 分隔开来进行注册,并且它起作用了。唯一的问题是我意识到我的处理程序中有一个无法解析的类型,即TEntity,这会阻止我正确注册泛型类型的类。 (即CreateAccessRequestHandler&lt;TViewModel, TNotificationModel, **TEntity**&gt;)。正如我在原始帖子中所描述的那样,我将采用从通用类中创建一个具体类的备份理念,即使这意味着为每个新用户操作创建一个新类。总比没有好:)
    • 您可以将部分封闭的泛型类型注册到 Simple Injector。例如。您可以创建一个仅填充 TModel 的类型。但请注意,C# 不支持部分封闭的类型,因此您需要使用反射从开放泛型类型构造该类型。但这实际上可能使事情变得过于复杂。您的里程可能会有所不同。
    • 是的,我不想改变太多,我只想将业务逻辑保留在一个地方。当我在那里使用具体类时它会起作用,所以这对我来说很好。
    猜你喜欢
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    相关资源
    最近更新 更多