16

I am unhappy with the current DropDownList implementation, because I can't really do much with the option tags (only selected, text and value is supported). I want to make my own where I can set disabled and other stuff on individual options.

Currently I'm altering the options by javascript, but I think it's a bit of a hacky way to do it, and I'd prefer to just render the correct html to begin with.

I know I can just make a template that uses select and option tags and make the options as I want them - but the normal DropDownList extension adds stuff val stuff and a specific name and ID which I guess is for proper databinding when submitting the form:

<select data-val="true" data-val-number="The field SelectedValue must be a number." id="ParentDropDown_SelectedValue" name="ParentDropDown.SelectedValue">

How do I go about adding these attributes to my own templates?

2
  • Possible duplicate of this: stackoverflow.com/questions/2655035/… Commented Aug 10, 2013 at 15:53
  • You're right, he's wanting the same end result. I had not found that. But I don't like the answer in there as it seems kinda hacky too. I'm going to give Daniel J. G.'s answer a try as it looks like the way I wanted to go. Commented Aug 10, 2013 at 19:04

1 Answer 1

31

You are right, those attributes (and specially the name attribute) are critical for the model binding.

Say you want to create a custom helper like

public static MvcHtmlString CustomHelperFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)

First you can use var fieldName = ExpressionHelper.GetExpressionText(expression); to get the field name.

Then use var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName); in order to get the full name, taking care of nested views.

Finally you can transform this into an id attribute using var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);.

So a simple custom helper that creates a textbox could be written as:

public static MvcHtmlString CustomHelperFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{            
    var fieldName = ExpressionHelper.GetExpressionText(expression);
    var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
    var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);

    var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
    var value = metadata.Model;

    TagBuilder tag = new TagBuilder("input");
    tag.Attributes.Add("name", fullBindingName);
    tag.Attributes.Add("id", fieldId);
    tag.Attributes.Add("type", "text");
    tag.Attributes.Add("value", value == null ? "" : value.ToString());

    var validationAttributes = html.GetUnobtrusiveValidationAttributes(fullBindingName, metadata);
    foreach (var key in validationAttributes.Keys)
    {
        tag.Attributes.Add(key, validationAttributes[key].ToString());
    }

    return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}

You can use it in a view like:

@Html.CustomHelperFor(model => model.ParentDropDown.SelectedValue)

And it will produce the following html:

<input id="ParentDropDown_SelectedValue" name="ParentDropDown.SelectedValue" type="text" value="4">

Hope it helps!

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks! This has helped me a long way. Now I just need to figure out how to databind it to a property of the model. I have a model that represents the whole DropDownList with options and selected value. I generate the html from this, and need to databind the value to the SelectedValue property. I'm guessing I should look into making a name and/or id that fits that property.
I have updated my answer including a bit of logic that would include data-annotation validation attributes in the rendered html.
Thanks. I had wondered about that, but I figured I could live without it since there would still be server side validation.
thanks, this has been a big help. Just wondering why you pass in a Expression. could you pass in the model object though and have the binding still work?
The expression is mainly required so binding complex objects works as in model.ParentDropDown.SelectedValue. This way the generated html can take into account the ParentDropDown prefix and generated ParentDropDown_SelectedValue as the input id

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.