MVC3输入相关validation

注意:我对MVC3比较陌生。
对于这个框架,输入validation似乎相当不错,你只需说[必需],客户端和服务器端validation就可以在那里工作。 但是如果我想实现条件validation呢?

场景:我将有一个Dropbox框,要求您选择2个选项之一。 如果选择了选项1,则会出现2个文本输入字段,两者都是必需的。 如果选择了选项2,则会出现2个单选按钮,您需要选择其中一个。 MVC3validation如何实现这一目标?

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

对于这个框架,输入validation似乎相当不错

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

我将尝试探索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()) { 
@Html.LabelFor(x => x.SelectedOption) @Html.DropDownListFor( x => x.SelectedOption, Model.Options, "-- select an option --", new { id = "optionSelector" } ) @Html.ValidationMessageFor(x => x.SelectedOption)
@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)
@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)
}

脚本:

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

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


可能性1

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

 public class MyViewModel: IValidatableObject { public string SelectedOption { get; set; } public IEnumerable 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 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" } ); } } } 

这种方法的优点在于您可以处理任何复杂的validation方案。 这种方法有什么不好之处在于它不太可读,因为我们将validation与消息和错误输入字段名称选择混合在一起。


可能性2

另一种可能性是编写自定义validation属性,如[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 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; } } 

这种方法有什么好处,我们的视图模型很干净。 这有什么不好的地方,使用自定义validation属性可能会很快达到极限。 例如,考虑更复杂的场景,您需要将其递归到子模型和集合以及其他东西。 这很快就会变得一团糟。


可能性3

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

所以:

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

     FluentValidationModelValidatorProvider.Configure(); 
  3. 为视图模型编写validation器:

     public class MyViewModelValidator : AbstractValidator { 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 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; } } 

这有什么好处,我们在validation和视图模型之间有一个完美的分离。 它与ASP.NET MVC很好地集成 。 我们可以以非常简单和流畅的方式单独测试我们的validation器。

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