I have an asp.net MVC 5 application in which I tried to re-use a nested complex view model class in different places in an .cshtml
file. The reused complex view model is named as SchoolPersonViewModel
that has many properties, and the Phone
and Email
properties are validated like "If Phone is not provided, then Email must be provided. If Phone is provided, then Email is optional input". I wrote a custom server and client side validation but it works with server side. But the client side validation is not working properly. For example Email
text box prompts to be filled even though the associated Phone
text box is filled. Please see the attachment. For example. Please help. Thank you beforehand.
I know the problem comes from the error: there are 3 validation attributes for the 3 Email text boxes with same value as data-val-emailrequired-stringphoneprop="Phone"
. The Phone
value at run time causes the ambiguity
(it is the uniqueness is missing) for the jQuery validation machine but I do not how to solve it. Please see the rendered attributes below. Please help. Thank you in advance.
Details about my codes:
On my cshtml
view, I call the view model complex class (SchoolPersonViewModel
) 3 times: one for Student
, one for Father
and one for Mother
.
C# MVC model 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; }
}
Validation attribute
// 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;
}
}
Container view model and the jQuery validation codes:
@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>
}
The above view is rendered as follows at running time:
<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>