【问题标题】:ASP.NET MVC FluentValidation with ViewModels and Business Logic Validation带有 ViewModel 和业务逻辑验证的 ASP.NET MVC FluentValidation
【发布时间】:2013-04-07 01:27:56
【问题描述】:

我正在探索使用 FluentValidation,因为它似乎是一个优雅的 API,用于在模型绑定时验证我的 ViewModel。我正在寻找有关如何使用此库以及从我的业务(服务)层正确集中验证并将其提升到视图的意见,而无需使用 2 种不同的方法来添加模型状态错误。

我对使用完全不同的 API 持开放态度,但本质上是希望解决这种分支验证策略。

[旁注:我尝试的一件事是将我的业务方法移动到我的 FluentValidation 的自定义 RsvpViewModelValidator 类中并使用 .Must 方法,但在那里隐藏该调用似乎是错误的,因为如果我需要实际使用我的客户对象,他们我将不得不再次重新查询它,因为它超出了范围]

示例代码:

[HttpPost]
public ActionResult AcceptInvitation(RsvpViewModel model)
{
    //FluentValidation has happened on my RsvpViewModel already to check that 
    //RsvpCode is not null or whitespace
    if(ModelState.IsValid)
    {
        //now I want to see if that code matches a customer in my database.
        //returns null if not, Customer object if existing
        customer = _customerService.GetByRsvpCode(model.RsvpCode);
        if(customer == null)
        {
            //is there a better approach to this?  I don't like that I'm
            //splitting up the validation but struggling up to come up with a 
            //better way.
            ModelState.AddModelError("RsvpCode", 
                string.Format("No customer was found for rsvp code {0}", 
                              model.RsvpCode);

            return View(model);
        }

        return this.RedirectToAction(c => c.CustomerDetail());
    }

    //FluentValidation failed so should just display message about RsvpCode 
    //being required
    return View(model);
}

[HttpGet]
public ActionResult CustomerDetail()
{
     //do work.  implementation not important for this question.
}

【问题讨论】:

  • 您可以将这种决策提取到服务层,但我认为您正在寻找白象。不要将业务/流程逻辑与验证逻辑混淆;一个模型可以是有效的,但它在数据库中没有多大意义。
  • 我同意你关于逻辑差异的说法。我想我想知道的是......你认为我上面的代码是正确的还是你会以不同的方式处理事情?我的问题是关于将消息返回到视图以进行验证和业务逻辑的一个广泛开放的“你会做什么”。上面的代码是我能想到的最好的方法。
  • 我一直在使用 N-Tier,所以我可能有一个 invitationService.Accept()(或任何可以描述该过程的服务)方法通过/失败,这就是逻辑将被容纳,但没有服务,下一个最佳位置是在控制器内。就个人而言,我会说你在当时的情况下已经做到了最好。
  • 好的,我的目标是构建一个能够很好地分离关注点的架构(因此_customerService)。该代码基于一个示例问题,但我并不局限于此,因为我只是为这个问题编写的。因此,使用您的invitationService.Accept() 示例,这将是无效的并引发异常,还是您会从中返回类似IList<Error> 的内容以填充ModelState 或其他内容。只是多动一下你的大脑。抱歉,我不是想把它拖出去。
  • 如果我介意问答,我不会是这里的常客,所以请走开! - 为了回答你的问题,我会根据方法的作用而反弹。有些返回一个枚举状态(几乎就像成员创建),有些返回一个带有状态码的 int,有些有out 参数,还有一些抛出异常。 (这是我做过的各种一次性项目)。我个人认为拥有一个可以产生验证异常的ProjectNameException 基础是更好的方法(通常在 infra/core 库中)。

标签: asp.net-mvc-4 business-logic fluentvalidation modelstate


【解决方案1】:

结束问题(并使其可以接受)并总结 cmets:

业务/流程逻辑和验证逻辑是两个实体。除非验证与数据库相关(例如检查唯一条目),否则没有理由将验证分组到一个位置。有些人负责模型中的信息,确保信息没有任何无效,有些人负责处理验证值在系统中的使用方式。考虑一下属性 getter/setter 与具有这些属性的方法中使用的逻辑。

话虽如此,分离流程(检查、错误处理等——任何与 UI 无关的)都可以在服务层中完成,这也倾向于保持应用程序DRY。然后动作只负责调用和呈现,而不是执行实际的工作单元。 (此外,如果您的应用程序中的各种操作使用类似的逻辑,则检查都在一个位置,而不是在操作之间放在一起。(我记得检查客户表中是否有一个条目吗?)

此外,通过将其分解为层,您可以保持关注点的模块化和可测试性。 (接受 RSVP 不依赖于 UI 中的操作,但现在它是服务中的一个方法,可以由该 UI 或移动应用程序调用)。

就冒泡错误而言,我通常有一个跨越每一层的基本异常,然后我可以根据目的对其进行扩展。您可以很容易地使用枚举、布尔值、out 参数,或者只是一个布尔值(Rsvp 要么被接受,要么不被接受)。这仅取决于用户纠正问题所需的响应有多有限,或者可能更改工作流程以使错误不是问题或用户需要纠正的东西。

【讨论】:

    【解决方案2】:

    您可以在流畅的验证中拥有整个验证逻辑:

    public class RsvpViewValidator : AbstractValidator<RsvpViewModel>
    {
        private readonly ICustomerService _customerService = new CustomerService();
        public RsvpViewValidator()
        {
            RuleFor(x => x.RsvpCode)
                .NotEmpty()
                .Must(BeAssociatedWithCustomer)
                .WithMessage("No customer was found for rsvp code {0}", x => x.RsvpCode)
        }
    
        private bool BeAssociatedWithCustomer(string rsvpCode)
        {
            var customer = _customerService.GetByRsvpCode(rsvpCode);
            return (customer == null) ? false : true;
        }
    }
    

    【讨论】:

    • 这是我最初的问题的一部分,也是我做过的一种方法(参见“旁注”)。我同意它在技术上有效,但从最佳实践的角度来看,它并不适合我。我担心遵循这种模式可能会将过多的业务逻辑/流程泄漏到我的 ViewModel 验证器中,只是为了将消息发送到 ModelState 中,因此很难决定哪些调用去哪里了。如果您想分享更多信息,我愿意倾听您的想法。
    猜你喜欢
    • 2023-04-01
    • 2011-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-10
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多