7

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?

divibisan
  • 11,659
  • 11
  • 40
  • 58
JBeckton
  • 7,095
  • 13
  • 51
  • 71
  • Add checkboxes to that as well, I have a required checkbox that is not being high lighted as an error field when not checked. – JBeckton Jan 25 '11 at 23:37
  • Not really an answer, more a workaround, but have you tried using the IValidatableObject interface - might help you out for now? – RichardW1001 Jan 26 '11 at 00:01
  • 3
    I am already using IValidatableObject for server side validation. This is a client side issue. I did find an open issue at codeplex for this http://aspnet.codeplex.com/workitem/7629 – JBeckton Jan 26 '11 at 00:26
  • It seems that the problem is with the size of the path to the property. If I do `model => model.State` it validates fine. But `model => model.Address.State` does not. Have you figured out any workaround? – Milimetric Nov 12 '11 at 02:02
  • Note that in MVC 5, this bug has been fixed. – howcheng Nov 14 '14 at 23:33

5 Answers5

8

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));
}
Milimetric
  • 13,411
  • 4
  • 44
  • 56
  • Seriously this is definitely the answer!!! Was working on this one for three days! I would've never figured this out. Awesome!!! Thanks @Milimetric – Rich Aug 20 '12 at 19:52
  • This SAVED me after hours of getting nowhere!!! It should be marked as the answer! – Nick Apr 25 '13 at 16:26
  • Thanks Nick :) It's a seriously nasty bug that's been happening for way too long. 18 months! – Milimetric Apr 25 '13 at 17:58
4

You have provided too little information in order for us to be able to pinpoint the problem. You might have forgot to include the proper unobtrusive validation scripts inside your view but who knows? You haven't shown your view.

Here's a full working example:

Model:

public class MyViewModel
{
    [Required(ErrorMessage = "State is Required.")]
    public string State { get; set; }

    public IEnumerable<SelectListItem> States 
    { 
        get
        {
            return Enumerable.Range(1, 5).Select(x => new SelectListItem
            {
                Value = x.ToString(),
                Text = "state " + x
            });
        }
    }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel());
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

@model AppName.Models.MyViewModel
@{
    ViewBag.Title = "Home Page";
}
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.State)
    @Html.DropDownListFor(
        x => x.State, 
        new SelectList(Model.States, "Value", "Text"), 
        "-- Please select a state --"
    )
    @Html.ValidationMessageFor(x => x.State)
    <input type="submit" value="OK" />
}

Notice how we are providing a default value in the DropDownListFor helper as last parameter. That will insert an option in the beginning with empty value and custom text and if the user doesn't pick some state the required validator should kick in.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • it should kick in but it does not, i tried adding the default value but I get same problem. The only way I can get this to work is to add 'required' to the html class attribute. problem is that it only provides a generic error message rather than the one I specified in my model validation attribute. – JBeckton Jan 28 '11 at 21:56
  • It's busted for me too. If I exclude jq.validate.js and only include jq.validate.unobtrusive.js, it works, which is weird. – George R Jul 15 '11 at 08:02
2

I added @class="required" to the attributes like some guy said on the thread at codeplex http://aspnet.codeplex.com/workitem/7629 and it worked fine for me =)

  • yes this is a work around but you get the generic error message rather than the custom one defined in your validation attribute. – JBeckton Sep 22 '11 at 16:11
1

This is the simpliest way I found to do it, just adding data-val-*-* attributes in HtmlAttributes of DropDownListFor, inside the view. The following method works with RemoteValidation too, if you do not need remote validation, simply remove the elements containing data-val-remote-*:

        @Html.DropDownListFor(m => m.yourlistID, (IEnumerable<SelectListItem>)ViewBag.YourListID, String.Empty, 
        new Dictionary<string, object>() { { "data-val", "true" }, 
        { "data-val-remote-url", "/Validation/yourremoteval" }, 
        { "data-val-remote-type", "POST" }, { "data-val-remote-additionalfield", "youradditionalfieldtovalidate" } })

I hope it may help. Best Regards!

Larry
  • 573
  • 5
  • 14
  • 31
0

If the issue is that when you select the 'blank' option, you are not seeing the validation message saying the field is required, it's probably because of how the Html Helper generates the option tag for the 'blank' option. The problem is the empty value attribute.

<select ...>
  <option value="">
  ...
</select>

I used this one-line workaround to remove the value attribute, and it worked like a charm!

$('option[value=""]').removeAttr("value");

All this does is remove the value attribute from any 'option' element with a blank value attribute.

Timothy Kanski
  • 1,861
  • 14
  • 20