I have a view with a form, simplified below:
@using (Html.BeginForm("New", "User", FormMethod.Post, new { id = "newUserForm" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary("There was a problem saving the user:", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.RoleID, new { htmlAttributes = new { @class = "form-label" } })
<select class="form-control" name="RoleID" id="RoleID" data-val-required="The Role field is required." data-val-number="The field Role must be a number." data-val="true">
@foreach(RoleOption role in Model.RoleOptions)
{
<option value="@role.ID" data-isdefault="@role.IsADefault" @(role.ID == Model.RoleID ? "selected" : "")>@role.DisplayName</option>
}
</select>
@Html.ValidationMessageFor(model => model.RoleID, "", new { @class = "text-danger" })
</div>
<div class="form-group">
@Html.LabelFor(model => model.Teams, new { htmlAttributes = new { @class = "form-label" } })
@Html.ListBoxFor(model => model.Teams, Model.TeamsOptions, new { @class = "form-control"})
@Html.ValidationMessageFor(model => model.Teams, "", new { @class = "text-danger" })
</div>
<button type="submit" class="btn btn-primary w-100">Save</button>
}
I'm using jQuery validation and I have a configured the validator as follows:
$("#newUserForm").validate({
invalidHandler: function (event, validator) {
var errors = validator.numberOfInvalids();
},
submitHandler: function (form) {
let selectedRole = $("[name='RoleID']").find("option:selected");
let selectedRoleADefault = selectedRole.data("isdefault").toLowerCase() === "true";
let selectedTeams = $("[name='Teams']").val();
if (!selectedRoleADefault && selectedTeams.length === 0) {
$("[data-valmsg-for='Teams']").text("You must select at least one Team");
}
else {
form.submit();
}
}
});
The idea of the submit handler is to do some fairly complicated validation for which I, frankly, couldn't be bothered creating a custom rule, especially since it won't be needed on any other forms. Go with it. The invalid handler is just there to try and help me figure this problem out.
Finally, the relevant excerpt from my view model is:
[Display(Name = "Role")]
[Required]
public int RoleID { get; set; }
I've had to manually add the data-val attributes to the select with the name RoleID because I'm not using the usual Html.DropdownFor HTML Helper, so as to allow me to add the data-isdefault attribute to each option, but I still want that field to be validated.
With the code as it appears above, when I click the submit button on my form the breakpoints I've added in both the invalidHandler and submitHanlder functions are never hit. That's the case whether I select a value for RoleID or not. In other words, whether the form is valid or otherwise, neither callback is ever fired. However, the client-side validation is nevertheless working because when no value is selected the error message for the RoleID field is displayed. When a value is selected, whilst the callback doesn't fire the form does get posted to the server. I find that very odd.
If I remove the data-val attributes that I've added to the RoleID control and submit the form, the breakpoint in the submitHandler function does fire. To a point that makes sense - having removed the data-val attributes that field is no longer being validated and so the form is being considered valid. But that just makes it every weirder to me that neither callback is fired when those attributes are present, even though validation does take place.
After further thought, I've just remembered that in a separate Javascript file I have overridden some of the defaults for the validator:
$.validator.setDefaults({
ignore: "hidden:not(.collapse :hidden)",
showErrors: function() {...}
});
I'd forgotten about this, so I've just tried adding a submitHandler property to that code and, sure enough, that is fired when I submit my form with the data-val attributes.
It's fairly obvious at this point that I just don't fully understand how to work with jQuery validation (not the first time I've been made aware of this!), but nevertheless I don't understand:
- Why the
submitHandlerfor my particular form fires when thedata-valattributes are missing from theRoleIDcontrol but not when they are present. - How to successfully specify a
submitHandlerfunction that will be called only when this one particular form is validated, whilst using thedata-valattributes on myRoleIDcontrol (and still picking up the overridden values in mysetDefaultscall).