zurück zum Artikel

Neues in ASP.NET 5, Teil 1: Tag Helper

Dr. Holger Schwichtenberg

Die kommende Version 5.0 von Microsofts Webframework ASP.NET ist eine komplette Neuimplementierung.

Neues in ASP.NET 5, Teil 1: Tag Helper

Die kommende Version 5.0 von Microsofts Webframework ASP.NET ist eine komplette Neuimplementierung. Neben einfacherer Bedienbarkeit und Plattformunabhängigkeit [1] gibt es einige funktionale Neuerungen für Webentwickler. Im ersten Teil einer Serie über diese Neuerungen geht es um Tag Helper.

Die Version 6.0 des MVC-Framework (Model View Controller) in ASP.NET 5.0 bietet einen neuen Ansatz für wiederverwendbare Komponenten in Form sogenannter Tag Helper (nicht zu verwechseln mit den schon in ASP.NET-MVC-Versionen 1 bis 5 verfügbaren HTML Helpers).

Mehr Infos

Softwareentwickler können mit Tag Helpers beliebige eigene Tags definieren, die dann auf der Serverseite vor der Auslieferung der HTML-Seite an den Client durch Standard-HTML-Tags ersetzt werden. Das ist vergleichbar mit den Direktiven in AngularJS; allerdings übersetzt das JavaScript-Framework sie clientseitig (also erst im Browser).

Beispielsweise können Entwickler ein selbstdefiniertes Tag <datetime/> in einer MVC View verwenden. Der folgende Code zeigt die Tag-Helper-Klasse, die das neue Tag <datetime> in ein <span>-Tag verwandelt, das aktuelles Datum und aktuelle Uhrzeit enthält.

using System;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;

namespace ASPNET5Web
{
public class DatetimeTagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "span";
output.Content = DateTime.Now.ToString();
}
}
}

Die Implementierung einer solchen Klasse ist einfach. Man schreibt eine öffentliche Klasse, die am Anfang dem Namen des selbstdefinierten Tags entspricht und auf die Wortkombination "TagHelper" endet. Alternativ können Entwickler die Annotation [HtmlElementName("Tagname")] verwenden, wenn sich die Namenskonvention nicht erfüllt werden kann oder soll. Diese Klasse muss von der Basisklasse Microsoft.AspNet.Razor.Runtime.TagHelpers.TagHelper ableiten und überschreibt die Methode Process() oder ProcessAsync(). Über den ersten Parameter (context) erhalten Entwickler Zugriff auf die Attribute des eigenen Tags. Über den zweiten (output) legen sie die Ausgabe fest.

ASP.NET 5 ist modular aufgebaut, und Tag Helper sind eine Zusatzkomponente, die von NuGet.org [4] (Milestone-Versionen) beziehungsweise MyGet.org [5] (Nightly Build) zu beziehen ist. Zudem ist vor der Verwendung der Tag Helpers die Assembly (DLL) einzubinden, in der die Tag-Helper-Klasse kompiliert wurde. Das geschieht mit @addtaghelper "Assembly-Name" entweder zu Beginn einer einzelnen View oder zentral in der Datei /Views/Shared/_ViewStart.cshtml. Die Tag-Helper-Klasse kann in der Haupt-Assembly eines Webprojekts oder einer beliebigen referenzierten Assembly liegen.

Sicherlich entsteht der Wunsch, die eigenen HTML-Tags mit Attributen zu parametrisieren. Innerhalb der Process()-Methode der Tag-Helper-Klasse haben Entwickler über die Liste context.AllAttributes Zugriff auf die Attribute, die das eigene Tag in der View besitzt. Schöner ist es aber, der Tag-Helper-Klasse eine öffentliche Property zu geben, die so heißt wie das Tag-Attribut. Dann befüllt ASP.NET MVC die Property mit dem Wert des Tag-Attributs. Alternativ kann man die Namensbindung über die Annotation [HtmlAttributeName("name")] explizit machen.

Die folgende Variante der Klasse DatetimeTagHelper besitzt zwei HTML-Attribute, über die sich Anzeigeformat und Kulturkreiseinstellung im .NET-Stil steuern lassen. Um nur das Datum ohne Uhrzeit in deutschem Format auszugeben, benutzt man in der View: <datetime format="d" culture="de-de" />:

public class DatetimeTagHelper : TagHelper
{
public string Format { get; set; }
public string Culture { get; set; }

public override void Process(TagHelperContext context,
TagHelperOutput output)
{
System.Globalization.CultureInfo ci;
if (!String.IsNullOrEmpty(Culture))
{
ci = new System.Globalization.CultureInfo(Culture);
}
else
{
ci = System.Threading.Thread.CurrentThread.CurrentCulture;
}
output.TagName = "span";
output.Content = DateTime.Now.ToString(Format, ci);
}
}

