如何将模型验证与Controller正确分离为服务?

时间:2014-02-09 04:00:22

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

我正在重新考虑我正在进行的项目。在我现有的控制器中,我确实使用了存储库模式,但我仍然执行了一些比我觉得舒服的脚手架更多的脚手架。那个和我的一些控制器可以传入10个以上的存储库(通过Ninject)。所以,我决定引入一个服务层,我的目的是为每个控制器提供一个服务,而每个服务都会将多个存储库注入其中并完成我需要的工作。到目前为止,这种方法很有效,但我遇到了各种各样的混乱:如何将模型验证从控制器移到服务层?

例如,在我的Edit上查看此OfficesController方法:

[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 :包含我将使用的所有服务。在将完整的视图模型传递回控制器之前,服务将注入一个或多个存储库并执行我需要完成的所有逻辑。
  • 身份:包含我的ASP.NET身份实现。
  • Web.Private :包含实际的MVC项目。

1 个答案:

答案 0 :(得分:2)

如果您还没有,请阅读以下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,就像一个viewmodel。可能看起来像这样:

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个接口依赖项的任何对象。