MVC3输入相关验证

本文关键字:验证 输入 MVC3 | 更新日期: 2023-09-27 18:24:38

注意:我对MVC3还比较陌生。
这个框架的输入验证似乎很好,你只需要说[必需],客户端和服务器端的验证都可以从那里开始工作。但是如果我想实现条件验证呢?

场景:我将有一个下拉框,要求您从两个选项中选择一个。如果选择选项1,将显示2个文本输入字段,并且这两个字段都是必需的。如果选择了选项2,将出现2个单选按钮,您需要选择其中一个。MVC3验证如何实现这一点?

显然,在模型中,我们不能只进行标准要求的验证,因为根据我们选择的下拉选项,某些字段不会被提交。

MVC3输入相关验证

使用这个框架,输入验证似乎非常好

真的吗?您描述的场景是使用数据注释进行验证的局限性的一个完美示例。

我将尝试探索3种可能的技术。转到这个答案的末尾,然后是我使用和推荐的第三种技巧。

在开始探索它们之前,让我展示将用于3个场景的控制器和视图,因为它们将是相同的。

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }
    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

视图:

@model MyViewModel
@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.SelectedOption)
        @Html.DropDownListFor(
            x => x.SelectedOption, 
            Model.Options, 
            "-- select an option --", 
            new { id = "optionSelector" }
        )
        @Html.ValidationMessageFor(x => x.SelectedOption)
    </div>
    <div id="inputs"@Html.Raw(Model.SelectedOption != "1" ? " style='"display:none;'"" : "")>
        @Html.LabelFor(x => x.Input1)   
        @Html.EditorFor(x => x.Input1)
        @Html.ValidationMessageFor(x => x.Input1)
        @Html.LabelFor(x => x.Input2)
        @Html.EditorFor(x => x.Input2)
        @Html.ValidationMessageFor(x => x.Input2)
    </div>
    <div id="radios"@Html.Raw(Model.SelectedOption != "2" ? " style='"display:none;'"" : "")>
        @Html.Label("rad1", "Value 1")
        @Html.RadioButtonFor(x => x.RadioButtonValue, "value1", new { id = "rad1" })
        @Html.Label("rad2", "Value 2")
        @Html.RadioButtonFor(x => x.RadioButtonValue, "value2", new { id = "rad2" })
        @Html.ValidationMessageFor(x => x.RadioButtonValue)
    </div>
    <button type="submit">OK</button>
}

脚本:

$(function () {
    $('#optionSelector').change(function () {
        var value = $(this).val();
        $('#inputs').toggle(value === '1');
        $('#radios').toggle(value === '2');
    });
});

这里没什么特别的。一个控制器,用于实例化传递给视图的视图模型。在视图中,我们有一个表单和一个下拉列表。使用javascript,我们订阅该dropdownlisty的更改事件,并根据所选值切换该表单的不同区域。


可能性1

第一种可能性是让视图模型实现IValidatableObject。请记住,如果您决定在视图模型上实现此接口,则不应在视图模型属性上使用任何验证属性,否则Validate方法将永远不会被调用:

public class MyViewModel: IValidatableObject
{
    public string SelectedOption { get; set; }
    public IEnumerable<SelectListItem> Options
    {
        get
        {
            return new[]
            {
                new SelectListItem { Value = "1", Text = "item 1" },
                new SelectListItem { Value = "2", Text = "item 2" },
            };
        }
    }
    public string RadioButtonValue { get; set; }
    public string Input1 { get; set; }
    public string Input2 { get; set; }
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (SelectedOption == "1")
        {
            if (string.IsNullOrEmpty(Input1))
            {
                yield return new ValidationResult(
                    "Input1 is required", 
                    new[] { "Input1" }
                );
            }
            if (string.IsNullOrEmpty(Input2))
            {
                yield return new ValidationResult(
                    "Input2 is required",
                    new[] { "Input2" }
                );
            }
        }
        else if (SelectedOption == "2")
        {
            if (string.IsNullOrEmpty(RadioButtonValue))
            {
                yield return new ValidationResult(
                    "RadioButtonValue is required",
                    new[] { "RadioButtonValue" }
                );
            }
        }
        else
        {
            yield return new ValidationResult(
                "You must select at least one option", 
                new[] { "SelectedOption" }
            );
        }
    }
}

