Neues in ASP.NET 5, Teil 2: View Components

View Components sind neue Instrumente zum Erstellen wiederverwendbarer Webseitenbausteine in ASP.NET MVC 6.0.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 9 Min.
Von
  • Dr. Holger Schwichtenberg
Inhaltsverzeichnis

View Components sind neue Instrumente zum Erstellen wiederverwendbarer Webseitenbausteine in ASP.NET MVC 6.0. Sie bieten eine Kompetenztrennung entsprechend dem MVC-Prinzip (Model View Controller).

ASP.NET MVC bietet schon in den bestehenden Releases einige Instrumente der Wiederverwendbarkeit. Dazu gehören Razor Helper (@helper/@functions), HTML Helper Extension Methods, Partial Views und die ViewStart.cshtml-Datei. In ASP.NET MVC 6.0, das Kernbestandteil des plattformunabhängigen ASP.NET 5.0 ist, gibt es nun zwei weitere Möglichkeiten, Webseiten in wiederverwendbare Bausteine zu strukturieren: Tag Helper und View Components.

Mehr Infos

Neues in ASP.NET 5

Webseiten in ASP.NET MVC bestehen aus einer View (HTML- plus Programmcode in Razor-Syntax) und einem Controller (reiner Programmcode). Eine Partial View ist ein Teil einer View, die sich mit @Html.Partial() oder @HTML.RenderPartial() in mehrere Views einbinden lässt. Sie besitzt aber keinen eigenen Controller. Hier arbeitet im Hintergrund immer der Controller der View, in der die Partial View eingebunden wurde. An dieser Stelle setzt das neue Konzept der View Components in ASP.NET MVC 6.0 an. Sie bestehen im Gegensatz zur Partial View sowohl aus einer View als auch aus einem Controller.

Der Controller einer View Component ist eine .NET-Klasse, die von der Klasse Microsoft.AspNet.Mvc.ViewComponent erbt. Sie muss öffentlich, darf nicht abstrakt und kann auch nicht in eine andere Klasse eingebettet sein, dafür aber an beliebiger Stelle im Projekt liegen. Der Name der Controller-Klasse für die View Component kann, muss aber nicht einer Konvention folgen. Wenn der Klassenname auf die Zeichenfolge "ViewComponent" endet, bilden die davor stehenden Buchstaben den Namen der View Component. Möchten Entwickler dieser Konvention nicht folgen, müssen sie die Klasse mit [ViewComponent(Name=xy)] annotieren. Der folgende Code zeigt die Controller-Klasse für die View Component "FlugTabelle", die mit dieser Annotation arbeitet, weil die Klasse selbst "FlugTabelleController" heißt.

