Februar 2019

Band 34, Nummer 2

[Cutting Edge]

Umgang mit Formularen in Blazor

Von Dino Esposito | Februar 2019

Dino EspositoASP.NET Core Blazor ist ein C#-basiertes, clientseitiges Framework zum Erstellen von Single-Page-Anwendungen (SPAs). In dieser Hinsicht unterscheidet sich das Framework nicht wesentlich von Angular, da es mit jedem Back-End interagieren kann, das Sie verwenden, das über HTTP erreichbare Endpunkte bereitstellt. Zu diesem Zeitpunkt wird an Blazor jedoch noch gearbeitet, und das Framework ist noch lange nicht für die Produktion bereit.

Allerdings werden einige Teile von Blazor als Bestandteil von ASP.NET Core 3.0 ausgeliefert. Razor Components ist eine spezielle Konfiguration einer Blazor-Anwendung, die vollständig auf dem Server ausgeführt wird, parallel zu einer klassischen ASP.NET Core-Anwendung, die ihr Back-End bilden könnte. Die Koexistenz einer klassischen Webanwendung, die hauptsächlich für Back-End-Aufgaben verwendet wird, und einer eigenständigen Blazor-basierten Präsentationsschicht auf dem ASP.NET-Server vereinfacht einige Programmierszenarien und schafft eine Art Mittelweg zwischen einem reinen SPA-Ansatz und einem reinen Serveransatz. In diesem Artikel werde ich den Umgang mit Eingabeformularen und die Kommunikation zwischen Client und Server näher erläutern. Beginnen wir mit einem kurzen Überblick über Razor Components.

Serverseitiges Blazor

Blazor ist ein Kernframework, das so konzipiert wurde, dass Ereignisse unabhängig von der Umgebung empfangen und verarbeitet werden können. Zurzeit sind die einzigen vollständig unterstützten Szenarien die Ausführung von Blazor im UI-Thread eines Hostingbrowsers und in der ASP.NET Core-Runtime. Andere realistische Szenarien, die bald folgen werden, sind die Ausführung von Blazor in einem Webworker oder sogar in einigen desktopfähigen Plattformen wie Electron. Das Hosting in einem Webworker würde Blazor zu einer geeigneten Plattform für progressive und Offlinewebanwendungen machen, während das Hosting in einer Plattform wie Electron ebenfalls sinnvoll ist. Vergleichen wir als Beispiel kurz die Anweisungen für den sehr einfachen Start von Electron (siehe https://github.com/electron/electron-quick-start) mit dem, was geschieht, wenn ein neues serverseitiges Blazor-Projekt ausgeführt wird.

Der Schnellstartquellcode von Electron enthält die folgende Methode:

function createWindow () { 
  mainWindow = new BrowserWindow({width: 
    800, height: 600}) 
  mainWindow.loadFile(‘index.html’)
}

Die Methode ist für das Erstellen des Browserfensters und das Laden einer separaten HTML-Datei in dieses verantwortlich. Alles, was anschließend geschieht, ist ein kontinuierlicher Austausch von Nachrichten zwischen der Außenwelt (dem Browser) und der innersten Shell (den HTML-Seiten). Das dazwischen befindliche Framework ist nur für die angemessene Verwaltung der Last von Proxyvorgängen für Nachrichten verantwortlich. Sehen wir uns an, was passiert, wenn eine serverseitige Blazor-Anwendung ausgeführt wird, wie in Abbildung 1 dargestellt.

Die Ladephase einer serverseitigen Blazor-Anwendung
Abbildung 1: Die Ladephase einer serverseitigen Blazor-Anwendung

Zunächst lädt der Browser die Datei „index.html“ auf Anwendungsebene, die das Layout der Hauptseite definiert (zum größten Teil das HEAD-Tag), und als Teil des Downloads auch eine kleine JavaScript-Datei namens „blazor.server.js“. Der Schritt _blazor/negotiate in Abbildung 1 zeigt den Zeitpunkt an, zu dem eine ASP.NET Core SignalR-Verbindung zwischen der Hostumgebung des Browsers und der Blazor-App hergestellt wird. Schließlich wird die Verbindung auf Websockets aktualisiert. Dies geschieht im letzten Schritt, den der Fiddler-Bildschirm in Abbildung1 zeigt.

In beiden Fällen lädt die Hostumgebung eine Initialisiererdatei. Sobald die Verbindung hergestellt wurde, wird die Anwendungslogik ausgeführt und generiert alle erforderlichen HTML-Elemente. Im serverseitigem Blazor wird das serverseitig generierte HTML mit dem aktuell gerenderten DOM verglichen, und nur die notwendigen Fragmente des DOMs werden tatsächlich aktualisiert. Alle Updates erfolgen über die SignalR-Verbindung.

Die im Browser erfassten Benutzerklicks verlaufen in umgekehrter Richtung. Verwandte Ereignisse werden in Paketen zusammengefasst und an die Serverseite gesendet, wo sie von der Blazor JavaScript-Interopschicht und in C#-Code verarbeitet werden.

Im Lieferumfang von Visual Studio ist eine Ad-hoc-Projektvorlage für serverseitige Blazor-Anwendungen enthalten. Das typische Projekt enthält ein Clientprojekt und ein ASP.NET Core-Projekt. Wahrscheinlich möchten Sie ein .NET Standard-Bibliotheksprojekt hinzufügen, um die Klassen aufzunehmen, die vom Front-End und Back-End gemeinsam verwendet werden müssen.

Einrichten der Client-App

Die Startklasse der Clientanwendung ist fast leer:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
  }

  public void Configure(IBlazorApplicationBuilder app)
  {
    app.AddComponent<App>(“app”);
  }
}

