If you're a seasoned ASP.NET MVC developer, you most likely already know that most Razor HTML helpers let you use an anonymous object in a parameter called htmlAttributes. As the name says, its members will be used to generate the HTML attribures of that element.
For example, this:
1 |
Html.TextBoxFor(m => m.Name, new { @class="name", @readonly="readonly" }) |
Will become this:
1 |
<input type="text" id="Name" name="Name" class="name" readonly="readonly" /> |
The only two notable exceptions are Html.EditorFor and Html.DisplayFor, which uses a slightly different syntax:
1 2 |
Html.EditorFor(m => m.Name, new { htmlAttributes = new { @class="name", @readonly="readonly" } } ) Html.DisplayFor(m => m.Name, new { htmlAttributes = new { @class="name", @readonly="readonly" } } ) |
As we can see, even though they're using an anonymous object into an anonymous object container, the overall logic is basically the same.
As you probably know, anonymous types are created by the compiler at runtime level, so they aren't exactly handy when we want to modify them at execution level... to put it in other words, it just cannot be done.
The Problem
So, what if we need to assign these HTML attributes programmatically and dinamically?
As a matter of fact, we can do a lot of things using variables, inline methods, conditional blocks or other viable strategies:
1 2 |
var className = (IsHidden) ? "hidden" : string.Empty; Html.TextBoxFor(m => m.Name, new { @class=className, placeholder=GetPlaceHolderText() }) |
However, there are scenarios where we need to conditionally add (or skip) certain attributes as a whole. The perfect example of this is a TextBoxFor element which we would like to set readonly only in some circumstances. The readonly attribute is a tricky one, because it works as a property setting and not as a valued attribute. Its mere presence is enough to set the element read-only, despite of its value: this basically means that there's no way to explicitly set it to FALSE.
1 2 3 4 5 6 |
<input type="text" readonly /> <!-- this will be read-only --> <input type="text" readonly="true" /> <!-- will also be read-only --> <input type="text" readonly="readonly" /> <!-- will be read-only as well --> <input type="text" readonly="" /> <!-- guess what? still read-only --> <input type="text" readonly="false" /> <!-- still read-only(!) --> <input type="text" /> <!-- the ONLY way to not have it read-only --> |
For the above reason, we cannot do something like this:
1 |
Html.TextBoxFor(m => m.Name, new { @class=className, @readonly=IsReadOnly() }) |
The Solution
There are a lot of viable workarounds around the web: most of them are collected in this StackOverflow threads. However, no one of the listed one was DRY enough for my tastes. For this very reason, I did some research and eventually I came up writing this simple helper method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/// <summary> /// Gets an object containing a htmlAttributes collection for any Razor HTML helper component, /// supporting a static set (anonymous object) and/or a dynamic set (Dictionary) /// </summary> /// <param name="fixedHtmlAttributes">A fixed set of htmlAttributes (anonymous object)</param> /// <param name="dynamicHtmlAttributes">A dynamic set of htmlAttributes (Dictionary)</param> /// <returns>A collection of htmlAttributes including a merge of the given set(s)</returns> public static IDictionary<string, object> GetHtmlAttributes( object fixedHtmlAttributes = null, IDictionary<string, object> dynamicHtmlAttributes = null ) { var rvd = (fixedHtmlAttributes == null) ? new RouteValueDictionary() : HtmlHelper.AnonymousObjectToHtmlAttributes(fixedHtmlAttributes); if (dynamicHtmlAttributes != null) { foreach (KeyValuePair<string, object> kvp in dynamicHtmlAttributes) rvd[kvp.Key] = kvp.Value; } return rvd; } |
The above technique allows me to deal with this issue just the way I wanted to, without having to repeat a single line of code.
It can be used in the following way:
1 2 3 |
var dic = new Dictionary<string,object>(); if (IsReadOnly()) dic.Add("readonly", "readonly"); Html.TextBoxFor(m => m.Name, GetHtmlAttributes(new { @class="someclass" }, dic)) |
Conclusion
That's it for now: I hope that the above method will help most ASP.NET MVC developers to deal with their htmlAttributes!
Hey bud, appreciate the article. I realize that this is an old article but I just wanted to note one thing. Your article uses the spelling, “dinamically” which is incorrect. The correct spelling is “Dynamically”.
https://www.merriam-webster.com/thesaurus/dynamically
Hi friend!!
Thanks about the post!! It was very useful.
I don’t know if you passed thru what I passed.
I dont’t know if it is because the front-end I’m using ( Metro UI ), but it turns out that in my code the “select” and “input” controls behave different.
The “select” do not accept the “readonly” while the “input” does, so, I changed you code and finish with this bellow.
public static IDictionary<string, object> GetHtmlAttributes(this HtmlHelper helper, object fixedHtmlAttributes = null, object dynamicHtmlAttributes = null)
{
var rvd = (fixedHtmlAttributes == null) ? new RouteValueDictionary() : HtmlHelper.AnonymousObjectToHtmlAttributes(fixedHtmlAttributes);
if (dynamicHtmlAttributes != null)
{
Type t = dynamicHtmlAttributes.GetType();
foreach(var prop in dynamicHtmlAttributes.GetType().GetProperties())
{
rvd[prop.Name] = prop.GetValue(dynamicHtmlAttributes);
}
}
return rvd;
}
I just made it as a extension to the class HtmlHelper, and changed the “dynamicHtmlAttributes” parameter to “object”.
Now I can use like this:
Html.TextBoxFor(m => m.Name, GetHtmlAttributes(new { @class=”someclass” }, (adding ? null : new { readonly = “readonly” })))
Or
Html.DropDownList(m => m.Name, GetHtmlAttributes(new { @class=”someclass” }, (adding ? null : new { disabled= “disabled” })))
Thank you mate! Your solution was exactely i was looking for!
if (condition)
{
@Html.CheckBox(“chk”, (bool)check, new { })
}
else
{
@Html.CheckBox(“chk”, (bool)check, new { readonly = “readonly” })
Duplicate code is bad news.