发送 IEnumerable<T> 时对 EditorTemplate 的客户端非侵入式验证
Client-side unobtrusive validation for EditorTemplate when sending IEnumerable<T>
有可能是我无法通过搜索找到解决方案,或者找不到解决方案。也许我措辞不当,但我的问题是当我向它传递 IEnumerable<T>
时,试图让客户端不引人注目的验证在 EditorTemplate
上触发。我的设置:
ParentModel.cs
[Validator(typeof(ParentModelValidator))]
public class ParentModel
{
...
public IEnumerable<ChildModel> ChildModels { get; set; }
}
public class ParentModelValidator : AbstractValidator<ParentModel>
{
public ParentModelValidator()
{
RuleFor(x => x.ChildModels).SetCollectionValidator(new ChildModelValidator());
}
}
ChildModel.cs
[Validator(typeof(ChildModelValidator))]
public class ChildModel
{
public bool IsRequired { get; set; }
public string foo { get; set; }
}
public class ChildModelValidator : AbstractValidator<ChildModel>
{
public ChildModelValidator ()
{
RuleFor(x => x.foo)
.NotEmpty().When(x => x.IsRequired);
}
}
ParentShell.cshtml
@model ParentModel
@using (Html.BeginForm("Index", "Application", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.Partial("_Parent", Model)
@Html.EditorFor(m => m.ChildModels)
<input type="submit" value="submit" />
}
_Parent
部分仅包含一些常见的、可重复使用的 @Html.TextBoxFor(m => m.bar)
和 @Html.ValidationMessageFor(m => m.bar)
字段。
ChildModel.cshtml EditorTemplate
@model ChildModel
@Html.TextBoxFor(m => m.foo)
@if (Model.IsRequired)
{
@Html.ValidationMessageFor(m => m.foo)
}
客户端验证针对 _Parent
部分中的所有字段触发,但当 IsRequired
为真并且应该有 ValidationMessageFor
时我什么也得不到。这是接收 IEnumerable<T>
的 EditorTemplate
客户端非侵入式验证的已知约束吗?是否由于在渲染过程中插入了索引器(ChildModels[0].foo
和 ChildModels_0__.foo
)?
的文档
Note that FluentValidation will also work with ASP.NET MVC's client-side validation, but not all rules are supported. For example, any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side
因为您使用了 .When
条件,您将不会获得客户端验证。
使用 foolproof [RequiredIfTrue]
等替代属性将适用于简单的 属性,但不适用于复杂的对象或集合。
您可以通过创建自己的实现 IClientValidatable
的自定义 ValidationAttribute
来解决此问题
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ComplexRequiredIfTrue : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "The {0} field is required.";
public string OtherProperty { get; private set; }
public ComplexRequiredIfTrue(string otherProperty) : base(_DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
PropertyInfo otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty);
bool isRequired = (bool)otherProperty.GetValue(validationContext.ObjectInstance, null);
if (isRequired)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "complexrequirediftrue"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
}
和关联的脚本
function nameToIndex (value) {
return value.replace(/[\[\].]/g, '_');
}
(function ($) {
$.validator.addMethod("complexrequirediftrue", function (value, element, params) {
// We need to get the prefix of the control we are validating
// so we can get the corresponding 'other property'
var name = $(element).attr('name');
var index = name.lastIndexOf('.');
var prefix = nameToIndex(name.substr(0, index + 1));
var otherProp = $('#' + prefix + params);
if (otherProp.val() == "True" && !value) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("complexrequirediftrue", "otherproperty");
}(jQuery));
然后应用到你身上属性
public class ChildModel
{
public bool IsRequired { get; set; }
[ComplexRequiredIfTrue("IsRequired")]
public string foo { get; set; }
}
并在 EditorTemplate
中包含 @Html.HiddenFor(m => m.IsRequired)
@model ChildModel
@Html.HiddenFor(m => m.IsRequired)
@Html.TextBoxFor(m => m.foo)
@Html.ValidationMessageFor(m => m.foo)
编辑:进一步评论,如果控制器是
model.ChildModels = new List<ChildModel>() { new ChildModel() { IsRequired = true }, new ChildModel() };
return View(model);
那么点击提交按钮时生成的html为:
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_0__IsRequired" name="ChildModels[0].IsRequired" type="hidden" value="True">
<input class="input-validation-error" data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_0__foo" name="ChildModels[0].foo" type="text" value="">
<span class="field-validation-error" data-valmsg-for="ChildModels[0].foo" data-valmsg-replace="true">The foo field is required.</span>
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_1__IsRequired" name="ChildModels[1].IsRequired" type="hidden" value="False">
<input data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_1__foo" name="ChildModels[1].foo" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="ChildModels[1].foo" data-valmsg-replace="true"></span>
请注意表单未提交,第一个文本框显示了错误消息
有可能是我无法通过搜索找到解决方案,或者找不到解决方案。也许我措辞不当,但我的问题是当我向它传递 IEnumerable<T>
时,试图让客户端不引人注目的验证在 EditorTemplate
上触发。我的设置:
ParentModel.cs
[Validator(typeof(ParentModelValidator))]
public class ParentModel
{
...
public IEnumerable<ChildModel> ChildModels { get; set; }
}
public class ParentModelValidator : AbstractValidator<ParentModel>
{
public ParentModelValidator()
{
RuleFor(x => x.ChildModels).SetCollectionValidator(new ChildModelValidator());
}
}
ChildModel.cs
[Validator(typeof(ChildModelValidator))]
public class ChildModel
{
public bool IsRequired { get; set; }
public string foo { get; set; }
}
public class ChildModelValidator : AbstractValidator<ChildModel>
{
public ChildModelValidator ()
{
RuleFor(x => x.foo)
.NotEmpty().When(x => x.IsRequired);
}
}
ParentShell.cshtml
@model ParentModel
@using (Html.BeginForm("Index", "Application", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.Partial("_Parent", Model)
@Html.EditorFor(m => m.ChildModels)
<input type="submit" value="submit" />
}
_Parent
部分仅包含一些常见的、可重复使用的 @Html.TextBoxFor(m => m.bar)
和 @Html.ValidationMessageFor(m => m.bar)
字段。
ChildModel.cshtml EditorTemplate
@model ChildModel
@Html.TextBoxFor(m => m.foo)
@if (Model.IsRequired)
{
@Html.ValidationMessageFor(m => m.foo)
}
客户端验证针对 _Parent
部分中的所有字段触发,但当 IsRequired
为真并且应该有 ValidationMessageFor
时我什么也得不到。这是接收 IEnumerable<T>
的 EditorTemplate
客户端非侵入式验证的已知约束吗?是否由于在渲染过程中插入了索引器(ChildModels[0].foo
和 ChildModels_0__.foo
)?
Note that FluentValidation will also work with ASP.NET MVC's client-side validation, but not all rules are supported. For example, any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side
因为您使用了 .When
条件,您将不会获得客户端验证。
使用 foolproof [RequiredIfTrue]
等替代属性将适用于简单的 属性,但不适用于复杂的对象或集合。
您可以通过创建自己的实现 IClientValidatable
ValidationAttribute
来解决此问题
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ComplexRequiredIfTrue : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "The {0} field is required.";
public string OtherProperty { get; private set; }
public ComplexRequiredIfTrue(string otherProperty) : base(_DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
PropertyInfo otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty);
bool isRequired = (bool)otherProperty.GetValue(validationContext.ObjectInstance, null);
if (isRequired)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "complexrequirediftrue"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
}
和关联的脚本
function nameToIndex (value) {
return value.replace(/[\[\].]/g, '_');
}
(function ($) {
$.validator.addMethod("complexrequirediftrue", function (value, element, params) {
// We need to get the prefix of the control we are validating
// so we can get the corresponding 'other property'
var name = $(element).attr('name');
var index = name.lastIndexOf('.');
var prefix = nameToIndex(name.substr(0, index + 1));
var otherProp = $('#' + prefix + params);
if (otherProp.val() == "True" && !value) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("complexrequirediftrue", "otherproperty");
}(jQuery));
然后应用到你身上属性
public class ChildModel
{
public bool IsRequired { get; set; }
[ComplexRequiredIfTrue("IsRequired")]
public string foo { get; set; }
}
并在 EditorTemplate
中包含 @Html.HiddenFor(m => m.IsRequired)
@model ChildModel
@Html.HiddenFor(m => m.IsRequired)
@Html.TextBoxFor(m => m.foo)
@Html.ValidationMessageFor(m => m.foo)
编辑:进一步评论,如果控制器是
model.ChildModels = new List<ChildModel>() { new ChildModel() { IsRequired = true }, new ChildModel() };
return View(model);
那么点击提交按钮时生成的html为:
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_0__IsRequired" name="ChildModels[0].IsRequired" type="hidden" value="True">
<input class="input-validation-error" data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_0__foo" name="ChildModels[0].foo" type="text" value="">
<span class="field-validation-error" data-valmsg-for="ChildModels[0].foo" data-valmsg-replace="true">The foo field is required.</span>
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_1__IsRequired" name="ChildModels[1].IsRequired" type="hidden" value="False">
<input data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_1__foo" name="ChildModels[1].foo" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="ChildModels[1].foo" data-valmsg-replace="true"></span>
请注意表单未提交,第一个文本框显示了错误消息