Der Tag Helper <DateTime> hat das Tag und den Inhalt komplett ausgetauscht. Oft wollen Entwickler aber auch den Inhalt weiterverwenden. So könnten sie ein <Textbox>-Tag erfinden:

<textbox size="50">Ihr Name:</textbox>

Die Ausgabe soll

<div><label>Ihr Name:</label><input type='textbox'
size='50'></div>

sein. Eine Möglichkeit zur Realisierung ist die Verwendung der Eigenschaften PreContent und PostContent, mit denen Entwickler festlegen können, was vor beziehungsweise nach dem bisherigen Inhalt ausgegeben werden soll. Den Tag-Namen dürfen sie auf eine leere Zeichenkette setzen, wenn der Inhalt aus einer Folge mehrerer Tags besteht.

public class TextboxTagHelper : TagHelper
{
[HtmlAttributeName("asp-size")]
public string Size { get; set; }

public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "";
output.PreContent = "<div><label>";
output.PostContent = "</label>" +
"<input type='textbox' size='" + Size + "'></div>";
}
}

PreContent und [/i]PostContent[/i] reichen aber nicht immer. Man stelle sich vor, dass man den Inhalt der <textbox>-Tags nicht nur als Bezeichnungstext, sondern auch zusätzlich noch im Platzhalterattribut des <input>-Tags verwenden will. So eine Doppelverwendung geht mit PreContent und PostContent gar nicht.

Den bisherigen Inhalt eines eigenen Tags auszulesen, erlaubt Microsoft nur asynchron mit der Methode context.GetChildContentAsync(). Damit Entwickler sie verwenden können, müssen sie anstelle der Methode Process() die asynchrone Variante ProcessAsync() in der Tag-Helper-Klasse implementieren. Das folgende Listing zeigt die Neufassung, die den bisherigen Inhalt ausliest und zweimal in der Ausgabe (in <label> und in <input>) verwendet.

public class TextboxTagHelper : TagHelper
{
[HtmlAttributeName("size")]
public string Size { get; set; }

public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
var inhalt = await context.GetChildContentAsync();
output.TagName = "";
output.Content = "<div><label>" + inhalt + "</label>" +
"<input type='textbox' size='" + Size + "' placeholder='"
+ inhalt + "'></div>";
}
}

Tag-Helper-Klassen können sich auch auf Standard-HTML-Tags beziehen und diese modifizieren, zum Beispiel neue HTML-Attribute ergänzen. Microsoft liefert eine Reihe von Tag Helpers mit, zum Beispiel sorgt die Klasse AnchorTagHelper dafür, dass die Attribute asp-controller und asp-action in einem <a>-Tag zum href-Attribut umgewandelt werden. Aus

<a asp-controller="Flug" asp-action="Buchen">Link zur Buchungsseite </a> 

wird dann

<a href="/Flug/Buchen">Link zur Buchungsseite</a>

Die bisherige Unterstützung für die Link-Erzeugung in ASP.NET MVC sah so aus:

@Html.ActionLink("Link zur Buchungsseite", "Buchen", "Flug")

Das nennt Microsoft einen HTML Helper [6]. Ihn findet man in ASP.NET MVC 6 weiterhin. Andere Tag Helper dieser Art gibt es für die Standard-HTML-Tags <form>, <label>, <input>, <select>, <script> und <link>. Nach einiger Diskussion [7] hat sich Microsoft entschlossen, für die Attribute der mitgelieferten Tag Helpers, die ein Standard-HTML-Tag erweitern, das Präfix "asp-" für alle Erweiterungsattribute zu verwenden. Das ist aber nicht Pflicht für eigene Tag Helpers. Auch Microsoft wendet das Präfix nicht bei Tag-Namen, die es in Standard-HTML nicht gibt. Beispiele für solche eingebauten Tag Helper sind <environment> (Inhalte rendern abhängig vom Wert der Umgebungsvariablen ASPNET_ENV) und <cache> (Inhalte für eine bestimmte Dauer zwischenspeichern).

Mit Tag-Helper-Klassen kann man HTML-Code stark verkürzen. Das folgende Beispiel für Twitter Bootstrap zeigt das. Typischer HTML-Quellcode unter Einsatz von Twitter Bootstrap [8] sieht so aus:

<div class="row">
<div class="col-xs-4">
Eigene Tag Helper-Klassen
</div>
<div class="col-xs-8">
<p class="bg-danger">Gefahr</p>
</div>
</div>

Beim Schreiben von ein paar Tag-Helper-Klassen lässt sich das verkürzen:

<row>
<xs4>
Eigene Tag-Helper-Klassen
</xs4>
<xs8>
<danger>Gefahr<danger>
</xs8>
</row>

