使用ViewModels和业务逻辑验证的ASP.NET MVC FluentValidation

时间:2013-04-07 01:27:57

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

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

我愿意使用完全不同的API,但实质上是希望解决这种分支验证策略。

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

示例代码:

[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.
}

2 个答案:

答案 0 :(得分:2)

对问题进行一些封闭(并使其可接受)以及总结评论:

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

话虽这么说,分离出来的过程(检查,错误处理等 - 与UI无关的任何事情)都可以在服务层完成,而服务层也往往会保留应用程序DRY。然后,动作仅负责呼叫和呈现而不执行实际的工作单元。 (另外,如果您的应用程序中的各种操作使用类似的逻辑,那么检查都在一个位置而不是在操作之间一起抛出。(我记得检查过客户表中是否有条目?)

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

就冒泡错误而言,我通常有一个基本异常,它横穿每一层然后我可以根据目的扩展它。你可以轻松地使用Enums,Booleans,out参数,或者只是一个布尔值(Rsvp已被接受或未被接受)。它只取决于用户纠正问题所需的响应有限,或者可能更改工作流程,以便错误不是问题或用户需要更正的问题。

答案 1 :(得分:0)

您可以在流畅的验证中使用整个验证逻辑:

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;
    }
}