ASP.Net MVC 3 unobtrusive client validation does not work with drop down lists

JBeckton picture JBeckton · Jan 26, 2011 · Viewed 9.6k times · Source

I have a simple drop down list, the first item in the list has an empty value. If I do not select anything in the list the client validation ignores it. I have that field set up as required on the model using annotation attributes.

 @Html.DropDownListFor(model => Model.CCPayment.State, UnitedStatesStates.StateSelectList)



[Required(ErrorMessage = "State is Required.")]
    public string State
    {
        get
        {
            return _state;
        }
        set
        {
            _state = value;
        }
    }

any ideas? am I missing something?

Answer

Milimetric picture Milimetric · Nov 12, 2011

It looks like a legitimate bug, here's the best workaround I've found in my search:

http://forums.asp.net/t/1649193.aspx

In short. You wrap the source of the problem, DropDownListFor, in a custom Html extension and you manually retrieve the unobtrusive clientside validation rules like this:

IDictionary<string, object> validationAttributes = htmlHelper.
    GetUnobtrusiveValidationAttributes(
        ExpressionHelper.GetExpressionText(expression),
        metadata
    );

Then you combine your validationAttributes dictionary with any other html attributes passed into your custom helper and you pass that along to DropDownListFor

The complete code that I'm using (I have a label in there too, you can feel free to de-couple):

public static IHtmlString DropDownListWithLabelFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string label, IEnumerable<SelectListItem> items, string blankOption, object htmlAttributes = null)
{
    var l = new TagBuilder("label");
    var br = new TagBuilder("br");

    var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
    var mergedAttributes = helper.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(expression), metadata);

    if (htmlAttributes != null)
    {
        foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(htmlAttributes))
        {
            object value = descriptor.GetValue(htmlAttributes);
            mergedAttributes.Add(descriptor.Name, value);
        }
    }

    l.InnerHtml = label + br.ToString(TagRenderMode.SelfClosing) + helper.DropDownListFor(expression, items, blankOption, mergedAttributes);
    return MvcHtmlString.Create(l.ToString(TagRenderMode.Normal));
}