如何正确地将模型验证从控制器分离到服务



我正在重新构建我正在处理的项目。在我现有的控制器中,我确实使用了存储库模式,但我仍然执行了太多的脚手架,这让我感到不舒服。这和我的一些控制器可能有10多个存储库传入(通过Ninject)。因此,我决定引入一个服务层,我的意图是每个控制器有一个服务,而每个服务将有多个存储库注入其中,并完成我需要的工作。到目前为止,这很有效,但我遇到了一些困惑:如何将模型验证从控制器转移到服务层?

例如,在我的OfficesController:上看看这个Edit方法

[HttpPost]
public async Task<RedirectToRouteResult> Edit(
short id,
FormCollection form,
[Bind(Prefix = "Office.Coordinates", Include = "Latitude,Longitude")] Coordinate[] coordinates) {
if (id > 0) {
Office office = await this.OfficesService.GetOfficeAsync(id);
if ((office != null)
&& base.TryUpdateModel(office, "Office", new string[2] {
"Name",
"RegionId"
}, form)
&& base.ModelState.IsValid) {
this.OfficesService.UpdateOfficeAsync(office, coordinates);
}
return base.RedirectToAction("Edit", new {
id = id
});
}
return base.RedirectToAction("Default");
}

与控制器的方法相比,它的问题在于,我仍然从数据库中获取Office对象,进行更新、验证,然后再次保存。在这种情况下,复杂性增加而不是减少。以前,我在方法中调用存储库,现在我调用调用存储库的服务来执行相同的功能。到目前为止,这种复杂性的增加只在我的Edit方法中表现出来,其他地方的复杂性都大幅下降,这正是我想要的。

那么,什么是将验证从控制器转移到服务的正确方法呢?欢迎推荐!

作为参考,以下是我的项目的结构:

  • 数据:包含我的所有模型类
  • Data.Google.Maps:包含反序列化特定Kml所需的所有类
  • Data.Models:包含我的DbContext、配置、视图模型和部分视图模型
  • 数据.存储库:包含所有与DbContext对话的存储库。由于EF本身就是一个伪存储库,我正在利用我的"存储库"作为一种更具体的数据查询方式。例如:FindTechnicians()FindActive()
  • Data.Services:包含我将使用的所有服务。服务将有一个或多个存储库注入其中,并在将完成的视图模型传递回控制器之前执行我需要完成的所有逻辑
  • 标识:包含我的ASP.NET标识实现
  • Web.Private:包含实际的MVC项目

如果你还没有读过以下两篇文章,你应该读:

  • https://cuttingedge.it/blogs/steven/pivot/entry.php?id=91
  • https://cuttingedge.it/blogs/steven/pivot/entry.php?id=92

问题的答案是FluentValidation.NET和依赖项修饰。

有了它,你可以做这样的事情:

private readonly IExecuteCommands _commands;
[HttpPost]
public async Task<RedirectToRouteResult> Edit(short id, UpdateOffice command) {
// with FV.NET plugged in, if your command validator fails,
// ModelState will already be invalid
if (!ModelState.IsValid) return View(command);
await _commands.Execute(command);
return RedirectToAction(orWhateverYouDoAfterSuccess);
}

该命令只是一个普通的DTO,就像视图模型一样。可能看起来像这样:

public class UpdateOffice
{
public int OfficeId { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
}

和神奇的验证器:

public class ValidateUpdateOfficeCommand : AbstractValidator<UpdateOffice>
{
public ValidateUpdateOfficeCommand(DbContext dbContext)
{
RuleFor(x => x.OfficeId)
.MustFindOfficeById(dbContext);
RuleFor(x => x.RegionId)
.MustFindRegionById(dbContext);
RuleFor(x => x.Name)
.NotEmpty()
.Length(1, 200)
.MustBeUniqueOfficeName(dbContext, x => x.OfficeId);
}
}

这些验证规则中的每一个都将在您的操作方法执行之前运行,前提是您已经为依赖注入设置了验证器,并且您正在使用FV MVC验证提供程序。如果存在验证错误,ModelState.IsValid将为false。

您还刚刚解决了控制器和(可能)服务层中的过度注入问题。您可以运行任何查询,执行任何命令,或验证任何只有3个接口依赖项的对象。

最新更新