139

Why can't I pass in html attributes to EditorFor()? eg;

<%= Html.EditorFor(model => model.Control.PeriodType, 
    new { disabled = "disabled", readonly = "readonly" }) %>

I don't want to use metadata

Update: The solution was to call this from the view :

 <%=Html.EditorFor( model => model.Control.PeriodEndDate, new {Modifiable=model.Control.PeriodEndDateModifiable})%>

and use ViewData["Modifiable"] in my custom EditorTemplates/String.ascx where I have some view logic that determines whether to add readonly and/or disabled attributes to the input The anonymous object passed into EditorFor() is a parameter called additionalViewData and its properties are passed to the editor template in the ViewData collection.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Typo Johnson
  • 5,974
  • 6
  • 29
  • 40
  • 23
    Seriously, still in MVC3 this limitation has no sense and it is really annoying. People around the world are spending time and pulling their hair out with this nonsense. I am sending this to Microsoft Connect. – Junior Mayhé Sep 21 '12 at 14:14
  • 5
    https://connect.microsoft.com/VisualStudio/feedback/details/764012/allow-htmlattributes-in-asp-net-mvc-editorfor – Junior Mayhé Sep 21 '12 at 14:44
  • 3
    Is it just me, or does that seem like a lot of bs for something that should be soooo simple? – Eric K Dec 11 '12 at 01:08
  • Maybe this will help you: [EditorFor-Implementation with CSS classes and HTML attributes][1] [1]: http://stackoverflow.com/questions/12675708/how-to-customize-the-editorfor-css-with-razor/14687322#14687322 – FunThom Feb 04 '13 at 13:16
  • Possible duplicate of [EditorFor() and html properties](https://stackoverflow.com/questions/1625327/editorfor-and-html-properties) – Gyrfalcon Jul 01 '18 at 13:12

14 Answers14

126

Update MVC 5.1 now supports the below approach directly, so it works for built in editor too. http://www.asp.net/mvc/overview/releases/mvc51-release-notes#new-features (It's either a case of Great mind thinking alike or they read my answer :)

End Update

If your using your own editor template or with MVC 5.1 which now supports the below approach directly for built in editors.

@Html.EditorFor(modelItem => item.YourProperty, 
  new { htmlAttributes = new { @class="verificationStatusSelect", style = "Width:50px"  } })

then in your template (not required for simple types in MVC 5.1)

@Html.TextBoxFor(m => m, ViewData["htmlAttributes"])
AntonK
  • 2,303
  • 1
  • 20
  • 26
  • 5
    This new feature perfectly solves the original problem asked four years ago. – CoderSteve Oct 30 '14 at 17:24
  • 1
    If instead of TextBoxFor and helpers like that I use tons of custom editor templates, I would not say it's a fantastic idea to add ViewData[...] into each of them... :( – Alexander May 26 '16 at 11:05
100

EditorFor works with metadata, so if you want to add html attributes you could always do it. Another option is to simply write a custom template and use TextBoxFor:

<%= Html.TextBoxFor(model => model.Control.PeriodType, 
    new { disabled = "disabled", @readonly = "readonly" }) %>    
Mrchief
  • 75,126
  • 20
  • 142
  • 189
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • The metadata won't work because the html attributes vary depending on other properties in the model, in other words domain or viemodel logic should determine the html attributes not static metadata. Or am I missing the point, can I set metadata dynamically? – Typo Johnson Sep 17 '10 at 13:05
  • What is `PeriodType`? Isn't it a simple property? If it is a complex object you could could customize the whole template by placing a partial in `~/Views/ControllerName/EditorTemplates/SomeType.ascx` where `SomeType` is the type name of the `PeriodType` property. – Darin Dimitrov Sep 17 '10 at 13:06
  • Also your suggested code would mean passing the whole model into a partial template which accesses a specific property, this would mean I have a partial for each property? – Typo Johnson Sep 17 '10 at 13:06
  • PeriodType is a simple object, but it may not be editable at the front end depending on domain permissions and workflow – Typo Johnson Sep 17 '10 at 13:08
  • 2
    I get you now. I can use <%=Html.EditorFor( model => model.Control.PeriodEndDate, new {Modifiable=model.Control.PeriodEndDateModifiable})%> and use ViewData["PeriodEndDateModifiable"] in my custom EditorTemplates/String.ascx. Thanks – Typo Johnson Sep 17 '10 at 13:32
  • Really boring workaround. People, vote up! http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3183610-allow-htmlattributes-in-asp-mvc-editorfor – Junior Mayhé Sep 21 '12 at 14:19
  • 1
    @JuniorMayhé, I wouldn't call this a boring limitation. If you think more carefully you will understand that this makes sense. In fact the whole point of the EditorFor helper is that you could have a corresponding template. This template could contain any markup. Not necessarily a single element. It would make absolutely no sense to define `@class` for an editor template that contains 3 divs for example. The Html.TextBoxFor helper allows you to define this because you know what this helper generates - a textbox so it makes sense to define a class to an input element. – Darin Dimitrov Sep 21 '12 at 15:15
  • Indeed a class for 3 divs has no sense. The exception would be if the `EditorFor` had no template to look for. The possibility to better handle html attributes would be nicer avoiding workarounds we see today in internet. Imagine a helper: `EditorFor(m=>m.YourField, renderAsSingleInput, htmlAttributesHere)`, that would save time for people who need to stick with EditorFor methods. – Junior Mayhé Sep 22 '12 at 16:31
43

As of MVC 5.1, you can now do the following:

@Html.EditorFor(model => model, new { htmlAttributes = new { @class = "form-control" }, })

http://www.asp.net/mvc/overview/releases/mvc51-release-notes#new-features

Matt
  • 74,352
  • 26
  • 153
  • 180
vtforester
  • 683
  • 5
  • 7
6

Now ASP.Net MVC 5.1 got a built in support for it.

From Release Notes

We now allow passing in HTML attributes in EditorFor as an anonymous object.

For example:

@Html.EditorFor(model => model, 
              new { htmlAttributes = new { @class = "form-control" }, })
Murali Murugesan
  • 22,423
  • 17
  • 73
  • 120
5

Here is the VB.Net code syntax for html attributes in MVC 5.1 EditorFor

@Html.EditorFor(Function(x) x.myStringProp, New With {.htmlAttributes = New With {.class = "myCssClass", .maxlength="30"}}))
Max
  • 7,408
  • 2
  • 26
  • 32
