Can you control the order in which the TagBuilder class provides attributes?

I know this is kind of obsessive, but is there a way to control the order in which the TagBuilder class renders HTML tag attributes when calling ToString() ?

i.e. so that

 var tb = new TagBuilder("meta"); tb.Attributes.Add("http-equiv", "Content-Type"); tb.Attributes.Add("content", "text/html; charset=utf-8"); tb.ToString(TagRenderMode.SelfClosing) 

will return

 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 

not

 <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> 

Changing the order in which you add attributes does not change it; it is presented in alphabetical order

+4
source share
3 answers

Try using this class, which inherits TagBuilder and overrides the ToString method, creating SortedDictionary from Attributes and using this dictionary for rendering.

  public class MyTagBuilder : TagBuilder { //required to inherit from TagBuilder public MyTagBuilder(string tagName) : base(tagName){} //new hides the original ToString(TagRenderMode renderMode) //The only changes in this method is that all calls to GetAttributesString //have been changed to GetMyAttributesString public new string ToString(TagRenderMode renderMode) { switch (renderMode) { case TagRenderMode.StartTag: return string.Format(CultureInfo.InvariantCulture, "<{0}{1}>", new object[] { this.TagName, this.GetMyAttributesString() }); case TagRenderMode.EndTag: return string.Format(CultureInfo.InvariantCulture, "</{0}>", new object[] { this.TagName }); case TagRenderMode.SelfClosing: return string.Format(CultureInfo.InvariantCulture, "<{0}{1} />", new object[] { this.TagName, this.GetMyAttributesString() }); } return string.Format(CultureInfo.InvariantCulture, "<{0}{1}>{2}</{0}>", new object[] { this.TagName, this.GetMyAttributesString(), this.InnerHtml }); } //Implement GetMyAttributesString where the Attributes are changed to a SortedDictionary private string GetMyAttributesString() { var builder = new StringBuilder(); var myDictionary = new SortedDictionary<string, string>(); //new foreach (KeyValuePair<string, string> pair in this.Attributes) //new { //new myDictionary.Add(pair.Key, pair.Value); //new } //new //foreach (KeyValuePair<string, string> pair in this.Attributes) foreach (KeyValuePair<string, string> pair in myDictionary) //changed { string key = pair.Key; if (!string.Equals(key, "id", StringComparison.Ordinal) || !string.IsNullOrEmpty(pair.Value)) { string str2 = HttpUtility.HtmlAttributeEncode(pair.Value); builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", new object[] { key, str2 }); } } return builder.ToString(); } } 
+2
source

I TagBuilder.ToString() with a Reflector, and this is the key bit of code:

 foreach (KeyValuePair<string, string> pair in this.Attributes) { string key = pair.Key; string str2 = HttpUtility.HtmlAttributeEncode(pair.Value); builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", new object[] { key, str2 }); } 

So I would say no - this.Attributes is an IDictionary<string,string> interface when it lists that "the order in which the elements are returned is undefined" according to MSDN.

+1
source

I did not want to redefine all this code to change the sorting behavior, so I changed the Attributes property to a regular unsorted dictionary with reflection instead

 private class MyTagBuilder: TagBuilder { private static readonly MethodInfo tagBuilderAttrSetMethod = typeof(TagBuilder).GetProperty(nameof(Attributes)).SetMethod; public MyTagBuilder(string tagName) : base(tagName) { // TagBuilder internally uses SortedDictionary, render attributes according to the order they are added instead tagBuilderAttrSetMethod.Invoke(this, new object[] { new Dictionary<string, string>() }); } } 
0
source