TagBuilder.MergeAttributes does not work as expected
Asked Answered
C

3

12

I am trying to make a HtmlHelper and I need to allow users to add their own custom attributes to the html tag.

I tried to do this using the TagBuilder class, but it seems that instead of merging the attributes, it just replaces them.

This is what I did in C#:

public static MvcHtmlString List(HtmlHelper helper, object htmlAttributes)
{
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

    var tag = new TagBuilder("div");
    tag.AddCssClass("myClass");
    tag.MergeAttributes(attributes, false);

    // tag class property has value "myClass", not "myClass testClass"

    return new MvcHtmlString("<div>");
}

This is my view:

@Html.List(new { @class = "testClass" })

What am I doing wrong?

Chromite answered 28/9, 2012 at 7:34 Comment(1)
Similar question already answered by Darin Dimitrov. #9520451Whitherward
K
17

The TagBuilder.MergeAttributes method doesn't work how you expect it to. This is the exact code of this method:

    public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes, bool replaceExisting)
    {
        if (attributes != null)
        {
            foreach (var entry in attributes)
            {
                string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
                string value = Convert.ToString(entry.Value, CultureInfo.InvariantCulture);
                MergeAttribute(key, value, replaceExisting);
            }
        }
    }

    public void MergeAttribute(string key, string value, bool replaceExisting)
    {
        if (String.IsNullOrEmpty(key))
        {
            throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "key");
        }

        if (replaceExisting || !Attributes.ContainsKey(key))
        {
            Attributes[key] = value;
        }
    }

As you can see it only adds new attributes to the collection (if replaceExisting is set to true it also replaces the ones already in the collection). It doesn't perform and attributes values merging logic. If you want to merge values you need to do it by yourself:

public static MvcHtmlString List(this HtmlHelperhelper, object htmlAttributes)
{
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);        
    if (attributes.ContainsKey("class"))
        attributes["class"] = "myclass " + attributes["class"];
    else
        attributes.Add("class", "myClass");

    var tag = new TagBuilder("div");
    tag.MergeAttributes(attributes, false);

    return new MvcHtmlString(tag.ToString(TagRenderMode.Normal));
}
Katheryn answered 28/9, 2012 at 8:52 Comment(2)
See my answer, it's just a case of using the two method in wrong orderTijerina
Working Fine :)Sessions
T
33

The MergeAttributes overrides the attributes already on the tag, AddCssClass appends the name in the class value.

So just switch it around and it will work;

    tag.MergeAttributes(attributes, false);
    tag.AddCssClass("myClass");

AddCssClass will append to the class name(s) merged above it.

Tijerina answered 8/11, 2012 at 11:42 Comment(1)
This is the correct answer and should be the accepted answerMoore
K
17

The TagBuilder.MergeAttributes method doesn't work how you expect it to. This is the exact code of this method:

    public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes, bool replaceExisting)
    {
        if (attributes != null)
        {
            foreach (var entry in attributes)
            {
                string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
                string value = Convert.ToString(entry.Value, CultureInfo.InvariantCulture);
                MergeAttribute(key, value, replaceExisting);
            }
        }
    }

    public void MergeAttribute(string key, string value, bool replaceExisting)
    {
        if (String.IsNullOrEmpty(key))
        {
            throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "key");
        }

        if (replaceExisting || !Attributes.ContainsKey(key))
        {
            Attributes[key] = value;
        }
    }

As you can see it only adds new attributes to the collection (if replaceExisting is set to true it also replaces the ones already in the collection). It doesn't perform and attributes values merging logic. If you want to merge values you need to do it by yourself:

public static MvcHtmlString List(this HtmlHelperhelper, object htmlAttributes)
{
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);        
    if (attributes.ContainsKey("class"))
        attributes["class"] = "myclass " + attributes["class"];
    else
        attributes.Add("class", "myClass");

    var tag = new TagBuilder("div");
    tag.MergeAttributes(attributes, false);

    return new MvcHtmlString(tag.ToString(TagRenderMode.Normal));
}
Katheryn answered 28/9, 2012 at 8:52 Comment(2)
See my answer, it's just a case of using the two method in wrong orderTijerina
Working Fine :)Sessions
R
4

I needed other attributes to be merged (besides just class), so AddCssClass() wasn't sufficient. I wrote an extension method to do what I thought MergeAttributes was supposed to do:

public static class TagBuilderExtensions
{
    public static void TrueMergeAttributes(this TagBuilder tagBuilder, IDictionary<string, object> attributes)
    {
        foreach (var attribute in attributes)
        {
            string currentValue;
            string newValue = attribute.Value.ToString();

            if (tagBuilder.Attributes.TryGetValue(attribute.Key, out currentValue))
            {
                newValue = currentValue + " " + newValue;
            }

            tagBuilder.Attributes[attribute.Key] = newValue;
        }
    }
}
Rabble answered 5/2, 2014 at 21:0 Comment(1)
This logic will probably not work as intended if you need to merge/override anything other than classes.Ezequiel

© 2022 - 2024 — McMap. All rights reserved.