30

I have created a default MVC 3 project (using razor), in order to demonstrate an issue.

On the login page, there is a line:

@Html.TextBoxFor(m => m.UserName)

if I change this to:

@Html.TextBoxFor(m => m.UserName, new { title = "ABC" })

Then the it is rendered as (with a title attribute):

<input data-val="true" data-val-required="The User name field is required." id="UserName" name="UserName" title="ABC" type="text" value="" />

However, if I make it an EditorFor:

 @Html.EditorFor(m => m.UserName, new { title = "ABC" })

Then it gets rendered (without a title attribute) as:

<input class="text-box single-line" data-val="true" data-val-required="The User name field is required." id="UserName" name="UserName" type="text" value="" />

So in summary, the title attribute is lost when I use EditorFor.

I know that the second parameter for TextBoxFor is called htmlAttributes, and for EditorFor it is additionalViewData, however I've seen examples where EditorFor can render attributes supplied with this parameter.

Can anyone please explain what I am doing wrong, and how I can have a title attribute when using EditorFor?

1
  • Fantastic, I was going to ask about this exact problem. Commented Dec 13, 2011 at 14:30

3 Answers 3

14

I think I found a little nicer solution to it. EditorFor takes in additionalViewData as a parameter. If you give it a parameter named "htmlAttributes" with the attributes, then we can do interesting things with it:

@Html.EditorFor(model => model.EmailAddress,
                new { htmlAttributes = new { @class = "span4",
                                             maxlength = 128,
                                             required = true,
                                             placeholder = "Email Address",
                                             title = "A valid email address is required (i.e. [email protected])" } })

In the template (in this case, EmailAddress.cshtml) you can then provide a few default attributes:

@Html.TextBox("",
              ViewData.TemplateInfo.FormattedModelValue,
              Html.MergeHtmlAttributes(new { type = "email" }))

The magic comes together through this helper method:

public static IDictionary<string, object> MergeHtmlAttributes<TModel>(this HtmlHelper<TModel> htmlHelper, object htmlAttributes)
{
    var attributes = htmlHelper.ViewData.ContainsKey("htmlAttributes")
                            ? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlHelper.ViewData["htmlAttributes"])
                            : new RouteValueDictionary();

    if (htmlAttributes != null)
    {
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(htmlAttributes))
        {
            var key = property.Name.Replace('_', '-');
            if (!attributes.ContainsKey(key))
            {
                attributes.Add(key, property.GetValue(htmlAttributes));
            }
        }
    }

    return attributes;
}

Of course you could modify it to render the attributes as well if you are doing raw HTML:

public static MvcHtmlString RenderHtmlAttributes<TModel>(this HtmlHelper<TModel> htmlHelper, object htmlAttributes)
{
    var attributes = htmlHelper.ViewData.ContainsKey("htmlAttributes")
                            ? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlHelper.ViewData["htmlAttributes"])
                            : new RouteValueDictionary();

    if (htmlAttributes != null)
    {
        foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(htmlAttributes))
        {
            var key = property.Name.Replace('_', '-');
            if (!attributes.ContainsKey(key))
            {
                attributes.Add(key, property.GetValue(htmlAttributes));
            }
        }
    }

    return MvcHtmlString.Create(String.Join(" ",
        attributes.Keys.Select(key =>
            String.Format("{0}=\"{1}\"", key, htmlHelper.Encode(attributes[key])))));
}
Sign up to request clarification or add additional context in comments.

3 Comments

Would you be so kind as to provide a complete sample project that uses the code you posted? I am new to ASP.NET and at loss where to put that MergeHtmlAttributes and RenderHtmlAttributes classes.
@RosdiKasim - These are extension methods that I typically store in a static class that either mirrors the namespace of the common set of HTML helpers or a new namespace that I include in the web.config in the Views folder so I do not have to keep referencing it on each page. So basically "HtmlHelperExtensions.cs" in the root of the project or an Infrastructure folder.
you can improve this using HtmlHelper.AnonymousObjectToHtmlAttributes() method.
12

In MVC3 you can add a title (and other htmlAttributes) using this kind of workaround if you create a custom EditorFor template. This case is prepared for the value to be optional, editorFor calls are not required to include the object additionalViewData

@Html.EditorFor(m => m.UserName, "CustomTemplate", new { title = "ABC" })

EditorTemplates/CustomTemplate.cshtml

@{
    string s = "";
    if (ViewData["title"] != null) {
        // The ViewData["name"] is the name of the property in the addtionalViewData...
        s = ViewData["title"].ToString();
    }
}

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { title = s })

I did something very similar to include an optional class in an EditorTemplate. You can add as many items to the addtionalViewData as you like but you need to handle each on in the EditorFor template.

Comments

5

You may take a look at the following blog post which illustrates how to implement a custom metadata provider and use data annotations on your view model in order to define html properties such as class, maxlength, title, ... This could then be used in conjunction with the templated helpers.

4 Comments

While impressive, that solution is the proverbial sledge hammer for a walnut. Is there a more subtle solution?
@G-unit, no there isn't. EditorFor doesn't support passing html attributes. It wouldn't make sense as you don't even know how the template would be implemented. It could be a custom template for which html attributes don't make sense. So the only way to achieve this would be to write a custom metadata provider. Personally that's what I use and I am quite happy with the results.
Thank you, I bow to your wisdom!
People, vote up who agrees this is a boring limitation. visualstudio.uservoice.com/forums/121579-visual-studio/…

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.