Making my own HtmlHelper extension for input that works with model binding
Asked Answered
T

1

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?

Thunderbolt answered 10/8, 2013 at 11:2 Comment(2)
Possible duplicate of this: #2655535Tweeter
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.Thunderbolt
D
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!

Diffract answered 10/8, 2013 at 12:45 Comment(5)
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.Thunderbolt
I have updated my answer including a bit of logic that would include data-annotation validation attributes in the rendered html.Diffract
Thanks. I had wondered about that, but I figured I could live without it since there would still be server side validation.Thunderbolt
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?Pendulous
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 idDiffract

© 2022 - 2024 — McMap. All rights reserved.