3

Why not just use

@Html.DisplayFor(model => model.Control.PeriodType)
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Mhoque
  • 377
  • 3
  • 11
  • 32
    One reason is that DisplayFor doesn't render an input, so the value is lost on postback. – ProfK Apr 25 '12 at 06:11
  • 2
    Another reason is the specific scenario where the editor is currently-but-not-always locked, and you want to communicate that the data is potentially editable - just not now. – Mir Dec 14 '12 at 22:39
2

I've been wrestling with the same issue today for a checkbox that binds to a nullable bool, and since I can't change my model (not my code) I had to come up with a better way of handling this. It's a bit brute force, but it should work for 99% of cases I might encounter. You'd obviously have to do some manual population of valid attributes for each input type, but I think I've gotten all of them for checkbox.

In my Boolean.cshtml editor template:

@model bool?

@{
    var attribs = new Dictionary<string, object>();
    var validAttribs = new string[] {"style", "class", "checked", "@class",
        "classname","id", "required", "value", "disabled", "readonly", 
        "accesskey", "lang", "tabindex", "title", "onblur", "onfocus", 
        "onclick", "onchange", "ondblclick", "onmousedown", "onmousemove", 
        "onmouseout", "onmouseover", "onmouseup", "onselect"};
    foreach (var item in ViewData) 
    {
        if (item.Key.ToLower().IndexOf("data_") == 0 || item.Key.ToLower().IndexOf("aria_") == 0) 
        {
            attribs.Add(item.Key.Replace('_', '-'), item.Value);
        } 
        else 
        {
            if (validAttribs.Contains(item.Key.ToLower()))
            {
                attribs.Add(item.Key, item.Value);
            }
        }
    }
}

@Html.CheckBox("", Model.GetValueOrDefault(), attribs)
Isochronous
  • 1,076
  • 10
  • 25
  • I added `Dictionary attribs = new Dictionary();` and `attribs.Add("tabindex","16");` in one of those `@( )` blocks at the top of my page. Then I did `@Html.CheckBox("IsActive", Model.IsActive.HasValue ? Model.IsActive : false, attribs)` on my page and it gives me an error: "'System.Web.Mvc.HtmlHelper' does not contain a definition for 'CheckBox' and the best extension method overload 'System.Web.Mvc.InputExtensions.CheckBox(System.Web.Mvc.HtmlHelper, string.bool, object)' has some invalid arguments". – vapcguy Oct 25 '14 at 02:22
2

You can still use EditorFor. Just pass the style/whichever html attribute as ViewData.

@Html.EditorFor(model => model.YourProperty, new { style = "Width:50px" })

Because EditorFor uses templates to render, you could override the default template for your property and simply pass the style attribute as ViewData.

So your EditorTemplate would like the following:

@inherits System.Web.Mvc.WebViewPage<object>

@Html.TextBoxFor(m => m, new { @class = "text ui-widget-content", style=ViewData["style"] })
2

If you don't want to use Metadata you can use a [UIHint("PeriodType")] attribute to decorate the property or if its a complex type you don't have to decorate anything. EditorFor will then look for a PeriodType.aspx or ascx file in the EditorTemplates folder and use that instead.

John Farrell
  • 24,673
  • 10
  • 77
  • 110
  • Thanks, I may end up doing that if the if/else in my editor template is only required for certain fields – Typo Johnson Sep 17 '10 at 13:36
  • I though you said you couldn't change the model (not your code), so decorating with UIHint directly would not be an option. – Darrel Lee Nov 08 '12 at 01:28
1
Html.TextBoxFor(model => model.Control.PeriodType, 
    new { @class="text-box single-line"})

