Html attributes for EditorFor() in ASP.NET MVC
Asked Answered
C

14

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.

Coronagraph answered 17/9, 2010 at 12:45 Comment(5)
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.Guberniya
connect.microsoft.com/VisualStudio/feedback/details/764012/…Guberniya
Is it just me, or does that seem like a lot of bs for something that should be soooo simple?Ocieock
Maybe this will help you: [EditorFor-Implementation with CSS classes and HTML attributes][1] [1]: #12676208Yean
Possible duplicate of EditorFor() and html propertiesSvetlanasvoboda
M
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" }) %>    
Massimo answered 17/9, 2010 at 12:52 Comment(8)
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?Coronagraph
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.Massimo
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?Coronagraph
PeriodType is a simple object, but it may not be editable at the front end depending on domain permissions and workflowCoronagraph
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. ThanksCoronagraph
Really boring workaround. People, vote up! visualstudio.uservoice.com/forums/121579-visual-studio/…Guberniya
@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.Massimo
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.Guberniya
F
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"])
Frigidarium answered 23/5, 2013 at 5:37 Comment(2)
This new feature perfectly solves the original problem asked four years ago.Dowden
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... :(Secretory
M
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" }) %>    
Massimo answered 17/9, 2010 at 12:52 Comment(8)
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?Coronagraph
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.Massimo
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?Coronagraph
PeriodType is a simple object, but it may not be editable at the front end depending on domain permissions and workflowCoronagraph
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. ThanksCoronagraph
Really boring workaround. People, vote up! visualstudio.uservoice.com/forums/121579-visual-studio/…Guberniya
@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.Massimo
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.Guberniya
C
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

Celisse answered 10/12, 2013 at 16:2 Comment(4)
The above link is dead. This is available in MVC 5.1, more info here: asp.net/mvc/overview/releases/mvc51-release-notes#new-featuresFeatherveined
Thanks, I fixed the link as well.Celisse
how to merge htmlAttributes ?Legitimist
This only works with the built in / default EditorFor templates. If you implement your own, you have to follow AntonK's answer.Fraud
V
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" }, })
Valene answered 13/5, 2014 at 9:33 Comment(0)
C
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"}}))
Copro answered 29/5, 2015 at 14:57 Comment(0)
D
3

Why not just use

@Html.DisplayFor(model => model.Control.PeriodType)
Deal answered 29/12, 2010 at 22:48 Comment(2)
One reason is that DisplayFor doesn't render an input, so the value is lost on postback.Trella
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.Arrowood
C
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.

Cortneycorty answered 17/9, 2010 at 13:34 Comment(2)
Thanks, I may end up doing that if the if/else in my editor template is only required for certain fieldsCoronagraph
I though you said you couldn't change the model (not your code), so decorating with UIHint directly would not be an option.Nutshell
T
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)
Taneshatang answered 14/8, 2012 at 19:35 Comment(1)
I added Dictionary<string,string> attribs = new Dictionary<string,string>(); 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<MyProject.Models.MyObject>' 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".Puisne
S
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"] })
Sciuroid answered 29/1, 2013 at 11:8 Comment(0)
M
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

Morphia answered 26/7, 2012 at 16:1 Comment(1)
Except TextBoxFor ignores any type of DisplayFormat that you may be attempting to apply.Ocieock
N
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.

Nutshell answered 8/11, 2012 at 1:48 Comment(0)
F
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.

Felony answered 12/9, 2014 at 21:33 Comment(0)
E
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 })
Ermentrude answered 31/7, 2015 at 9:39 Comment(0)
A
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" }})
Arawakan answered 26/8, 2014 at 6:26 Comment(1)
Thanks, that works. Except for all data_xxx attributes, where I had to replace _ with - when casting source1 and source2 as IDictionary<string, object>. I made this function: private static IDictionary<string, object> ToHtmlAttributesDictionary(this IEnumerable<KeyValuePair<string, object>> dico) { return dico.ToDictionary(s => s.Key.Replace('_', '-'), s => s.Value); }Theurgy

© 2022 - 2024 — McMap. All rights reserved.