Catalin
Catalin

Reputation: 11721

TagBuilder.MergeAttributes does not work as expected

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?

Upvotes: 12

Views: 10194

Answers (3)

David McClelland
David McClelland

Reputation: 2756

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;
        }
    }
}

Upvotes: 5

user755404
user755404

Reputation:

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.

Upvotes: 33

tpeczek
tpeczek

Reputation: 24125

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));
}

Upvotes: 18

Related Questions