you can use like this ; same output with Html.EditorFor ,and you can add your html attributes

zamoldar
  • 548
  • 10
  • 13
  • Except `TextBoxFor` ignores any type of `DisplayFormat` that you may be attempting to apply. – Eric K Dec 11 '12 at 00:57
1

Just create your own template for the type in Views/Shared/EditorTemplates/MyTypeEditor.vbhtml

@ModelType MyType

@ModelType MyType
@Code
    Dim name As String = ViewData("ControlId")
    If String.IsNullOrEmpty(name) Then
        name = "MyTypeEditor"
    End If
End Code

' Mark-up for MyType Editor
@Html.TextBox(name, Model, New With {.style = "width:65px;background-color:yellow"})

Invoke editor from your view with the model property:

@Html.EditorFor(Function(m) m.MyTypeProperty, "MyTypeEditor", New {.ControlId = "uniqueId"})

Pardon the VB syntax. That's just how we roll.

Darrel Lee
  • 2,372
  • 22
  • 22
1

In my case I was trying to create an HTML5 number input editor template that could receive additional attributes. A neater approach would be to write your own HTML Helper, but since I already had my .ascx template, I went with this approach:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<input id="<%= Regex.Replace(ViewData.TemplateInfo.GetFullHtmlFieldId(""), @"[\[\]]", "_") %>" name="<%= ViewData.TemplateInfo.HtmlFieldPrefix %>" type="number" value="<%= ViewData.TemplateInfo.FormattedModelValue %>"
<% if (ViewData["attributes"] != null)
   {
       Dictionary<string, string> attributes = (Dictionary<string, string>)ViewData["attributes"];
       foreach (string attributeName in attributes.Keys){%>
        <%= String.Format(" {0}=\"{1}\"", attributeName, attributes[attributeName])%>
       <% }
   } %> />

This ugly bit creates a number type input and looks for a ViewData Dictionary with the key "attributes". It will iterate through the dictionary adding its key/value pairs as attributes. The Regex in the ID attribute is unrelated and is there because when used in a collection, GetFullHtmlFieldId() returns an id containing square brackets [] which it would normally escape as underscores.

This template is then called like this:

Html.EditorFor(m => m.Quantity, "NumberField", new { attributes = new Dictionary<string, string>() { { "class", "txtQuantity" } } }

Verbose, but it works. You could probably use reflection in the template to use property names as attribute names instead of using a dictionary.

xr280xr
  • 12,621
  • 7
  • 81
  • 125
1

Set the condition using ViewData in the controller

ViewData["Modifiable"] = model.recProcessed;

Then use this viewdata in editor template to set the html attribute of the control

@Html.RadioButton(prefix, li.Value, li.Selected, @ViewData["Modifiable"].ToString().ToLower() == "true" ? (object)new  { @id = li.Value, @disabled = "disabled" } : new { @id = li.Value })
Vivek Jain
  • 3,811
  • 6
  • 30
  • 47
0

MVC 5.1 and higher solution (will merge local HtmlAttributes and defined in the EditorTemplates):

Shared\EditorTemplates\String.cshtml:

@Html.TextBoxFor(model => model, new { @class = "form-control", placeholder = ViewData.ModelMetadata.Watermark }.ToExpando().MergeHtmlAttributes(ViewData["htmlAttributes"].ToExpando()))

Extensions:

public static IDictionary<string, object> MergeHtmlAttributes(this ExpandoObject source1, dynamic source2)
{
    Condition.Requires(source1, "source1").IsNotNull().IsLongerThan(0);

    IDictionary<string, object> result = source2 == null
        ? new Dictionary<string, object>()
        : (IDictionary<string, object>) source2;

    var dictionary1 = (IDictionary<string, object>) source1;

    string[] commonKeys = result.Keys.Where(dictionary1.ContainsKey).ToArray();
    foreach (var key in commonKeys)
    {
        result[key] = string.Format("{0} {1}", dictionary1[key], result[key]);
    }

    foreach (var item in dictionary1.Where(pair => !result.ContainsKey(pair.Key)))
    {
        result.Add(item);
    }

    return result;
}

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var item in anonymousDictionary)
        expando.Add(item);
    return (ExpandoObject)expando;
}

public static bool HasProperty(this ExpandoObject expando, string key)
{
    return ((IDictionary<string, object>)expando).ContainsKey(key);
}

Usage:

@Html.EditorFor(m => m.PromotionalCode, new { htmlAttributes = new { ng_model = "roomCtrl.searchRoomModel().promoCode" }})
SerjG
  • 3,325
  • 3
  • 30
  • 30
  • 1
    Thanks, that works. Except for all data_xxx attributes, where I had to replace _ with - when casting `source1` and `source2` as `IDictionary`. I made this function: `private static IDictionary ToHtmlAttributesDictionary(this IEnumerable> dico) { return dico.ToDictionary(s => s.Key.Replace('_', '-'), s => s.Value); }` – Marien Monnier Oct 31 '14 at 10:50