What is HtmlHelper's method to render htmlAttributes object?
Asked Answered
C

2

5

I am building an Editor Template, which will accept htmlAttributes object.

Example is simple.

In main view I have something like

@Html.EditorFor(Function(x) x.SomeProperty, New With {.htmlAttributes = New With {.class = "form-control"}}). 

And in Editor Template I need class="form-control" attribute being written.

If within template I use something like @Html.TextBoxFor(Function(x) x, ViewData("htmlAttributes")), then everything is ok. But if I build a tag manually, I have no means to output htmlAttributes, i.e. I build <input type="text" {htmlAttributes should be here} />

Is there any public method to render HTML attributes within tag correctly?

Cattleman answered 8/10, 2015 at 12:29 Comment(2)
Can you show example? htmlAttributes should be passed to helper that knows how to add it to rendered html tag. I don't understand now what are you trying to achiveSignory
Well, example is simple. In main view I have something like @Html.EditorFor(Function(x) x.SomeProperty, New With {.htmlAttributes = New With {.class = "form-control"}}). And in Editor Template I need class="form-control" attribute being written. If within template I use something like @Html.TextBoxFor(Function(x) x, ViewData("htmlAttributes")), then everything is ok. But if I build a ta manually, I have not means to output htmlAttributes, i.e. I build <input type="text" {htmlAttributes should be here} />Cattleman
L
6

I'm not sure exactly what you're looking for here. Traditionally, an editor template will just pass-through the htmlAttributes. For example:

View

@Html.EditorFor(m => m.FooString, new { htmlAttributes = new { @class = "foo" } })

String.cshtml

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue.ToString(), ViewData["htmlAttributes"])

If you're asking about how to do something like set defaults that can then be overridden or added to by passing htmlAttributes. Then, you're pretty much on your own there. There's nothing existing in MVC to help you out (at least fully). However, I did write my own HtmlHelper extension to take care of this. I actually wrote a blog post to explain what this does and how to use it. I recommend you check it out, but I'll post the code here for completeness.

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;

public static partial class HtmlHelperExtensions
{
    public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
    {
        var concatKeys = new string[] { "class" };

        var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
        var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;

        RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
            ? new RouteValueDictionary(htmlAttributesDict)
            : HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
        RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
            ? new RouteValueDictionary(defaultHtmlAttributesDict)
            : HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);

        foreach (var item in htmlAttributes)
        {
            if (concatKeys.Contains(item.Key))
            {
                defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
                    ? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
                    : item.Value;
            }
            else
            {
                defaultHtmlAttributes[item.Key] = item.Value;
            }
        }

        return defaultHtmlAttributes;
    }
}

Then, in your editor template (Date.cshtml in this example):

@{
    var defaultHtmlAttributesObject = new { type = "date", @class = "form-control" };
    var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
    var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue.ToString(), htmlAttributes)

UPDATE

I cannot use @Html.TextBox, but need to write manually <input type="text" {htmlAttributes should be here} />

Why? There's not really anything you can't do with the helper version. However, if you insist on going that route, you're going to have a hard time doing it with Razor. Assuming you can actually write it without Razor syntax errors, the code is going to be virtual unreadable. I'd suggest using TagBuilder and/or StringBuilder in pure C# to construct a string and then just make sure you return/set a variable of type MvcHtmlString:

var output = new MvcHtmlString(builder.ToString());

However, if you're going that far, it kind of negates the purpose of using an editor template, unless you're trying to override one of the default editor templates. Regardless, I'd recommend just creating your own HtmlHelper extension and then either using this directly in your view or using it in your editor template.

Lanneret answered 8/10, 2015 at 12:49 Comment(5)
Basically you are merging new values into existing htmlAttributes object. That is ok, but I need to build my tag manually, i.e. I cannot use @Html.TextBox, but need to write manually <input type="text" {htmlAttributes should be here} />.Cattleman
My case is really too big to explain. I have simplified it as much as possible just for this question. But thanks for the tip - I will give TagBuilder a chance and come back here with results. Basically I need to rebuild <select>, as DropDownList has problems with GUIDs (see my questions: #33013172 and #33015145).Cattleman
I think you're making this too hard. If the drop down is bound to a Guid property, then if the selected value can be interpreted as a Guid at all, it will bind (basically the posted value will be passed to the Guid constructor). If it's in a non-interpretable format (not a Guid, even though it may have started out life that way), then you just need some intermediary property that can translate it. Instead, bind to a string property, and then convert that string as necessary to a Guid before setting the actual Guid property.Lanneret
There's no need for any custom helper or editor template.Lanneret
Thanks for the tip, Chris. While it is not the best solution, at least it works. Having intermediary property requires writing JS/CSS code for intermediary property, not for original. So, some overhead. I will try to build my own control with TagBuilder, as select with GUIDs is involved in many places. But your solution is also very nice. Main reason why I think it is not the best solution is because it changes other layers, not the one which causes the trouble.Cattleman
S
5

Well for your case you can use static method that help you serialize your htmlAttributes to string that you need. I thik this is what you need:

public static string StringlifyHtmlAttributes(object htmlAttributes)
{
    string stringHtmlAttributes = String.Empty;
    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(htmlAttributes))
    {
        stringHtmlAttributes += string.Format("{0}=\"{1}\" ", property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
    }
    return stringHtmlAttributes;
}
Signory answered 8/10, 2015 at 13:4 Comment(6)
This is custom code. But I wonder if ASP.NET MVC provides out of the box method? I do not want to reinvent the bicycle ;)Cattleman
@Cattleman i suppose that MVC doesn't have that thing like public method. Could be that it have some inside mechanism, but i don't know where.Signory
If my memory is correct, the default HtmlHelper methods utilize TagBuilder under the hood to create the HTML. As a result, there's no need for such a method.Lanneret
Man, that took way too long to track down. The MVC source isn't always logical. Anyways, yep, they use TagBuilder (ultimately). You can see an example of what's being done with the InputHelper method starting at line 483 here: github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/….Lanneret
@ChrisPratt TagBuilder can add attributes to tag only, i suppose there are no clean way to stringlify only tags, am I right?Signory
What else would you add an attribute to? Attributes are always added to tags. If you're talking about adding attributes to a tag within a tag, you just need multiple TagBuilders. You build the inner tags and then set the InnerHtml property of the outer tag with that.Lanneret

© 2022 - 2024 — McMap. All rights reserved.