客户端validation不使用重用和嵌套的复杂属性

我有一个asp.net MVC 5应用程序,我尝试在.cshtml文件中的不同位置重用嵌套的复杂视图模型类。 重用的复杂视图模型被命名为具有许多属性的SchoolPersonViewModel ,并且PhoneEmail属性被validation为“如果未提供电话,则必须提供电子邮件。如果提供电话,则电子邮件是可选输入”。 我写了一个自定义服务器和客户端validation,但它适用于服务器端。 但是客户端validation工作不正常。 例如,即使填充了关联的Phone文本框,也会提示填写Email文本框。 请参阅附件。 例如。 在此处输入图像描述 请帮忙。 先谢谢你了。

我知道问题来自错误:3个电子邮件文本框有3个validation属性,其值与data-val-emailrequired-stringphoneprop="Phone" 。 运行时的Phone值导致jQueryvalidation机器的歧义(缺少唯一性),但我不知道如何解决它。 请参阅下面的渲染属性。 请帮忙。 先感谢您。

我的代码详情:

在我的cshtml视图中,我将视图模型复杂类( SchoolPersonViewModel )调用3次:一个用于Student ,一个用于Father ,一个用于Mother

C#MVC模型类

 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; } } 

validation属性

 // 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(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 GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var modelClientValidationRule = new ModelClientValidationRule { ValidationType = "emailrequired", ErrorMessage = FormatErrorMessage(metadata.DisplayName) }; modelClientValidationRule.ValidationParameters.Add("stringphoneprop", StringPhonePropertyName); yield return modelClientValidationRule; } } 

容器视图模型和jQueryvalidation代码:

 @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)  } @section scripts {  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; });  } 

上面的视图在运行时呈现如下:

 
Email is required if Phone is not provided
Email is required if Phone is not provided
Email is required if Phone is not provided

第一个糟糕的设计决策是您编写了一个ValidationAttribute ,它对特定场景来说太具体了。 您的属性应该是一个简单的RequiredIfAttribute ,可以在属性值(特别是您的“Email”属性)依赖于另一个属性的值的任何场景中使用。

在您的情况下,该属性将用作

 [RequiredIf("Phone", null, ErrorMessage = "...")] public string Email { get; set; } 

您遇到的下一个问题是客户端脚本以及您没有获得依赖属性值的事实。 您的

 var phoneTextboxId = $(element).attr('data-val-emailrequired-stringphoneprop'); 

返回"Phone" ,因此返回以下行

 var phoneTextboxValue = $('#' + phoneTextboxId).val(); 

返回undefined ,(你想要的元素有id="Father_Phone"等),这意味着return phoneTextboxValue || value; return phoneTextboxValue || value; 总是返回false

foolproof提供了许多常见条件validation属性的库,包括[RequiredIf][RequiredIfEmpty] ,两者都适用于您的情况。

但是如果你想自己编写,那么我推荐ASP.NET MVC 3中的完整指南validation – 第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 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; });