如何使用 MediatR 执行查询,然后根据查询结果执行命令?

How do I perform a query and then a command based on the query's result with MediatR?

我正在尝试使用 MediatR 库在我的网络核心网络中实施命令模式 API,但是,我不确定如何继续。

我遇到这样一种情况,当用户尝试注册帐户时,API 应该检查数据库中是否有与提供的电子邮件地址相匹配的域的公司,然后将公司 ID 附加到用户对象作为外键,或者 return 如果该域不存在公司则出错。

我有所有必要的命令和处理程序来单独执行这些操作:

GetCompanyByDomainHandler.cs

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Application.Application.Exceptions;
using Application.Domain.Entities;
using Application.Persistence;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace Application.Application.Companies.Queries.GetCompanyByDomain
{
    public class GetCompanyByDomainHandler 
IRequestHandler<GetCompanyByDomainQuery, Company>
    {
        private readonly ApplicationDbContext _context;

        public GetCompanyByDomainHandler(ApplicationDbContext context)
        {
            _context = context;
        }
        public async Task<Company> Handle(GetCompanyByDomainQuery request, 
CancellationToken cancellationToken)
        {
            var company = await _context.Companies.Where(c => c.Domain == 
request.Domain).SingleOrDefaultAsync();

            if (company != null) {
                return company;
            }

            throw new NotFoundException(nameof(Company), request.Domain);
        }
    }
}

GetCompanyByDomainQuery.cs

using Application.Domain.Entities;
using MediatR;

namespace Application.Application.Companies.Queries.GetCompanyByDomain
{
    public class GetCompanyByDomainQuery : IRequest<Company>
    {
        public string Domain { get; set; }
    }
}

CreateUserCommand.cs

using MediatR;

namespace Application.Application.Users.Commands.CreateUser
{
    public class CreateUserCommand : IRequest<int>
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmailAddress { get; set; }
        public string Password { get; set; }
        public string ConfirmPassword { get; set; }
        public int CompanyId { get; set; }
    }
}

CreateUserCommandHandler.cs

using MediatR;
using Application.Domain.Entities.Identity;
using Microsoft.AspNetCore.Identity;
using System.Threading;
using System.Threading.Tasks;
using System;

namespace Application.Application.Users.Commands.CreateUser
{
    public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
    {
        private readonly UserManager<User> _userManager;

        public CreateUserCommandHandler(UserManager<User> userManager)
        {
            _userManager = userManager;
        }

        public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
        {
            var entity = new User
            {
                FirstName = request.FirstName,
                LastName = request.LastName,
                Email = request.EmailAddress,
                UserName = request.EmailAddress,
                CompanyId = request.CompanyId
            };

            var createUserResult = await _userManager.CreateAsync(entity, request.Password);
            if (createUserResult.Succeeded)
            {
                return entity.Id;
            }

            throw new Exception("failed to create user");
        }
    }
}

CreateUserCommandValidator.cs

using FluentValidation;

namespace Application.Application.Users.Commands.CreateUser
{
    public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
    {
        public CreateUserCommandValidator()
        {
            RuleFor(v => v.Password)
                .Equal(v => v.ConfirmPassword).WithName("password").WithMessage("Passwords do not match");
            RuleFor(v => v.ConfirmPassword)
                .Equal(v => v.Password).WithName("confirmPassword").WithMessage("Passwords do not match");
            RuleFor(v => v.EmailAddress)
                .NotEmpty().WithName("emailAddress").WithMessage("Email Address is required")
                .EmailAddress().WithName("emailAddress").WithMessage("Invalid email address");
            RuleFor(v => v.FirstName)
                .NotEmpty().WithName("firstName").WithMessage("First Name is required");
            RuleFor(v => v.LastName)
                .NotEmpty().WithName("lastName").WithMessage("Last Name is required");
        }
    }
}

AuthenticationController.cs

using System.Threading.Tasks;
using Application.Application.Users.Commands.CreateUser;
using Microsoft.AspNetCore.Mvc;

namespace Application.WebUI.Controllers
{
    public class AuthenticationController : ControllerBase
    {
        [HttpPost]
        public async Task<IActionResult> Register([FromBody] CreateUserCommand command)
        {
            return Ok(Mediator.Send(command));
        }
    }
}

但是我如何让它们成为单个请求的一部分?

首先,将 GetCompanyByDomainHandler 更改为在未找到公司时不抛出异常。
找不到公司也不例外,这是查询的结果。只是 return null
See why

现在,您可以获取查询结果并对其进行操作(而不是 try catch)

//todo: implement a way of getting the domain from an email address - regex, or string.split('.') ??
var domain = GetDomainFromEmail(command.EmailAddress);

//now get the company (or null, if it doesn't exist)
var getCompanyByDomainQuery = new GetCompanyByDomainQuery() { Domain = domain}
var company = await _mediator.SendAsync(getCompanyByDomainQuery);

//if theres a company, attach the id to the createUserCommand
if(company != null)
{
    command.CompanyId = company.Id;
}

//now save the user
var createdUser = await _mediator.SendAsync(command);

您可以将其包装在另一个 Handler 中 - 或者将 API 操作方法视为 'orchestrator'(我的偏好)

多年后,与 MediatR 一起工作了很多,我意识到我的问题根本上是错误的。您不应该在查询后调用命令。如果您需要这样做,那么您应该将两者合并在一起,因为它们正在执行单个域驱动的操作。 GetDomainFromEmail 应该是 CreateUserCommand 在其自己的处理程序中使用的东西,而不是它自己的查询。如果存在从电子邮件中获取域的端点,它确实可以是它自己的查询,并且 CreateUserCommand 处理程序和 GetDomainFromEmailQuery 处理程序都将使用一个实际从电子邮件中提取域的通用实用程序。