using Microsoft.AspNet.Mvc;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ASPNET5.Components
{
[ViewComponent(Name = "FlugTabelle")]
public class FlugTabelleController : ViewComponent
{
EF7_Kontext.WWWingsContext ctx = new EF7_Kontext.WWWingsContext();
public FlugTabelleController()
{
System.Diagnostics.Debug.WriteLine("FlugTabelleViewComponent
instanziiert: Kontextinstanz #" + ctx.ID);
}

public IViewComponentResult Invoke(string Ort, int Anzahl)
{
// Ort merken für die View
ViewBag.Ort = Ort;

// synchrone Datenbankabfrage
var liste = ctx.FlugSet
.Where(f => f.Datum > DateTime.Now.AddDays(2) && f.FreiePlaetze > 0)
.Where(f => f.Abflugort == Ort || f.Zielort == Ort)
.OrderBy(f => f.Datum)
.Take(Anzahl)
.ToList();

// fallweise eine der beiden Views aufrufen
if (liste.Count == 0) return View("KeineGefunden", liste);
return View(liste);
}

public async Task<IViewComponentResult> InvokeAsync(string Ort,
int Anzahl)
{
// Ort merken für die View
ViewBag.Ort = Ort;

// asynchrone Datenbankabfrage
var listeAsync = ctx.FlugSet
.Where(f => f.Datum > DateTime.Now.AddDays(2) && f.FreiePlaetze > 0)
.Where(f => f.Abflugort == Ort || f.Zielort == Ort)
.OrderBy(f => f.Datum)
.Take(Anzahl)
.ToListAsync();

// Daten asynchron abrufen
var liste = await listeAsync;

// fallweise eine der beiden Views aufrufen
if (liste.Count == 0) return View("KeineGefunden", liste);
return View(liste);
}
}
}

Die View-Component-Controller-Klasse muss die Methode Invoke() und/oder InvokeAsync() implementieren. Die Controller-Klasse (siehe zweites Beispiel) bietet beides, sowohl den synchronen Aufruf mit Invoke() als auch den asynchronen mit InvokeAsync() an. Beide Methoden in obigen Listing liefern die gleichen Daten unter Verwendung von Microsofts objektrelationalem Mapper Entity Framework: die nächsten Flüge, die mindestens zwei Tage in der Zukunft liegen und noch nicht ausgebucht sind. Für die Praxis ist die asynchrone Implementierung bei allen Aktionen vorzuziehen, die externe Ressourcen wie Datenbanken verwenden, da hierbei während der Datenbankabfrage der Webserver-Thread freigegeben wird und für andere Aufgaben zur Verfügung steht.

Beide Methoden haben zwei benutzerdefinierte Parameter: eine Zeichenkette für den Ort und eine Anzahl. Der Ort dient als Filter für die Flugstrecke – die Anzahl steuert, wie viele Flüge maximal aus der Datenbank geholt werden. Beide Methoden speichern auch den Ort im dynamischen View Bag und geben somit der View die Möglichkeit, diese Zusatzinformation zu verwerten. Denkbare Rückgabetypen von Invoke() und InvokeAsync() sind neben den Instanzen der Klasse View Component einfache Zeichenketten (Klasse String).

Der zweite Teil der View Component ist die View. Dies ist eine .cshtml-Datei, die entweder im Verzeichnis /Views/Shared/Components/ oder aber in /Components unterhalb der View liegt, die die View Component aufruft. In beiden Fällen folgen danach ein Unterverzeichnis mit dem Namen der View Component und darin der Name der View. Als Standard ist "default" gesetzt. Somit ergibt sich für die View der Dateiname default.cshtml. Der Controller kann aber für jede einzelne Aktion im Rückgabetyp einen anderen Namen festlegen und somit situationsabhängig verschiedene Views aufrufen. So führt der Aufruf return View("KeineGefunden", liste) im obigen Listing dazu, dass ASP.NET statt default.cshtml eine Datei KeineGefunden.cshtml lädt, falls keine passenden Flüge gefunden wurden.

Der folgende Code zeigt eine typisierte View für eine View Component, die für eine Liste von Instanzen der Klasse Flug eine tabellarische Darstellung mit Twitter-Bootstrap-CSS-Klassen erzeugt. Auch die Information aus ViewBag.Ort wird für die Anzeige verwendet.

@model System.Collections.Generic.List<EF7_GO.Flug>
@using System.Globalization
<div class="row">
<div class="col-md-4">
<strong>Anzahl Flüge von oder nach @ViewBag.Ort: @Model.Count</strong>
</div>
</div>
<br />
@foreach (EF7_GO.Flug f in Model)
{
<div class="row">
<span class="col-xs-2">@f.FlugNr</span>
<span class="col-xs-4">@f.Abflugort</span>
<span class="col-xs-4">@f.Zielort</span>
<span class="col-xs-2">@f.Datum.ToString("d",
new CultureInfo("de-DE"))</span>
</div>
}

Auf dem Bildschirm steht die tatsächliche Anzahl der geladenen Flüge, die kleiner oder gleich der dem Controller übergebenen Maximalanzahl ist. Das nächste Beispiel zeigt die Alternativ-View KeineGefunden.cshtml für diese View Component; sie nutzt keine Datenbankinhalte, sondern zeigt nur statischen Text und den Inhalt von @ViewBag.Ort an.

<div class="row">
<div class="col-md-12">
<strong>Leider haben wir keine freien Flüge von oder nach @ViewBag.Ort.
</strong>
</div>
</div>

Wenn Entwickler die beiden Views der View Component unter /Views/Shared/Components/FlugTabelle/default.cshtml und /Views/Shared/Components/FlugTabelle/nichtgefunden.cshtml ablegen, können sie die View Component nun in allen Views verwenden. Der folgende Code zeigt die View /Views/Fluege/default.cshtml, die die View Component aus den ersten drei Codestücken direkt zweimal einbindet, für zwei verschiedene Flughäfen.

<html>
...
<class="jumbotron">
<h1>Buchbare Flüge</h1>
<h3>Beispiel für den Einsatz von ASP.NET MVC 6.0 View Components</h3>
<h6>(C) Dr. Holger Schwichtenberg 2015</h6>
</div>

<p class="bg-danger">Buchbare Flüge mit View Component "Flugtabelle",
synchron geladen</p>
@Component.Invoke("FlugTabelle", "Dortmund", 5)
<p></p>
<p class="bg-danger">Buchbare Flüge mit View Component "Flugtabelle",
asynchron geladen</p>
@await Component.InvokeAsync("FlugTabelle", "Essen/Mülheim", 5)
</div>
...
</html>

Die Ergebnisseite zeigt Abbildung 1.

Ausgabe der View Fluege/default.cshtml (Abb. 1)

Beim ersten Aufruf wird die synchrone Invoke()-Methode verwendet, beim zweiten die asynchrone. In beiden Fällen erfolgt der Aufruf über die Hilfsklasse @Component unter Angabe des Namens der View Component im ersten Parameter. Als weitere Parameter müssen Entwickler die von den Aktionen des View Component Controller erwarteten Parameter "Ort" und "Anzahl" mitgeben. Durch die Indirection über ]@Component ist es allerdings nicht möglich, dass man in einer View Component andere Methoden außer Invoke() und InvokeAsync() aufrufen kann.

View Components sind ein schönes Instrument, um Teile einer Webseite wiederverwendbar auszulagern. Dabei bleibt die von MVC gewohnte Trennung in View und Controller erhalten, sodass sich View Components gut mit Unit-Tests prüfen lassen. Anders als Tag Helper lassen sich View Components aber nicht komplett in externe Bibliotheken auslagern. Tag Helper kann man getrennt in eine DLL kompilieren und dann in binärer Form in mehrere Projekte einbinden. Zudem gibt es leider keine Eingabehilfe für die Aufrufe von View Components. Deren Name wird als Zeichenkette angegeben und Tippfehler fallen folglich erst zur Laufzeit auf.

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