【问题标题】:How to properly separate model validation from Controller into Service?如何正确地将模型验证从控制器分离到服务中?
【发布时间】:2014-03-06 11:28:26
【问题描述】:

我正在重构我正在进行的项目。在我现有的控制器中,我确实使用了存储库模式,但我仍然执行了太多的脚手架,而不是我觉得舒服的。那和我的一些控制器可能有 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、配置、视图模型和部分视图模型
  • Data.Repositories:包含我所有与 DbContext 对话的存储库。由于 EF 本身就是一个伪存储库,因此我正在利用我的“存储库”作为查询数据的更具体的方式。例如:FindTechnicians()FindActive()
  • Data.Services:包含我将使用的所有服务。这些服务将注入一个或多个存储库,并在我将完整的视图模型传回控制器之前执行我需要完成的所有逻辑。
  • Identity:包含我的 ASP.NET Identity 实现。
  • Web.Private:包含实际的 MVC 项目。

【问题讨论】:

  • 您的服务有多少存储库依赖项?你是刚刚解决了依赖过度注入的问题,还是解决了?
  • 我只是将它移到服务层,因为我知道我必须对一些存储库的结果做一些更复杂的事情,然后然后将完成的模型传递到控制器。

标签: c# asp.net-mvc entity-framework asp.net-mvc-5 entity-framework-6


【解决方案1】:

如果您还没有阅读,请阅读以下 2 篇文章:

您的问题的答案是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 个接口依赖项。

【讨论】:

  • 我今天早些时候看了第一篇,第二篇没看过,主要是因为史蒂夫的文章比较长。就您所引用的 FluentValidation.NET 而言,它与 EF Fluent API 有何不同?如果我使用 EF Fluent API,它还有什么好处?
  • EF fluent API 用于将关系模式映射到概念对象模型,其验证很少。您可以将字段设为必填并指定最大长度,但仅此而已。使用上述内容,您的验证者会进行检查以确保您的所有其他业务规则都没有被破坏。在 SQL 中,您必须使用触发器来执行此操作,而 EF 没有流畅的 API。
猜你喜欢
  • 1970-01-01
  • 2015-08-18
  • 2013-09-06
  • 1970-01-01
  • 2014-08-05
  • 2023-03-25
  • 1970-01-01
  • 2016-09-01
  • 1970-01-01
相关资源
最近更新 更多