客户端验证不适用于重复使用和嵌套的复合体 属性
Client side validation not working with reused and nested complex property
我有一个 asp.net MVC 5 应用程序,我在其中尝试在 .cshtml
文件的不同位置重新使用嵌套的复杂视图模型 class。重用的复杂视图模型被命名为 SchoolPersonViewModel
,它有很多属性,Phone
和 Email
属性被验证为 "If Phone is not provided, then Email must be provided. If Phone is provided, then Email is optional input"。我写了一个自定义服务器和客户端验证,但它适用于服务器端。但是 客户端验证 无法正常工作。例如 Email
文本框提示填写,即使关联的 Phone
文本框已填写。请参阅附件。例如。请帮忙。预先谢谢你。
我知道问题来自错误:3 个电子邮件文本框有 3 个验证属性,其值与 data-val-emailrequired-stringphoneprop="Phone"
相同。 运行 时的 Phone
值导致歧义
jQuery 验证机(缺少唯一性)但我不知道如何解决它。请查看下面呈现的属性。请帮忙。提前谢谢你。
我的代码详情:
在我的 cshtml
视图中,我调用视图模型复杂 class (SchoolPersonViewModel
) 3 次:一次用于 Student
,一次用于 Father
和一个 Mother
.
C# MVC 模型 classes
public class SchoolPersonViewModel
{
[DisplayName("Phone")]
public string Phone { get; set; }
[DisplayName("Email")]
[EmailRequired(StringPhonePropertyName = "Phone", ErrorMessage = "Email is required if Phone is not provided")]
public string Email { get; set; }
.... // other properties
}
public class StudentEnrollViewModel
{
public SchoolPersonViewModel Student { get; set; }
public SchoolPersonViewModel Father { get; set; }
public SchoolPersonViewModel Mother { get; set; }
}
验证属性
// If Phone is not input then Email is required -- server and client side validation
public class EmailRequiredAttribute : ValidationAttribute, IClientValidatable
{
public string StringPhonePropertyName { get; set; }
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var phone = ValidatorCommon.GetValue<string>(validationContext.ObjectInstance, StringPhonePropertyName);
var email = (string)value;
if (!string.IsNullOrWhiteSpace(phone) || (string.IsNullOrWhiteSpace(phone) && !string.IsNullOrWhiteSpace(email)))
{
return ValidationResult.Success;
}
if (string.IsNullOrWhiteSpace(phone) && string.IsNullOrWhiteSpace(email))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "emailrequired",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("stringphoneprop", StringPhonePropertyName);
yield return modelClientValidationRule;
}
}
容器视图模型和 jQuery 验证码:
@model ExpandoObjectSerializeDeserialize.Web.Models.StudentEnrollViewModel
....
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
....
@Html.EditorFor(m => m.Student)
....
@Html.EditorFor(m => m.Father)
....
@Html.EditorFor(m => m.Mother)
<input type="submit" value="Create" class="btn btn-default" />
}
@section scripts {
<script type="text/javascript">
jQuery.validator.addMethod('emailrequired', function(value, element, params) {
var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');
var phoneTextboxValue = $('#' + phoneTextboxId).val();
// empty string is evaluated as ‘false’ and non-empty, non-null string is evaluated as ‘true’ in JavaScript
return phoneTextboxValue || value;
});
jQuery.validator.unobtrusive.adapters.add('emailrequired', {}, function(options) {
options.rules['emailrequired'] = true;
options.messages['emailrequired'] = options.message;
});
</script>
}
以上视图在运行ning时间渲染如下:
<div class="col-md-4">
<input class="form-control text-box single-line" id="Father_Phone" name="Father.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Father.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Student_Email" name="Student.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Student.Email" data-valmsg-replace="true"><span for="Student_Email" class="">Email is required if Phone is not provided</span></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line" id="Mother_Phone" name="Mother.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Mother.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Father_Email" name="Father.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Father.Email" data-valmsg-replace="true"><span for="Father_Email" class="">Email is required if Phone is not provided</span></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line" id="Mother_Phone" name="Mother.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Mother.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Mother_Email" name="Mother.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Mother.Email" data-valmsg-replace="true"><span for="Mother_Email" class="">Email is required if Phone is not provided</span></span>
</div>
第一个糟糕的设计决定是您编写的 ValidationAttribute
对特定场景过于具体。您的属性应该是一个简单的 RequiredIfAttribute
,可以在 属性 的值(不是特别是您的“电子邮件” 属性)的值取决于另一个 属性.
在您的情况下,该属性将用作
[RequiredIf("Phone", null, ErrorMessage = "...")]
public string Email { get; set; }
您遇到的下一个问题是客户端脚本以及您没有获得从属 属性 的值这一事实。你的
var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');
returns "Phone"
,因此下面一行
var phoneTextboxValue = $('#' + phoneTextboxId).val();
returns undefined
,(你想要的元素有 id="Father_Phone"
等)这意味着 return phoneTextboxValue || value;
总是 returns false
.
foolproof 提供了一个包含许多常见条件验证属性的库,包括 [RequiredIf]
和 [RequiredIfEmpty]
,两者都适合您的情况。
但如果您想自己编写,那么我推荐 The Complete Guide To Validation In ASP.NET MVC 3 - Part 2 作为一个很好的指南。
RequiredIfAttribute
的代码为
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
#region .Declarations
private const string _DefaultErrorMessage = "Please enter the {0}.";
private readonly string _PropertyName;
private readonly object _Value;
public RequiredIfAttribute(string propertyName, object value)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException("propertyName");
}
_PropertyName = propertyName;
_Value = value;
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
var property = validationContext.ObjectInstance.GetType().GetProperty(_PropertyName);
var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
if (propertyValue != null && propertyValue.Equals(_Value))
{
return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
rule.ValidationParameters.Add("dependentproperty", _PropertyName);
rule.ValidationParameters.Add("targetvalue", _Value);
yield return rule;
}
}
和关联的脚本
sandtrapValidation = {
getDependentElement: function (validationElement, dependentProperty) {
var dependentElement = $('#' + dependentProperty);
if (dependentElement.length === 1) {
return dependentElement;
}
var name = validationElement.name;
var index = name.lastIndexOf(".") + 1;
var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
dependentElement = $('#' + id);
if (dependentElement.length === 1) {
return dependentElement;
}
// Try using the name attribute
name = (name.substr(0, index) + dependentProperty);
dependentElement = $('[name="' + name + '"]');
if (dependentElement.length > 0) {
return dependentElement.first();
}
return null;
}
}
$.validator.addMethod("requiredif", function (value, element, params) {
if ($(element).val() != '') {
// The element has a value so its OK
return true;
}
if (!params.dependentelement) {
return true;
}
var dependentElement = $(params.dependentelement);
if (dependentElement.is(':checkbox')) {
var dependentValue = dependentElement.is(':checked') ? 'True' : 'False';
return dependentValue != params.targetvalue;
} else if (dependentElement.is(':radio')) {
// If its a radio button, we cannot rely on the id attribute
// So use the name attribute to get the value of the checked radio button
var dependentName = dependentElement[0].name;
dependentValue = $('input[name="' + dependentName + '"]:checked').val();
return dependentValue != params.targetvalue;
}
return dependentElement.val() !== params.targetvalue;
});
$.validator.unobtrusive.adapters.add("requiredif", ["dependentproperty", "targetvalue"], function (options) {
var element = options.element;
var dependentproperty = options.params.dependentproperty;
var dependentElement = sandtrapValidation.getDependentElement(element, dependentproperty);
options.rules['requiredif'] = {
dependentelement: dependentElement,
targetvalue: options.params.targetvalue
};
options.messages['requiredif'] = options.message;
});
我有一个 asp.net MVC 5 应用程序,我在其中尝试在 .cshtml
文件的不同位置重新使用嵌套的复杂视图模型 class。重用的复杂视图模型被命名为 SchoolPersonViewModel
,它有很多属性,Phone
和 Email
属性被验证为 "If Phone is not provided, then Email must be provided. If Phone is provided, then Email is optional input"。我写了一个自定义服务器和客户端验证,但它适用于服务器端。但是 客户端验证 无法正常工作。例如 Email
文本框提示填写,即使关联的 Phone
文本框已填写。请参阅附件。例如。
我知道问题来自错误:3 个电子邮件文本框有 3 个验证属性,其值与 data-val-emailrequired-stringphoneprop="Phone"
相同。 运行 时的 Phone
值导致歧义
jQuery 验证机(缺少唯一性)但我不知道如何解决它。请查看下面呈现的属性。请帮忙。提前谢谢你。
我的代码详情:
在我的 cshtml
视图中,我调用视图模型复杂 class (SchoolPersonViewModel
) 3 次:一次用于 Student
,一次用于 Father
和一个 Mother
.
C# MVC 模型 classes
public class SchoolPersonViewModel
{
[DisplayName("Phone")]
public string Phone { get; set; }
[DisplayName("Email")]
[EmailRequired(StringPhonePropertyName = "Phone", ErrorMessage = "Email is required if Phone is not provided")]
public string Email { get; set; }
.... // other properties
}
public class StudentEnrollViewModel
{
public SchoolPersonViewModel Student { get; set; }
public SchoolPersonViewModel Father { get; set; }
public SchoolPersonViewModel Mother { get; set; }
}
验证属性
// If Phone is not input then Email is required -- server and client side validation
public class EmailRequiredAttribute : ValidationAttribute, IClientValidatable
{
public string StringPhonePropertyName { get; set; }
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var phone = ValidatorCommon.GetValue<string>(validationContext.ObjectInstance, StringPhonePropertyName);
var email = (string)value;
if (!string.IsNullOrWhiteSpace(phone) || (string.IsNullOrWhiteSpace(phone) && !string.IsNullOrWhiteSpace(email)))
{
return ValidationResult.Success;
}
if (string.IsNullOrWhiteSpace(phone) && string.IsNullOrWhiteSpace(email))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "emailrequired",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("stringphoneprop", StringPhonePropertyName);
yield return modelClientValidationRule;
}
}
容器视图模型和 jQuery 验证码:
@model ExpandoObjectSerializeDeserialize.Web.Models.StudentEnrollViewModel
....
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
....
@Html.EditorFor(m => m.Student)
....
@Html.EditorFor(m => m.Father)
....
@Html.EditorFor(m => m.Mother)
<input type="submit" value="Create" class="btn btn-default" />
}
@section scripts {
<script type="text/javascript">
jQuery.validator.addMethod('emailrequired', function(value, element, params) {
var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');
var phoneTextboxValue = $('#' + phoneTextboxId).val();
// empty string is evaluated as ‘false’ and non-empty, non-null string is evaluated as ‘true’ in JavaScript
return phoneTextboxValue || value;
});
jQuery.validator.unobtrusive.adapters.add('emailrequired', {}, function(options) {
options.rules['emailrequired'] = true;
options.messages['emailrequired'] = options.message;
});
</script>
}
以上视图在运行ning时间渲染如下:
<div class="col-md-4">
<input class="form-control text-box single-line" id="Father_Phone" name="Father.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Father.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Student_Email" name="Student.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Student.Email" data-valmsg-replace="true"><span for="Student_Email" class="">Email is required if Phone is not provided</span></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line" id="Mother_Phone" name="Mother.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Mother.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Father_Email" name="Father.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Father.Email" data-valmsg-replace="true"><span for="Father_Email" class="">Email is required if Phone is not provided</span></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line" id="Mother_Phone" name="Mother.Phone" type="text" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="Mother.Phone" data-valmsg-replace="true"></span>
</div>
<div class="col-md-4">
<input class="form-control text-box single-line input-validation-error" data-val="true" data-val-emailrequired="Email is required if Phone is not provided" data-val-emailrequired-stringphoneprop="Phone" id="Mother_Email" name="Mother.Email" type="text" value="">
<span class="text-danger field-validation-error" data-valmsg-for="Mother.Email" data-valmsg-replace="true"><span for="Mother_Email" class="">Email is required if Phone is not provided</span></span>
</div>
第一个糟糕的设计决定是您编写的 ValidationAttribute
对特定场景过于具体。您的属性应该是一个简单的 RequiredIfAttribute
,可以在 属性 的值(不是特别是您的“电子邮件” 属性)的值取决于另一个 属性.
在您的情况下,该属性将用作
[RequiredIf("Phone", null, ErrorMessage = "...")]
public string Email { get; set; }
您遇到的下一个问题是客户端脚本以及您没有获得从属 属性 的值这一事实。你的
var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop');
returns "Phone"
,因此下面一行
var phoneTextboxValue = $('#' + phoneTextboxId).val();
returns undefined
,(你想要的元素有 id="Father_Phone"
等)这意味着 return phoneTextboxValue || value;
总是 returns false
.
foolproof 提供了一个包含许多常见条件验证属性的库,包括 [RequiredIf]
和 [RequiredIfEmpty]
,两者都适合您的情况。
但如果您想自己编写,那么我推荐 The Complete Guide To Validation In ASP.NET MVC 3 - Part 2 作为一个很好的指南。
RequiredIfAttribute
的代码为
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
#region .Declarations
private const string _DefaultErrorMessage = "Please enter the {0}.";
private readonly string _PropertyName;
private readonly object _Value;
public RequiredIfAttribute(string propertyName, object value)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException("propertyName");
}
_PropertyName = propertyName;
_Value = value;
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
var property = validationContext.ObjectInstance.GetType().GetProperty(_PropertyName);
var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
if (propertyValue != null && propertyValue.Equals(_Value))
{
return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
rule.ValidationParameters.Add("dependentproperty", _PropertyName);
rule.ValidationParameters.Add("targetvalue", _Value);
yield return rule;
}
}
和关联的脚本
sandtrapValidation = {
getDependentElement: function (validationElement, dependentProperty) {
var dependentElement = $('#' + dependentProperty);
if (dependentElement.length === 1) {
return dependentElement;
}
var name = validationElement.name;
var index = name.lastIndexOf(".") + 1;
var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
dependentElement = $('#' + id);
if (dependentElement.length === 1) {
return dependentElement;
}
// Try using the name attribute
name = (name.substr(0, index) + dependentProperty);
dependentElement = $('[name="' + name + '"]');
if (dependentElement.length > 0) {
return dependentElement.first();
}
return null;
}
}
$.validator.addMethod("requiredif", function (value, element, params) {
if ($(element).val() != '') {
// The element has a value so its OK
return true;
}
if (!params.dependentelement) {
return true;
}
var dependentElement = $(params.dependentelement);
if (dependentElement.is(':checkbox')) {
var dependentValue = dependentElement.is(':checked') ? 'True' : 'False';
return dependentValue != params.targetvalue;
} else if (dependentElement.is(':radio')) {
// If its a radio button, we cannot rely on the id attribute
// So use the name attribute to get the value of the checked radio button
var dependentName = dependentElement[0].name;
dependentValue = $('input[name="' + dependentName + '"]:checked').val();
return dependentValue != params.targetvalue;
}
return dependentElement.val() !== params.targetvalue;
});
$.validator.unobtrusive.adapters.add("requiredif", ["dependentproperty", "targetvalue"], function (options) {
var element = options.element;
var dependentproperty = options.params.dependentproperty;
var dependentElement = sandtrapValidation.getDependentElement(element, dependentproperty);
options.rules['requiredif'] = {
dependentelement: dependentElement,
targetvalue: options.params.targetvalue
};
options.messages['requiredif'] = options.message;
});