Die einzige erforderliche Änderung besteht darin, einen Verweis auf HttpClient einzufügen, sodass jede der Blazor-Seiten in der Lage ist, HTTP-Remoteaufrufe vorzunehmen:

public void ConfigureServices(IServiceCollection services)
{
  services.AddScoped<HttpClient>();
}

Die wichtigste Ergänzung der Clientanwendung ist eine Komponente, die es Benutzern ermöglicht, ein Formular auszufüllen und zu senden. In Abbildung2 sehen Sie die Anwendungsbenutzeroberfläche, die durch die Schaltfläche „Register User“ (Benutzer registrieren) generiert wurde. Es handelt sich um ein einfaches HTML-Formular, das eine E-Mail-Adresse und ein Kennwort vom Benutzer erfasst und an ein Remote-Back-End zurückgibt.

Das Beispielformular
Abbildung 2: Das Beispielformular

Interessant ist hier das Markup, das Sie benötigen, um den Container für die Eingabefelder in Abbildung 2 zu generieren. Obwohl sich der Container wie ein HTML-Formular verhält, handelt es sich nicht um ein echtes HTML-Formular, wie in Abbildung 3 dargestellt.

Abbildung 3: Markup zum Generieren eines (Nicht-HTML)-Formulars

<div class=”col-sm-6 col-md-4 offset-md-4”>
  <h3 class=”text-center login-title”>I Want To Be a New User</h3>
  <div class=”account-wall”>
    <div class=”form-signin”>
      <input type=”text” 
                   class=”form-control” 
                   name=”email” bind=”@Email” />
      <input type=”password” 
                   class=”form-control” 
                   name=”password” bind=”@Password” />
      <button class=”btn btn-primary 
                     onclick=”@TryRegister”>
        Sign me in
      </button>
    </div>
    <div class=”text-center”>@Message</div>
  </div>
</div>

Wie Sie sehen können, ist das Markup reines HTML, aber es gibt keine Notwendigkeit für ein FORM-Element. Am Ende übernimmt der auf dem Client ausgeführte C#-Code das Erfassen der Eingabewerte und leitet sie an einen Remoteendpunkt weiter, der ein ASP.NET-Back-End sein kann (aber nicht muss).

Das bind-Attribut, das den INPUT-Elementen zugeordnet ist, garantiert eine bidirektionale Bindung zwischen dem Pseudoformular und den Eigenschaften der Blazor-Clientseite. Die TryRegister-Funktion ist für das Bereitstellen des Inhalts für das Back-End verantwortlich. Abbildung 4 zeigt den Code des @functions-Blocks der Clientseite.

Abbildung 4: Der @functions-Block

