Asp.net mvc 4 ASP.NET MVC FluentValidation与ViewModels和业务逻辑验证

Asp.net mvc 4 ASP.NET MVC FluentValidation与ViewModels和业务逻辑验证,asp.net-mvc-4,business-logic,fluentvalidation,modelstate,Asp.net Mvc 4,Business Logic,Fluentvalidation,Modelstate,我正在探索使用FluentValidation,因为它似乎是一个优雅的API,用于在模型绑定时验证我的ViewModels。我正在寻找关于如何使用此库以及从我的业务(服务)层正确集中验证的意见,并将其提升到视图,而无需使用两种不同的方法来添加modelstate错误 我对使用完全不同的API持开放态度,但本质上希望解决这个分支验证策略 [旁注:我尝试的一件事是将我的业务方法移动到FluentValidation的自定义RsvpViewModelValidator类中,并使用.Must方法,但将调

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

我对使用完全不同的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.
}

您可以在fluent validation中使用整个验证逻辑:

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;
    }
}
公共类RsvpViewValidator:AbstractValidator
{
私有只读ICCustomerService_customerService=新customerService();
公共RsvpViewValidator()
{
规则(x=>x.RsvpCode)
.NotEmpty()
.必须(与客户相关)
.WithMessage(“没有为rsvp代码{0}找到客户”,x=>x.RsvpCode)
}
与客户关联的专用bool(字符串rsvpCode)
{
var customer=_customerService.GetByRsvpCode(rsvpCode);
退货(客户==null)?假:真;
}
}

对问题进行总结(并使其可接受),同时总结评论:

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

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

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


至于冒泡错误,我通常有一个基本异常,它横穿每一层,然后我可以根据目的扩展它。您可以同样轻松地使用enum、Boolean、
out
参数,或者简单地使用Boolean(Rsvp被接受或不被接受)。这取决于用户纠正问题所需的响应有多有限,或者可能会更改工作流程,以使错误不是问题或用户需要纠正的东西。

您可以将此类决策提取到服务层,但我认为您正在寻找白象。不要将业务/流程逻辑与验证逻辑混淆;一个模型可能是有效的,但它在数据库中没有太多意义。我同意你关于逻辑差异的说法。我想我想知道的是…你认为我上面的代码是正确的还是你会用不同的方式处理问题?我的问题是,在将消息返回到视图进行验证和业务逻辑方面,您会怎么做。上面的代码是我能想到的最好的方法。我总是使用N-Tier,所以我可能会有一个
invitationService.Accept()
(或任何类型的服务将描述流程)方法,该方法要么通过要么失败,这就是逻辑所在,但没有服务,下一个最佳位置是控制器内。就我个人而言,考虑到目前的情况,我认为您已经尽了最大的努力。好吧,我的目标是构建一个能够很好地分离关注点的体系结构(因此
\u customerService
)。代码基于一个示例问题,但我并不局限于此,因为我只是为这个问题编写了代码。因此,使用您的
invitationService.Accept()
示例,这是无效的并引发异常,还是返回类似于
IList的内容以填充ModelState或其他内容。只是再多挑一点你的脑子。对不起,我不想把这件事拖出去。如果我介意问答,我就不会是这里的常客了,所以请走开为了回答您的问题,我会根据方法的作用进行反弹。有些返回枚举状态(几乎类似于成员身份创建),有些返回带有状态代码的int,有些具有
out
参数,有些抛出异常。(这是我做过的各种一次性项目)。就我个人而言,我认为有一个
ProjectNameException
库,我可以从中产生验证异常是更好的方法(通常在infra/core库中)。这是我最初问题的一部分,也是我已经做过的一种方法(请参阅“旁注”)。我同意它在技术上是有效的,但从最佳实践的角度来看,它并不适合我。我担心,遵循这种模式可能会将太多的业务逻辑/流程泄漏到我的ViewModel验证器中,只是为了将消息传递到ModelState,因此很难决定调用的位置。如果你想分享更多,我愿意听听你的想法。