在使用MVC的CQRS中,验证应该在哪里进行

本文关键字:验证 在哪里 MVC CQRS | 更新日期: 2023-09-27 18:25:01

这是我创建新用户的post方法:

[HttpPost]
public ActionResult CreateUser(CreateUserViewModel createUserViewModel)
{
    CreateSystemUserCommand createSystemUserCommand = new CreateSystemUserCommand()
    {
        Firstname = createUserViewModel.Forename,
        Surname = createUserViewModel.Surname,
        Username = createUserViewModel.Username,
        Password = createUserViewModel.Password
    };
    CreateSystemUserCommandHandler handler = new CreateSystemUserCommandHandler();
    handler.Execute(createSystemUserCommand);
    return RedirectToAction("ViewUsers");
}

已经对视图模型、必填字段等进行了一些验证,因此UI将对其进行验证

然而,我想知道如何在服务器端做到这一点。

我应该创建一个方法createSystemUserCommand.Validate();

还是在handler.Execute()之前,做handler.Validate()

我应该如何将这些错误转换为ModelState?我猜CQRS与MVC没有关联,因此返回特定的模型错误是没有意义的。

欢迎有任何想法。我的直觉是做处理者。验证,因为它将把验证逻辑保留在一个类中,感觉是正确的,但我愿意接受建议。

在使用MVC的CQRS中,验证应该在哪里进行

您可能需要两种类型的验证:

  • 一种是简单的ModelState验证,它可以确保所需的字段不丢失,int是int等等。为此,使用Data注释属性就可以了。

  • 第二种类型是业务逻辑验证——可能需要访问数据库或运行其他验证逻辑,以确保数据完整性不受影响。这种类型的验证将在命令级别进行。最好的方法是遵循decorator模式——将实际的处理程序包装在一个验证处理程序中:

    public class ValidationCommandHandlerDecorator<TCommand, TResult>
        : ICommandHandler<TCommand, TResult>
        where TCommand : ICommand<TResult>
    {
        private readonly ICommandHandler<TCommand, TResult> decorated;
        public ValidationCommandHandlerDecorator(ICommandHandler<TCommand, TResult> decorated)
        {
            this.decorated = decorated;
        }
        [DebuggerStepThrough]
        public TResult Handle(TCommand command)
        {
            var validationContext = new ValidationContext(command, null, null);
            Validator.ValidateObject(command, validationContext, validateAllProperties: true);
            return this.decorated.Handle(command);
        }
    }
    

    验证器的一个例子是:

    public class SomeCustomLogicValidator : IValidator {
        void IValidator.ValidateObject(object instance) {
            var context = new ValidationContext(instance, null, null);
            // Throws an exception when instance is invalid.
            Validator.ValidateObject(instance, context, validateAllProperties: true);
        }
    }       
    

    然后将其注册为:

    // using SimpleInjector.Extensions;
    container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(ValidationCommandHandlerDecorator<>));
    

    您可以包装任意多的装饰器,甚至可以使其特定于谓词(确切的语法取决于您使用的DI框架):

    // another decorator
    container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(TransactionCommandHandlerDecorator<>));
    // specific decorator
    container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(AccessValidationCommandHandlerDecorator<>),
    context => !context.ImplementationType.Namespace.EndsWith("Admins"));
    

我的例子使用了一个DI框架,这使事情变得更简单,但这个想法可以在不使用任何DI容器的情况下进行扩展。

我通常在应用程序层(如命令处理程序)和域层中使用FluentValidation。这些验证器都会抛出异常,我在全局异常处理程序中捕捉到这些异常,该处理程序有责任以正确的格式将它们传播给使用者(例如WCF中的错误)。这些消息已经使用了正确的语言,基于线程上设置的区域性(如果您有多语言网站)。

然后在网站上使用错误列表。错误消息只是简单地显示,基于错误键,我可以添加额外的逻辑来禁用控制等。

因此,在我的案例中,验证在大多数情况下是服务器端的,并且只在应用程序和域层中定义一次。在客户端,可以有一些其他小的输入验证,例如限制用户输入。

我不确定你是否在使用数据注释,但使用数据注释可以是这样的。另请参阅其他属性ValidateAntiForgeryToken(可能对您有用)。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateUser(CreateUserViewModel createUserViewModel)
{
    if (ModelState.IsValid)
    {
        CreateSystemUserCommand createSystemUserCommand = new CreateSystemUserCommand()
        {
            Firstname = createUserViewModel.Forename,
            Surname = createUserViewModel.Surname,
            Username = createUserViewModel.Username,
            Password = createUserViewModel.Password
        };
        CreateSystemUserCommandHandler handler = new CreateSystemUserCommandHandler();
        handler.Execute(createSystemUserCommand);
        return RedirectToAction("ViewUsers");
    }
    return View(createUserViewModel);
}

但如果你需要复杂的验证,你可以使用:

if (ModelState.IsValid && handler.Validate())

或者,您可以实现自己的验证逻辑,然后使用ModelState.AddModelErrorModelState添加错误。