注意:我对MVC3比较陌生 对于这个框架,输入验证似乎相当不错,你只需说[必需],客户端和服务器端验证就可以在那里工作。但是如果我想实现条件验证呢?
场景:我将有一个保管箱,要求您从2个选项中选择一个。如果选择了选项1,则会出现2个文本输入字段,两者都是必需的。如果选择了选项2,则会出现2个单选按钮,您需要选择其中一个。 MVC3验证如何实现这一目标?
显然,在模型中我们不能只进行标准的必需验证,因为根据我们选择的下拉选项,某些字段不会被提交。
答案 0 :(得分:18)
这个框架的输入验证似乎相当不错
真的?您描述的场景是使用数据注释进行验证的局限性的完美示例。
我将尝试探索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的change事件,并根据所选值切换此表单的不同区域。
第一种可能性是让您的视图模型实现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" }
);
}
}
}
这种方法的优点在于您可以处理任何复杂的验证方案。这种方法有什么不好之处在于它不太可读,因为我们将验证与消息和错误输入字段名称选择混合在一起。
另一种可能性是编写自定义验证属性,如[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; }
}
这种方法有什么好处,我们的视图模型很干净。这有什么不好的地方,使用自定义验证属性可能会很快达到极限。例如,考虑更复杂的场景,您需要将其递归到子模型和集合以及其他东西。这很快就会变得一团糟。
第三种可能性是使用FluentValidation.NET。这是我个人使用和推荐的内容。
所以:
Install-Package FluentValidation.MVC3
在Application_Start
的{{1}}中添加以下内容:
Global.asax
为视图模型编写验证器:
FluentValidationModelValidatorProvider.Configure();
视图模型本身就是一个POCO:
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");
}
}
这有什么好处,我们在验证和视图模型之间有一个完美的分离。它integrates nicely with ASP.NET MVC。我们可以以非常简单和流畅的方式{@ 3}}我们的验证器。
有什么不好的是,当微软设计ASP.NET MVC时,他们选择了声明性验证逻辑(使用数据注释)而不是命令式,这更适合于验证场景并且可以处理任何事情。 FluentValidation.NET实际上并不是标准方式在ASP.NET MVC中执行验证。