ViewModel使用IValidatableObject进行嵌套并具有依赖于业务逻辑的验证

时间:2014-07-10 17:59:25

标签: c# asp.net-mvc validation

目前我正在提交一份付款表单,在此付款表单上可以切换是否显示credit card validation。显示CVV字段取决于某些配置,这取决于应用程序中几个层中的某些业务逻辑(在我的实例中,某些支付网关没有/需要CVV)。

表单显示以下字段:

  • 持卡人姓名
  • 卡号
  • 卡片类型(Visa,美国运通卡,万事达卡等下拉列表)
  • 到期日
  • CVV取决于数据库中的配置设置,各不相同

模型(Validate方法的问题)

public class IndexViewModel
{
    public IEnumerable<CardType> CardTypes { get; set; }
    public bool IsCvvEnabled { get; set; }
    public PayProcess { get; set; }
}

public class ProcessViewModel : IValidatableObject
{
    public string CardHolder { get; set; }
    public string CardNumber { get; set; }
    public string CardType { get; set; }
    public string ExpiryDate { get; set; }
    public string Cvv { get; set; }

    public IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext)
    {
        // validation of properties CardHolder, Number, Type, etc...

        // how do I read this value?
        if (IsCvvEnabled) 
        {
            int tempCvv;

            if (string.IsNullOrEmpty(Cvv)) 
            {
                yield return new ValidationResult(Index.CVVRequired, 
                    new[] { "CVV" });
            }
            else if (!int.TryParse(Cvv, out tempCvv))
            {
                yield return new ValidationResult(Index.CVVInvalid, 
                     new[] { "CVV" });
            }
        }
    }
}

视图

该视图如下所示:

<section id="aligned">
    <h3>@ViewRes.Index.Header</h3>

    @Html.ValidationMessageFor(m => m.PayProcess.CreditCardType)
    @Html.LabelFor(x => x.PayProcess.CreditCardType, 
        "Card Type")
    @Html.DropDownListFor(m => m.PayProcess.CreditCardType, 
        Model.CreditCardTypes)

    <!-- the other fields ... -->

    @if (Model.PayProcess.IsCvvEnabled)
    {                            
        @Html.ValidationMessageFor(m => m.PayProcess.Cvv)
        @Html.LabelFor(x => x.PayProcess.Cvv, 
            "Card Verification")
        @Html.TextBoxFor(m => m.PayProcess.Cvv)
    }
</section>

控制器

public ActionResult Index()
{
    var indexViewModel = CreateIndexViewModel(new ProcessViewModel());

    return View(model);
}

private PayIndexViewModel CreateIndexViewModel(ProcessViewModel processViewModel)
{
    if (!ModelState.IsValid)
    {
        return View("Index", CreateIndexViewModel(processViewModel));
    }

    // handle success scenario
}

private IndexViewModel CreateIndexViewModel(ProcessViewModel processViewModel)
{
    var isCvvEnabled = _someDependency.Gateway.SupportsCvv;
    var cardTypes = _someDependency.Gateway.GetSupportedCardTypes
                        .Select(x => new SelectListItem { 
                            Text = x.Name, 
                            Value = x.ID });

    return new IndexViewModel
    {
        CardTypes = cardTypes,
        IsCvvEnabled = isCvvEnabled,
        PayProcess = processViewModel
    };
}

问题

ProcessViewModel仅包含用户提交的表单输入值,但不包含IsCvvEnabled,因为用户未提交该值。

如果验证需要此上下文信息,如何正确执行此验证?

1 个答案:

答案 0 :(得分:1)

如果用户没有在表单中提交值,那么您必须在帖子操作上手动设置它,或者您可以在验证属性之前设置自定义模型绑定器以设置属性。然而,对于像这样微不足道的事情,我认为这太过分了。

就我个人而言,我实现你想要的方法的首选方法是将你的验证方法重构为一个接收ModelState的方法,然后你自己在你的帖子行动中调用它:

viewModel.IsCvvEnabled = _someDependency.Gateway.SupportsCvv;

viewModel.Validate(this.ModelState);

if(!ModelState.IsValid)
{
    return View(viewModel);
}

您必须将IsCvvEnabled属性放入ProcessViewModel。