TextBoxFor vs EditorFor, and htmlAttributes vs add

2019-02-01 18:56发布

问题:

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:

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. user@domain.com)" } })

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])))));
}


回答2:

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.



回答3:

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.