In an ASP.NET Core Razor Pages application, using jQuery unobtrusive validation, I'm attempting to get a custom validation attribute to work client side. The form is being rendered by a fetch() call into a bootstrap modal popup, this may have something to do with the problem, although I'm having the exact same issue in a .NET 4.8 ASP.NET MVC application where the forms are rendered via an Ajax call.
In the Razor Pages app I have the server-side attribute set up in the usual way:
public class TerminationReasonCodeAttribute : ValidationAttribute, IClientModelValidator
{
private const string errorMessage = "When a Termination Reason is selected, a Termination Date must be entered, and vice-versa.";
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-terminationreasoncode", errorMessage);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
...rest of code omitted for brevity.
The attribute is added to the property of the class that is rendered in the form in the usual way:
[TerminationReasonCode]
[StringLength(50)]
[Display(Name = "Termination Reason")]
public string TerminationReasonCode { get; set; }
The form in question is rendered via a fetch() call to a server-side method and setting a div's innerHTML, like so:
div.querySelector('div.modal-body').innerHTML = cleanHtml;
Since I'm rendering the form async I have to re-parse it:
let $form = $(div).find('#editUserForm');
$form.removeData('validator').removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse($form);
Then I call addMethod:
$.validator.addMethod("terminationreasoncode",
function (value, element, param) {
//the attribute is applied to TerminationReasonCode. this is arbitrary
//it could have been applied to TerminationDate
let terminationReason = value;
let terminationDate = document.getElementById('TerminationDate').value;
if (terminationReason && !terminationDate) {
return false;
}
else if (terminationDate && !terminationReason) {
return false;
}
else {
return true;
}
});
And finally, I call this:
$.validator.unobtrusive.adapters.addBool("terminationreasoncode");
In my form, when the user clicks the "save" button, I call $("#editUserForm").valid(). My custom validation method never fires, and I cannot figure out why. When I look at what gets rendered I can see that all of my "data-..." tags get rendered properly:
<select name="TerminationReasonCode" id="TerminationReasonCode"
data-val-terminationreasoncode="When a Termination Reason is selected, a Termination Date must be
entered, and vice-versa."
data-val-length-max="50"
data-val-length="The field Termination Reason must be a string with a maximum length of 50."
data-val="true"
class="form-control">
Here is the kicker: if instead of calling $.validator.unobtrusive.adapters.addBool("terminationreasoncode"), I instead do the code you see below, it works! My validation method gets invoked when I call $("#editUserForm").valid() in javascript. Why does the code below work, but the call to addBool does nothing? Again, I'm having the exact same issue in an asp.net mvc Framework 4.8 application, where the form is rendered by an ajax call, and the same workaround works there.
$('#TerminationReasonCode').rules('add',
{
terminationreasoncode: true,
messages: { terminationreasoncode: "When a Termination Reason is selected, a Termination Date must be entered, and vice-versa." }
});
The answer is that this call:
$.validator.unobtrusive.parse($form);
Must happen after this call:
$.validator.unobtrusive.adapters.addBool("terminationreasoncode");
Everything else works, even if it runs after parse(), except for the call to addBool(). The call to addBool() must execute before to the call to parse(). If anyone knows why this is the case please post an answer and I'll select it.