WebAssembly-Programmierung mit ASP.NET Blazor

Seite 2: Erste Schritte mit ASP.NET Blazor

Inhaltsverzeichnis

Aktuell ist die Version 0.3 von Blazor, die am 2. Mai 2018 erschienen ist. Für ASP.NET Blazor benötigt man derzeit (dies kann und wird sich aber ändern):

Ein neues Blazor-Projekt legt der Entwickler dann in Visual Studio über New Project/C#/.NET Core Web Application an (Abbildung 2). Hier kann man zwischen einem reinen Client-Projekt (Vorlage "Blazor") oder einem Client-Projekt mit Web-API-Server-Projekt wählen, das ASP.NET Core verwendet, sowie einer zwischen Client und Server gemeinsamen DLL (Shared Library) auf Basis von .NET Standard 2.0. Die Eingruppierung in die Rubrik .NET Core ist eigentlich falsch, denn ASP.NET Blazor basiert nicht auf .NET Core, sondern, wie eingangs erwähnt, auf Mono. Microsoft arbeitet freilich daran, .NET Core und Mono zu vereinen. Die Auswahl eines Authentifizierungsmechanismus oder das Hosting in einem Docker-Container ist für Blazor-Projekte bisher nicht verfügbar.

Abbildung 2: Anlegen eines Blazor-Projekts in Visual Studio 2017 Update 7

Alternativ kann man die .NET Core-Kommandozeile verwenden. Zunächst installiert man die Projektvorlagen:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates

Dann legt man ein neues Projekt an und startet es:

dotnet new blazor -o BlazorWeb
cd BlazorApp1
dotnet run

Hier soll zunächst das Blazor-Projekt beschrieben werden, dass durch die Visual Studio-Vorlage "Blazor (ASP.NET Core hosted)" angelegt wird. Abbildung 3 zeigt die entstandene Projektstruktur für ein Blazor-Projekt mit Client und Server inklusive Shared Code Library. Während der Server ein .NET Core-Projekt ist, basieren Client und Shared Code Library auf .NET Standard 2.0; damit laufen sie sowohl auf .NET Core als auch auf Mono. Die .NET Standard 2.0-Projekte können .NET-Assemblies referenzieren, die kompatibel zu .NET Standard 2.0 sind. .NET-Bibliotheken, die nicht im Rahmen der Sandbox laufen können (zum Beispiel Datenzugriffskomponenten wie ADO.NET und Entity Framework Core) sind jedoch nicht im Browser lauffähig.

Abbildung 3: Entstandene Projektstruktur fĂĽr ein Blazor-Projekt mit Client und Server

Die Seite wwwroot/index.html startet den Client. Dort findet man den Tag <script type="blazor-boot"></script>, der aber beim Kompilieren durch folgendes ersetzt wird:

<script src="_framework/blazor.js" 
main="MWABuchBlazor.Client.dll"
entrypoint="MWABuchBlazor.Client.Program::Main"
references="Microsoft.AspNetCore.Blazor.Browser.dll,Microsoft.AspNetCore.Blazor.dll,Microsoft.Extensions.DependencyInjection.Abstractions.dll,Microsoft.Extensions.DependencyInjection.dll,mscorlib.dll,MWABuchBlazor.Shared.dll,netstandard.dll,System.Core.dll,System.dll,System.Net.Http.dll" linker-enabled="true"></script>

Dies führt dazu, dass der Browser die genannten JavaScript- und DLL-Dateien vom Webserver nachlädt (Abbildung 4). In Organisationen, wo das Herunterladen von DLLs per Firewall unterbunden wird, können Blazor-Webanwendungen aktuell nicht laufen. Microsoft ist sich dessen aber bewusst und will dazu später eine Lösung liefern.

Abbildung 4: Beim Start einer Blazor-Anwendung in den Browser geladene Dateien

Die zu Beginn darzustellenden Inhalte bestimmt die Datei /Pages/Index.html, die, wie man es von ASP.NET Core kennt, per _ViewImports.cshtml den Verweis auf eine Layoutseite (Masterpage) in /Shared/MainLayout.chtml erhält. MainLayout.cshtml bildet das Grundlayout der Anwendung und bindet das Menü (linke Seite in Abbildung 5) ein, das in der Datei /Shared/NavMenu.cshtml realisiert ist. Dort sieht man spezielle <NavLink>-Tags, die Blazor in <a>-Tags umwandelt. Alternativ kann der Entwickler das <a>-Tag auch direkt verwenden.

Die Vorlage erstellt eine Single-Page-Application mit drei Ansichten:

  1. Home (/Pages/Index.cshtml) ist eine statische Startseite.
  2. Counter (/Pages/Counter.cshtml) realisiert einen einfachen Zähler, der im Browser bei jedem Klick auf eine Schaltfläche erhöht wird.
  3. Fetch Data (/Pages/FetchData.chtml, Abbildung 4 holt Daten von dem Web-API im Server-Projekt. Die Datumsausgaben sind derzeit immer amerikanisch, auch wenn der Browser auf Deutsch eingestellt ist.

Eine Blazor-Webseite wird also aus MainLayout.chtml, NavMenu.cshtml sowie einer der Ansichten zusammengestellt. Analog zu Googles SPA-Framework Angular nennt auch Microsoft diese einzelnen Seitenbestandteile Komponenten.

Abbildung 5: Das Web-API liefert zufällig erzeugte Wetterdaten. Hier im Chrome-Browser gezeigt.

Listing 1 zeigt den Tag- und Programmcode aus FetchData.cshtml. Hierbei kommt HTML in Verbindung mit der ASP.NET Razor-Syntax zum Einsatz. C#-Ausdrücke und -Befehle sowie spezielle Razor-Direktiven werden dabei immer mit einem Klammeraffen @ eingeleitet. Innerhalb der Razor-Ausdrücke können wieder HTML-Tags eingebettet sein. Visual Studio stellt IntelliSense-Eingabeunterstützung überall in der Vorlage sowohl für HTML und CSS als auch C# bereit. In den von der Projektvorlage generierten Dateien liegt der C#-Programmcode komplett in der .cshtml-Datei; dieser Artikel zeigt später, dass auch eine Trennung von Vorlage und Logik möglich ist.

In der asynchronen Seitenlebenszyklusmethode OnInitAsync() ruft der Client die auf ASP.NET Core-basierende REST-API auf, die man im Serverprojekt im /Controllers/SampleDataController.cs findet. Für den HTTP-Aufruf verwendet der Client die in .NET übliche Klasse Systen(!!!System?).Net.Http.HttpClient. Eine Instanz dieser Klasse erhält die Seite über das in Blazor eingebaute Dependency-Injection-Framework und die Direktive @inject zu Beginn der Datei (siehe dritte Zeile in Listing 1). Anders als im serverseitigen ASP.NET verwendet Microsoft für die JSON-Serialisierung und -Deserialisierung hier aktuell SimpleJSON statt Newtonsoft JSON.NET, da es damit noch Probleme gibt.

Listing 1: FetchData.chtml

@using BlazorWeb.Shared
@page "/fetchdata"
@inject HttpClient Http

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}

@functions {
WeatherForecast[] forecasts;

protected override async Task OnInitAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("/api/SampleData/WeatherForecasts");
}
}