@inject HttpClient Http...
@functions
{
  public string Email { get; set; }
  public string Password { get; set; }
  public string Message = “?”;

  public async Task TryRegister()
  {
    var input = new RegisterUserViewModel(Email, Password);
    try
    {
      var response = await Http.PostJsonAsync<CommandResponse>(
        “.../account/register”, input);
      if (response.Success)
        Message = response.Message;
    }
    catch (Exception e)
    {
      Console.WriteLine(e);
      throw;
    }
  }
}

Der Inhalt der Eingabefelder wird in einer neuen Instanz eines Datenübertragungsobjekts (RegisterUserViewModel) gesammelt und als JSON über die PostJsonAsync-Methode von HttpClient serialisiert. Das Back-End empfängt eine HTTP POST-Anforderung, in der der Anforderungstext wie folgt festgelegt ist:

{“email”:”...”,”password”:”...”}

In einer serverseitigen Blazor-Anwendung ist das Back-End in der Regel eine ASP.NET Core-Anwendung. In diesem Fall wird die Anforderung an einen Controllerendpunkt weitergeleitet, an dem Modellbindung gilt. Infolgedessen werden die geposteten Daten von einer serverseitigen Instanz der gleichen RegisterUserViewModel-Klasse erfasst. Beachten Sie jedoch, dass das Back-End nicht unbedingt eine ASP.NET Core-Anwendung sein muss. Die URL, die Sie im Aufruf über HttpClient angeben, muss eine absolute URL sein, die Daten an praktisch jedes Ziel senden kann, z.B. auch an ein auf Visual Basic basierendes Legacy-Back-End.

Einrichten der Server-App

In der Beispielanwendung ist die Serveranwendung eine einfache ASP.NET Core Web-API, die so geschützt ist, wie Sie jede andere Web-API sichern würden. So kann sie beispielsweise vor unbefugtem Zugriff über ein JWT-Token geschützt werden. Dies funktioniert auch mit Nicht-Webclients (einschließlich Desktop- und mobilen Clients) gut. Nehmen wir zunächst einmal an, dass die Web-API anonymen Zugriff erlaubt. Dies ist der Code, den Sie benötigen, um alle von Blazor bereitgestellten Daten erfolgreich zu empfangen und zu verarbeiten:

public class AccountController : Controller
{
  [HttpPost]
  public IActionResult Register(
    [FromBody] RegisterUserViewModel input)
  {
    // Some work here      ...
    return Json(CommandResponse.Ok.AddMessage(“Done”));
  }
}

Das FromBody-Attribut spielt hier eine wichtige Rolle. Ohne dieses Attribut würde jeder Aufruf des Endpunkts mit dieser Implementierung des Blazor-Clients eine Modellbindungsausnahme auslösen.

Das FromBody-Attribut weist die ASP.NET Core-Runtime an, den Parameter an die Daten zu binden, die im Anforderungstext der eingehenden HTTP-Anforderung enthalten sind. Beachten Sie, dass es maximal einen Parameter pro Aktion geben kann, der mit dem FromBody-Attribut versehen ist.

ASP.NET Core überträgt die Verantwortung für die Verarbeitung des Anforderungsdatenstroms an eine spezielle Formatierungsklasse. Die Standardklasse ist JsonInputFormatter. Beachten Sie auch, dass der Anforderungsdatenstrom nicht mehr für einen anderen Parameter gelesen werden kann, sobald er für einen Parameter gelesen wurde. Der Datenstromzeiger ist tatsächlich am Ende des Anforderungstexts angelangt und kann nicht zurückbewegt werden.

Die Antwort der Web-API ist eine weitere JSON-Antwort, die der Blazor-Client empfängt und deserialisiert:

var response = await 
  Http.PostJsonAsync<CommandResponse>(absoluteUrl, input);

In diesem Codeausschnitt wird die Antwort in eine Instanz vom Typ CommandResponse deserialisiert, und es wird eine Ausnahme ausgelöst, wenn sich dies als unmöglich erweist.

Eine Anmerkung zur Sicherheit und Authentifizierung