这种方法的好处在于,您可以处理任何复杂的验证场景。这种方法的缺点是它不太可读,因为我们将验证与消息和错误输入字段名称选择混合在一起。


可能性2

另一种可能性是编写自定义验证属性,如[RequiredIf]:

[AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
public class RequiredIfAttribute : RequiredAttribute
{
    private string OtherProperty { get; set; }
    private object Condition { get; set; }
    public RequiredIfAttribute(string otherProperty, object condition)
    {
        OtherProperty = otherProperty;
        Condition = condition;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(OtherProperty);
        if (property == null)
            return new ValidationResult(String.Format("Property {0} not found.", OtherProperty));
        var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
        var conditionIsMet = Equals(propertyValue, Condition);
        return conditionIsMet ? base.IsValid(value, validationContext) : null;
    }
}

然后:

public class MyViewModel
{
    [Required]
    public string SelectedOption { get; set; }
    public IEnumerable<SelectListItem> Options
    {
        get
        {
            return new[]
            {
                new SelectListItem { Value = "1", Text = "item 1" },
                new SelectListItem { Value = "2", Text = "item 2" },
            };
        }
    }
    [RequiredIf("SelectedOption", "2")]
    public string RadioButtonValue { get; set; }
    [RequiredIf("SelectedOption", "1")]
    public string Input1 { get; set; }
    [RequiredIf("SelectedOption", "1")]
    public string Input2 { get; set; }
}

这种方法的好处在于我们的视图模型是干净的。糟糕的是,使用自定义验证属性可能会很快达到极限。例如,考虑更复杂的场景,在这些场景中,您需要递归到子模型、集合等。这会很快变得一团糟。


可能性3

第三种可能性是使用FluentValidation.NET。这是我个人使用和推荐的。

因此:

  1. NuGet控制台中的Install-Package FluentValidation.MVC3
  2. Global.asaxApplication_Start中添加以下行:

    FluentValidationModelValidatorProvider.Configure();
    
  3. 为视图模型编写一个验证器:

    public class MyViewModelValidator : AbstractValidator<MyViewModel>
    {
        public MyViewModelValidator()
        {
            RuleFor(x => x.SelectedOption).NotEmpty();
            RuleFor(x => x.Input1).NotEmpty().When(x => x.SelectedOption == "1");
            RuleFor(x => x.Input2).NotEmpty().When(x => x.SelectedOption == "1");
            RuleFor(x => x.RadioButtonValue).NotEmpty().When(x => x.SelectedOption == "2");
        }
    }
    
  4. 视图模型本身就是一个POCO:

    [Validator(typeof(MyViewModelValidator))]
    public class MyViewModel
    {
        public string SelectedOption { get; set; }
        public IEnumerable<SelectListItem> Options
        {
            get
            {
                return new[]
                {
                    new SelectListItem { Value = "1", Text = "item 1" },
                    new SelectListItem { Value = "2", Text = "item 2" },
                };
            }
        }
        public string RadioButtonValue { get; set; }
        public string Input1 { get; set; }
        public string Input2 { get; set; }
    }
    

这样做的好处是,我们在验证和视图模型之间有一个完美的分离。它与ASP.NET MVC很好地集成在一起。我们可以以一种非常简单和流畅的方式孤立地对验证器进行单元测试。

糟糕的是,当微软设计ASP.NET MVC时,他们选择了声明性验证逻辑(使用数据注释),而不是命令式验证逻辑,后者更适合验证场景,并且可以处理任何事情。FluentValidation.NET实际上并不是在ASP.NET MVC中执行验证的标准方式,这很糟糕。