ViewModel嵌套并使用IValidatableObject进行业务逻辑相关验证

本文关键字:业务 验证 嵌套 IValidatableObject ViewModel | 更新日期: 2023-09-27 18:12:34

目前我正在呈现一个支付表单,在这个支付表单上可以切换是否显示信用卡验证。显示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" });
            }
        }
    }
}
<<h3>视图/h3>

视图如下所示:

<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,因为用户没有提交该值。

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

ViewModel嵌套并使用IValidatableObject进行业务逻辑相关验证

如果用户没有在表单中提交值,那么您将不得不在post操作中手动设置它,或者您可以创建一个自定义模型绑定器来在验证之前设置该属性。然而,对于这样微不足道的事情,我认为这是太多的工作。

就我个人而言,我更喜欢的方式来实现你想要的,是重构你的验证方法的方法,在一个ModelState,然后你自己调用它在你的post action:

viewModel.IsCvvEnabled = _someDependency.Gateway.SupportsCvv;
viewModel.Validate(this.ModelState);
if(!ModelState.IsValid)
{
    return View(viewModel);
}

你必须把IsCvvEnabled属性放到ProcessViewModel中