Search code examples
c#jqueryasp.net-mvcunobtrusive-validation

Unobtrusive validation doesn't fire on radio buttons


I'm trying to get a form to validate in JavaScript using the included jQuery.Validate. Text and select inputs validate correctly in JavaScript, but any radio button groups I include in the form only validate on the server. How can I get the client validation to work? A snippet of my view follows:

@Html.AccessibleValidationMessageFor(m => m.DeliveryMethod)
@foreach (var deliveryMethod in Model.GetDeliveryMethodsOrdered())
{
    @Html.AccessibleRadioButtonFor(m => m.DeliveryMethod, deliveryMethod.Id, new { id = string.Format("DeliveryMethod_{0}", deliveryMethod.Id) })
    <label for="[email protected]">@deliveryMethod.Description</label>
}

I'm using some extensions of built in HTML helpers, but I don't think that this is causing the problem, as I have also tried with the built in @Html.RadioButtonFor and @Html.ValidationMessageFor helpers. Just in case, here is the custom helper code.

    public static IHtmlString AccessibleRadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object values, object htmlAttributes = null)
    {
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);



        var fieldName = ExpressionHelper.GetExpressionText(expression);
        var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
        var validationAttributes = html.GetUnobtrusiveValidationAttributes(fullBindingName, metadata);
        if (!html.ViewData.ModelState.IsValidField(fullBindingName))
        {
            if (!validationAttributes.ContainsKey("aria-describedby"))
            {
                validationAttributes.Add("aria-describedby", fullBindingName.ToLower() + "-valMsg");
                validationAttributes.Add("aria-invalid", "true");
            }
        }

        RouteValueDictionary routeValues = new RouteValueDictionary(htmlAttributes);
        if (routeValues != null)
        {
            foreach (var attribute in validationAttributes)
            {
                routeValues.Add(attribute.Key, attribute.Value);
            }
            return html.RadioButtonFor(expression, values, routeValues);
        }
        return html.RadioButtonFor(expression, values, validationAttributes);
    }

In the model, I made the type nullable and added the required attribute.

    [Required]
    [Display(Name = "Delivery Method")]
    public int? DeliveryMethod { get; set; }

With this set up, the radio button renders as:

<input aria-describedby="deliverymethod-valMsg" aria-invalid="true" 
    class="input-validation-error" data-val="true" data-val-number="The field Delivery Method must be a number." 
    data-val-required="The Delivery Method field is required." id="DeliveryMethod_-1" 
    name="DeliveryMethod" type="radio" value="-1">

I've tried several ideas:

  • Making the model type into a regular int or a string
  • Creating a custom validation attribute and applying that to the model property.
  • As I said above, replacing the custom helpers with the built in versions, trying to isolate the cause.

All of these have left me with the same result, the radio button will validate correctly on the server side, but never on the client. Currently I'm trying to debug in the jquery.validate.js file and it looks like the required validator function is never actually called on any radio button controls, if that helps narrow things down. If anybody knows how to make this validate, I'd appreciate the help. Thanks!


Solution

  • A colleague of mine figured this out after some considerable digging. It turns out that jQuery validation won't validate any hidden inputs. One of the conditions that will cause it to think an input is hidden is having both height and width set to zero pixels. We had applied some custom styling to radio buttons and checkboxes so they were set to height and width zero, thus they were considered hidden and not validating.

    The fix turned out to be making them each just 1 pixel, height and width.