10

If I am passing HtmlAttributes into a template, like this:

@Html.DisplayFor(m => m.FirstName, new { htmlAttributes = new { @class = "orangetxt strongtxt" } })

In my template, how would I inject these into my HTML:

<span @ViewData["htmlAttributes"]>@Model</span>

This almost works, but it does some pretty weird stuff, so I'm assuming this isn't the way to go.

I realize I can accomplish this with an HtmlHelper extension method to render the full HTML element (span, in this case) and pass in the attributes that way, but is there a way to just render attributes straight into an HTML element, like the above example?

Jerad Rose
  • 15,235
  • 18
  • 82
  • 153

4 Answers4

9

The below extension method will allow me to convert HtmlAttributes to a string:

    public static MvcHtmlString RenderHtmlAttributes<TModel>(
        this HtmlHelper<TModel> htmlHelper, object htmlAttributes)
    {
        var attrbituesDictionary = new RouteValueDictionary(htmlAttributes);

        return MvcHtmlString.Create(String.Join(" ", 
            attrbituesDictionary.Select(
                item => String.Format("{0}=\"{1}\"", item.Key, 
                htmlHelper.Encode(item.Value)))));
    }

Then, to render them within the tag, I can just do this:

<span @Html.RenderHtmlAttributes(ViewData["htmlAttributes"])>@Model</span>
Toine Seiter
  • 437
  • 4
  • 20
Jerad Rose
  • 15,235
  • 18
  • 82
  • 153
  • Hmm i may be wrong here, but your intended usage as based on the question, seems to me will not work..can you perhaps include how you are using the helper – Ahmad Oct 20 '11 at 15:29
5

Jerad Rose's answer is good, but I ran into couple of issues with it:

  • It does not not convert underscores to dashes in attribute names
  • It does not handle no-value attributes gracefully

To address first issue, use HtmlHelper.AnonymousObjectToHtmlAttributes.

Below is my modification of Jerad's method:

public static MvcHtmlString RenderHtmlAttributes(this HtmlHelper helper, object htmlAttributes)
{
        if (htmlAttributes == null) return new MvcHtmlString(String.Empty);
        var attrbituesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
        return new MvcHtmlString(String.Join(" ", attrbituesDictionary.Select(item => string.IsNullOrEmpty((string)item.Value) ? String.Format("{0}", item.Key) : String.Format("{0}=\"{1}\"", item.Key, helper.Encode(item.Value)))));
}
Hlefreyr
  • 96
  • 1
  • 3
1

Try this instead,

@Html.DisplayFor(m => m.FirstName, 
                 new { htmlAttributes = "class = orangetxt strongtxt"})

This will render a string, whereas your version did do weird stuff, rendered { } as part of the output.

Ahmad
  • 22,657
  • 9
  • 52
  • 84
  • This is close, but still not exactly what I'm looking for. I don't really want to have to deal with all of the string conversion and encoding. I actually came up with an extension method to render HTML attributes, and will post that below. – Jerad Rose Oct 20 '11 at 13:56
  • @JeradRose - based on what you have current, nothing changes except the actual call to Displayfor so no conversions and encoding – Ahmad Oct 20 '11 at 15:26
  • 1
    Yes, but the above was just an example. I may have cases where the attributes need to be a bit more complex, and will require quotes. For example, if I need to include `class = "orangetxt strongtxt" id = "myId"`, then it starts to get a little crazy using your solution. – Jerad Rose Oct 20 '11 at 15:42
  • 1
    @JeradRose - fair enough, I didn't catch the part about using multiple attributes, my bad... – Ahmad Oct 20 '11 at 15:55
0

DisplayFor() is used to render the template that matches the property type.

Display templates are .cshtml files inside /DisplayTemplates folder which in turn is inside a view folder (i.e. any folder from Home, Shared or even a specific controller).

An example.

If you've a String.cshtml template like this inside /Views/Shared:

@model String

@if (string.IsNullOrEmpty(Model)) {
   <span>(no string)</span>
}
else {
   <span>@Model</span>
}

Every time you call DisplayFor() for a string property:

DisplayFor(model => model.MyStringProperty);

It renders the template accordingly to the string's value. You can be more specific and put /DisplayTemplates inside a specific View folder and them only calls from those views are affected by the template.


In your case you can be even more specific and call DisplayFor() with a particular template.

Suppose you've a template for a particular property, called MyPropertyTemplate.cshtml. You would call DisplayFor() like this:

DisplayFor(model => model.MyProperty, "MyPropertyTemplate");

And them, inside that template you can have whatever HTML attributes you want.

@model MyProperty

<span class="orangetxt strongtxt">@MyProperty.ToString()</span>

PS: When it doesn't find a template I guess it only calls model.Property.ToString() without additional html.

FYI: EditorFor(), for example, works in a similar way but it uses /EditorTemplates folder.

Joao
  • 7,366
  • 4
  • 32
  • 48
  • 1
    Thanks. I understand how templates work, I just need to figure out how to pass in HtmlAttributes to a template and render them like in the example above, without hard-coding these in a new template. I want them to be more flexible. – Jerad Rose Oct 20 '11 at 03:21
  • 1
    The problem with templates, Jerad, is where do the htmlAttributes go? You could have a huge template with many divs and what not. What you want to do will be very custom and it basically involves writing your own HtmlHelper methods to output with the custom htmlAttributes. – Buildstarted Oct 20 '11 at 03:42
  • 1
    True, I understand that with complex templates, it's hard to know what the `htmlAttributes` would get applied to. In my case, it will always be applied to the element immediately surrounding the model value. If I wanted to, say, include another option to apply custom attributes to the label, then I could add support for `labelHtmlAttributes`, for example. – Jerad Rose Oct 20 '11 at 14:06