Die Implementierung der hier verwendeten Tag Helper <row>, <xs4>, <xs8> und <danger> zeigen die folgende C#-Codefragemente.

using Microsoft.AspNet.Razor.Runtime.TagHelpers;

namespace ASPNET5
{
public class RowTagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "row");
}
}
}
using Microsoft.AspNet.Razor.Runtime.TagHelpers;

namespace ASPNET5
{
public class XS1TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-1");
}
}

public class XS2TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-2");
}
}

public class XS3TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-3");
}
}

public class XS4TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-4");
}
}
public class XS5TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-5");
}
}
public class XS6TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-6");
}
}
public class XS7TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-7");
}
}
public class XS8TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-8");
}
}
public class XS9TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-9");
}
}
public class XS10TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-10");
}
}
public class XS11TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-11");
}
}
public class XS12TagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-12");
}
}

}
using Microsoft.AspNet.Razor.Runtime.TagHelpers;

namespace
ASPNET5
{
public class DangerTagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "p";
output.Attributes.Add("class", "bg-danger");
}
}
}

Ein alternativer (oder zusätzlicher) Ansatz zu den zwölf XS-Tag-Helper-Klassen ist, nur eine einzige Tag-Helper-Klasse mit Property zu schreiben:

public class XSTagHelper : TagHelper
{
public int Size { get; set; }
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.Add("class", "col-xs-" + Size);
}
}

Dieses <xs>-Tag kann man dann so verwenden:

<xs size="4">
Inhalt
</xs>

Ein letztes Beispiel ist die Klasse TableTagHelper, die dafür sorgt, dass alle HTML-Tabellen automatisch einige Bootstrap-Klassen zugewiesen bekommen. Alle Tabellenzeilen haben somit einen anderen Farbton (Bootstrap-CSS-Klasse table-striped) und werden beim Überfahren mit der Maus hervorgehoben (Bootstrap-CSS-Klasse table-hover).

using Microsoft.AspNet.Razor.Runtime.TagHelpers;

namespace ASPNET5
{
public class TableTagHelper : TagHelper
{
public override void Process(TagHelperContext context,
TagHelperOutput output)
{
output.Attributes.Add("class", "table table-striped table-hover");
}
}
}

Einige Dinge sind bei Tag Helpers noch zu beachten:

      [Activate] private ViewContext ViewContext { get; set; }

Tag Helper bieten elegante Möglichkeiten, wiederverwendbare Komponenten für ASP.NET MVC 6 zu schaffen. Code in ASP.NET MVC Views lässt sich damit prägnanter gestalten. Bei den Tag Helpers kann man sich auch an die Webserver-Steuerelemente von ASP.NET Webforms erinnert fühlen. Tag Helper sind aber viel einfacher strukturiert; es gibt weder ein komplexes Ereignismodell, noch Templates und auch keine Designer-Unterstützung.

Wie Tag Helper letztlich die Performance der Seitenerzeugung negativ beeinflussen, wird man noch sehen müssen. ASP.NET 5 ist derzeit noch in der Beta-Phase, bei der man noch keine aussagekräftigen Leistungsmessungen vornehmen kann. Die gezeigten Beispiele basieren zum Teil auf dem Nightly Build vom Stand 14. Februar 2015 (Name: v1.0.0-Beta 4-12718). Den (leider bisher extrem spärlich dokumentierten) Quellcode findet man auf GitHub [9].

Dr. Holger Schwichtenberg
leitet das Expertennetzwerk www.IT-Visions.de [10], das Beratung, Schulungen und Softwareentwicklung Umfeld von .NET und Webtechniken anbietet. Er hält Vorträge auf Fachkonferenzen und ist Autor zahlreicher Fachbücher.
(ane [11])


URL dieses Artikels:
https://www.heise.de/-2573410

Links in diesem Artikel:
[1] https://www.heise.de/news/ASP-NET-vNext-Aus-MVC-Web-API-und-Web-Pages-wird-MVC-6-2188601.html
[2] https://www.heise.de/hintergrund/Neues-in-ASP-NET-5-Teil-2-View-Components-2639138.html
[3] https://www.heise.de/hintergrund/Neues-in-ASP-NET-5-Teil-3-Aenderungen-in-Visual-Studio-2015-2751695.html
[4] http://www.nuget.org/packages/Microsoft.AspNet.Mvc.TagHelpers
[5] https://www.myget.org/gallery/aspnetvnext
[6] https://msdn.microsoft.com/de-de/library/system.web.mvc.htmlhelper%28v=vs.118%29.aspx
[7] https://github.com/aspnet/Razor/issues/88
[8] http://getbootstrap.com/
[9] https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNet.Mvc.TagHelpers
[10] http://www.it-visions.de/start.aspx
[11] mailto:ane@heise.de