我正在探索使用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.
}
答案 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;
}
}