Der Titel hat es in sich. ASP.NET MVC ist nicht XHTML konform! Zugegeben, eine etwas reißerische, provokante Aussage – aber völlig korrekt. Ich habe mich in letzter Zeit mit dem HtmlHelper und Erweiterungen zu dem Helper etwas näher beschäftigt. Wie es so ist, wenn man Extensions zum HtmlHelper entwickelt, habe ich natürlich den TagBuilder eingesetzt.
Zügiger als es mir selbst lieb gewesen ist stellte ich durch meine Tests fest, daß der TagBuilder beim Element-Namen sowie allen Attribut-Namen Groß- und Kleinschreibung beachtet:
[Test]
public void Cms_Tag_With_Invalid_Slot_Returns_Div_Only()
{
CmsTag cmsTag = new CmsTag();
int invalidSlot = 0;
string cmsContainer = cmsTag.GetContainer(invalidSlot);
Assert.AreEqual("<div></div>", cmsContainer);
}
Der Test sieht ja ganz passabel aus. Aber als ich dann die Methode dazu implementiert habe, ungefähr so:
public string GetContainer(int slot)
{
TagBuilder tagBuilder = new TagBuilder("DIV");
if (slot < 1)
return tagBuilder.ToString();
// ab hier geht es mit dem echten slot weiter...
}
machte es mich stutzig, dass mein Test immer noch rot war. Wieso? Na klar, ich hatte im Konstruktor des TagBuilders den Element-Namen groß geschrieben ("DIV"), während ich es im Test klein schrieb ("<div></div>"). Hmm. Ich musste überlegen. Warum habe ich denn im Test das DIV klein erwartet?
Na Klar! Meine Gewohnheit hatte mich eingefangen. Denn ich bin es gewohnt, alle Tags und Attribute klein zu schreiben. Nicht nur etwa, weil mir es so besser gefällt - sondern aus einem ganz praktischen und gleichermaßen wichtigen Grund: XHTML.
XHTML verlangt alle Element-Namen und Attribut-Namen explizit kleingeschrieben. Also soetwas wie <DIV ID="test"> ist schon mal grundsätzlich nicht XHTML-konform.
Nach dieser lapidaren Feststellung, dass der Tagbuilder Tags und Attribute so annimmt, wie man sie angegeben hat, machte ich mit Gedanken über das "Wieso". Der Fix hin zur XHTML-Konformität ist ja schließlich trivial. Statt des großgeschriebenen "DIV" im Konstruktor des TagBuilders einfach ein kleingeschriebenes "div" - das sollte es gewesen sein. Doch damit wollte ich mich nicht zufrieden geben. Denn mir war klar, die gleiche Konvention gilt schließlich auch für Attribute. So etwas wie:
TagBuilder builder = new TagBuilder("div");
builder.Attributes.Add("Title", "This is strange.");
würde das Problem wieder hervorrufen. Im Übrigen ist das Problem gerade bei dem schönen "Syntactic Sugar" mit der Kombination Anonymer Typ + Objekt-Initialisierer wirklich mehr als unschön - ich würde sogar sagen absolut störend.
Konsequenterweise kann das nicht der Weisheit letzter Schluß sein. Nach ein paar Minuten gründlicher Überlegung und der Durchsicht des Source-Codes des TagBuilders kam ich zur unweigerlichen Einsicht, dass der TagBuilder nicht (oder nur halbherzig) für XHTML-konforme Dokumente entwickelt wurde. Schade, denn deutliche Hinweise gibt es ja (z.B. mit dem TagRenderMode). Das führt dann unweigerlich zur Frage, ob man generell den TagBuilder umschreiben sollte, oder um einen "XHTML-Konformitätsmodus" erweitern sollte. Im Raum stünden dann drei wesentliche Varianten:
// variante 1 - der existierende tagbuilder wird intern umgeschrieben
TagBuilder builder = new TagBuilder("DIV");
string tag = builder.ToString();
// variante 2 - der tagbuilder wird um einen xhtml-modus erweitert
TagBuilder builder = new TagBuilder("DIV");
string tag = builder.ToString(TagRenderConvention.XHtml);
// variante 3 - es gibt einen speziellen (warscheinlich von tagbuilder geerbten) builder
TagBuilder builder = new XHtmlTagBuilder("DIV");
string tag = builder.ToString();
Auf den ersten Blick würde ich mich persönlich wohl für die erste Variante entscheiden. Denn schließlich bedeutet XHTML-Konformität ja nicht, dass man nicht mehr HTML-Konform ist. Eine "abwärts"- bzw. "schlechtwärts"-Kompatibilität ist also gegeben. Überdies muss man auch bedenken, daß die meisten Websites heutzutage sowieso sich in Richtung XHTML bewegen (müssen). Ein weiterer Aspekt ist sicherlich die Zukunft mit HTML5, welche XHTML als Grundlage hat.
So, nach dem ich jetzt so viele Worte über ein Problem geschrieben habe, welches mit einem einfachen ToLower() lösbar ist, möchte ich noch ein wenig zur Lösung dieser kleinen Schwierigkeit mit Code beitragen. Doch wer jetzt denkt, ich würde ASP.NET MVC patchen, der hat sich getäuscht. Statt dessen habe ich ein kleines Set von Tests zur XHTML-Konformität geschrieben. Wenn alle Tests grün sind, dann ist der TagBuilder auch XHTML-Konform
.
byGolo RodenonMarch 1st 2010Hi Ilker,
da die TagBuilder-Klasse ja nicht nur für XHTML gedacht ist, muss es auch möglich sein, in HTML durchaus erlaubte Schreibweisen zu erzeugen. Klar hast Du schon recht, wenn Du sagst, dass komplett Lowercase auch korrektes HTML ist – aber vielleicht will man das ja nicht, warum auch immer.
Insofern gefällt mir auch Variante 1 als Lösungsvorschlag nicht wirklich, am ehesten wäre ich persönlich für Variante 3 zu haben – weil man dann verschiedene Tagbuilder hätte, die das gleiche Interface implementieren, was für DI die perfekte Voraussetzung ist.
Ansonsten Variante 2, damit es zumindest konfigurierbar ist.
Spannend fände ich dann die Frage, ob zB auch die verschiedenen HTML-Modi wie Strict oder Transitional unterstützt werden sollten – und ob die TagBuilder-Klasse gegebenenfalls bei nicht erlaubten Tags entsprechend reagiert … das ganze kann also recht schnell ein sehr großes Thema werden.
Viele Grüße,
Golo
byAlbert WeinertonMarch 1st 2010Neben der Tatsache das XHTML 1.x/2.0 gescheitert ist.
ASP.NET MVC bietet dir halt die komplette Kontrolle, Du darfst halt auch mist bauen. Genau wie die HTML Helper dich auch nicht dabei unterstützen eindeutige IDs zu erzeugen.