Die Clientkomponente von Blazor wird in einer Sandbox auf die gleiche Weise wie JavaScript-Code ausgeführt. Das Server-Back-End ist vollständig vom Client getrennt und nur unter den vom Back-End definierten Bedingungen erreichbar. Die Blazor-Client-App muss der Sicherheitsebene genügen, die für das Back-End gilt. In dieser Hinsicht ist Blazor ein einfacher webbasierter Client, der eine Web-API aufruft. Die Authentifizierung kann über ein Cookie erfolgen. Wahrscheinlicher ist jedoch die Authentifizierung über ein JWT-Bearertoken.

Das serverseitige Blazor-Szenario kann sich anders verhalten und alternative Szenarien für Authentifizierung und Autorisierung ermöglichen. Zu diesem Aspekt wurde noch keine Entscheidung getroffen, aber Sie können sich über die Debatte unter bit.ly/2CdS74c informieren. Wahrscheinlich wird eine integrierte API bereitgestellt, um die Erstellung von Anmelde- und Abmeldeformularen für serverseitige Blazor-Anwendungen sowie für andere gängige Authentifizierungsvorgänge wie die Wiederherstellung eines Kennworts und die Interaktion mit dem internen Mitgliedersystem zu optimieren. Es ist möglich, dass Blazor in irgendeiner Weise die integrierte Benutzeroberfläche von ASP.NET Core Identity einbindet. Aber auch hier wurde zurzeit noch nichts entschieden.

Vor- und Nachteile von Razor Components

Serverseitiges Blazor (oder Razor Components) wird der erste Teil von Blazor sein, der den Go Live-Status erreicht, wenn es als Teil von ASP.NET Core 3.0 ausgeliefert wird. Einige Entwickler schrecken möglicherweise vor den Nachteilen zurück. Wenn Sie Blazor zum Einrichten einer reinen SPA-Lösung verwenden, mag es zu weit von der Programmiererfahrung klassischer ASP.NET-Webentwickler entfernt erscheinen. Außerdem ist WebAssembly erforderlich, um C# im Browser auszuführen, was Kosten für den Download und den Erststart verursacht, während das Debuggen problematisch sein kann. Mit einer serverseitigen Lösung ist die Entwicklungserfahrung reibungsloser und bietet verbessertes Debuggen, JIT-Kompilierung und eine größere API. Darüber hinaus werden alle Browser unterstützt, und zwar unabhängig von ihrer nativen Unterstützung für WebAssembly. Für einen klassischen ASP.NET-Entwickler wird das Aktualisieren nur von Teilen der Benutzeroberfläche fast so einfach wie in einer Desktopanwendung.

Das Problem besteht darin, dass all diese Magie auf einer SignalR-Verbindung beruht. Dies führt zu einigen potenziellen Problemen. Erstens wird es eine ganze Reihe von Anforderungen geben, die über das Netz erfolgen. In der Tat erfordern alle Updates einen Roundtrip. Zweitens ist der Server gezwungen, jeden Client und seinen Status nachzuverfolgen. ASP.NET Core SignalR wird auch als Azure-Dienst angeboten (und dies bietet eine hervorragende Grundlage für Skalierbarkeit), aber noch niemand hat diese Lösung in der realen Welt ausprobiert. Schließlich gibt es noch keinen integrierten Mechanismus, um den Anwendungszustand wiederherzustellen, falls die SignalR-Verbindung abbricht. Jede dauerhafte Lösung ist gut, aber das bleibt vorerst alles den Entwicklern überlassen.

Alles in allem fördert Blazor das SPA-Muster, und das SPA-Muster wird von vielen Entwicklern als störend empfunden. Serverseitiges Blazor (auch als Razor Components bezeichnet) ist eine intelligente, unterbrechungsfreie Möglichkeit, sich schrittweise in Richtung SPA-Architektur zu bewegen. Der Quellcode für diesen Artikel basiert auf Blazor 0.7.0 und ist unter bit.ly/2EpGF8d verfügbar.


Dino Esposito hat in seiner 25-jährigen Karriere über 20 Bücher und mehr als 1.000 Artikel verfasst. Als Autor von „The Sabbatical Break“, einer theatralisch angehauchten Show, schreibt Esposito Software für eine grünere Welt als digitaler Stratege bei BaxEnergy. Folgen Sie ihm auf Twitter: @despos.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Jonathan Miller


Diesen Artikel im MSDN Magazine-Forum diskutieren