在使用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没有关联,因此返回特定的模型错误是没有意义的。
欢迎有任何想法。我的直觉是做处理者。验证,因为它将把验证逻辑保留在一个类中,感觉是正确的,但我愿意接受建议。
您可能需要两种类型的验证:
-
一种是简单的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.AddModelError
向ModelState
添加错误。