Mutation aus Browser und Razor

Microsoft Blazor: Razor Syntax und C# im Browser
1 Kommentar

Blazor ist ein experimentelles Webframework von Microsoft, das Dank neuester Browsertechnologien auf einer .NET Runtime im Browser läuft und völlig auf JavaScript verzichtet. Doch was steckt eigentlich dahinter, wie funktioniert es, und hat das Experiment eine Zukunft?

Mit Blazor wählt Microsoft einen völlig neuen Ansatz, was die clientseitige Entwicklung angeht. Während Webframeworks sonst eigentlich immer direkt JavaScript nutzen oder eine verwandte Sprache während des Build-Prozesses in JavaScript transpilieren, verzichtet Blazor vollständig auf JavaScript. Stattdessen setzt es allein auf C# und die bewährte Razor-Syntax für die Entwicklung von HTML-Strukturen. Entwickelte Anwendungen werden dann mit einem normalen .NET-Compiler in Standard .NET-Bytecode kompiliert, der dann direkt im Browser ausgeführt wird.

Möglich ist das durch eine Version der Mono .NET Runtime, die mithilfe von WebAssembly im Browser ausgeführt wird. Dadurch läuft die Anwendung als vollwertige .NET-Anwendung im Browser. Somit können .NET-Entwickler den gesamten Stack mit den gleichen .NET-Technologien und Werkzeugen bedienen und sind nicht mehr nur auf das Backend beschränkt. Da mit Mono eine vollwertige .NET Runtime im Browser läuft, können Entwickler auch auf bewährte Lösungen und Pakete aus dem .NET-Ökosystem zurückgreifen.

Im Umkehrschluss bedeutet das allerdings, dass für die Ausführung einer .NET-Anwendung auch im Browser verschiedene Abhängigkeiten geladen werden müssen. So laden Blazor-Anwendungen beispielsweise auch Assemblies wie die mscorlib.dll, wodurch in Summe ein paar Megabytes heruntergeladen werden müssen, bevor die Anwendung laufen kann.

Da Blazor sich allerdings noch in einem sehr frühen Entwicklungsstatus befindet, ist damit zu rechnen, dass früher oder später auch etwas daran geändert wird. Das Blazor-Team erklärt selbst immer wieder, dass es mit der Situation aktuell nicht zufrieden ist, obwohl solche großen Assemblies durch geeignete Caching-Strategien und CDNs auf lange Sicht weniger ins Gewicht fallen würden.

WebAssembly als Basistechnologie

Blazor funktioniert dank der Tatsache, dass die .NET Runtime Mono im Browser ausgeführt werden kann. Möglich wird das durch WebAssembly, bei dem es sich um ein Bytecode-Format handelt, das direkt von Browsern ausgeführt werden kann. WebAssembly besitzt keine Garbage Collection, ist statisch getypt und nutzt ein lineares Speichermodell, wodurch Programme in WebAssembly generell schneller ausführbar sind als beispielsweise dynamisches JavaScript. Zudem ist es so gebaut, dass Browserhersteller die Ausführung weiter optimieren können und somit eine nativ-ähnliche Performance möglich ist. Dank Compilern wie Emscripten lassen sich viele Programme auch direkt in WebAssembly kompilieren.

So wurde auch die .NET Runtime Mono nach WebAssembly kompiliert und kann damit direkt in Browsern ausgeführt werden. Dadurch ist es möglich, .NET-Anwendungen nativ über die Mono Runtime im Browser auszuführen, ohne auf Plug-ins wie das damalige Silverlight zurückgreifen zu müssen. Mono, das als Runtime sowieso für die hohe Portabilität und die „Runs everywhere“-Mentalität bekannt ist und beispielsweise auch auf den mobilen Plattformen iOS und Android läuft, ist außerdem für Clientszenarien optimiert und damit für den Einsatz im Browser geeignet.

WebAssembly ist ein offener Standard und wird seit Herbst 2017 in allen modernen Browsern unterstützt. Selbst Browser, die WebAssembly nicht nativ unterstützen, wie zum Beispiel der Internet Explorer, können WebAssembly-Module über asm.js ausführen. Somit ist WebAssembly effektiv in allen Browsern ausführbar.

WebAssembly und asm.js

WebAssembly ist nicht das erste Projekt dieser Art: Den ersten Grundstein legte Mozilla 2013 mit asm.js, einem strikten Subset von JavaScript, das starke Browseroptimierungen (insbesondere für Ahead-of-Time-Compilation) ermöglichte und dadurch bereits große Performanceverbesserungen gegenüber normalem JavaScript erzielte. Als Subset von JavaScript ist es dabei aber auch ohne Optimierungen oder sonstige Anpassungen in allen Browsern ausführbar.

Als direkte Folge auf asm.js wurde Mitte 2015 dann WebAssembly als Bytecode-Format vorgestellt, das durch ein effizienteres Format das Parsing und die Codeausführung noch weiter beschleunigte. Mithilfe von asm.js ist es zudem möglich, WebAssembly-Module in ausführbares asm.js-JavaScript zu konvertieren. Somit ist WebAssembly in allen Browsern ausführbar, wobei die Performance natürlich durch fehlenden WebAssembly-Support oder asm.js-Optimierungen deutlich schlechter ist.

WebAssembly-Bytecode wird vom Browser in der JavaScript VM ausgeführt. Das hat den entscheidenden Vorteil gegenüber anderen Ansätzen, dass für die Implementierung von WebAssembly keine neue virtuelle Maschine notwendig ist. Für die Unterstützung reicht tatsächlich ein relativ kleiner Parser für das WebAssembly-Format wasm innerhalb der JavaScript Engine des Browsers. Da der Bytecode direkt auf der JavaScript VM ausgeführt wird und damit in der Sandbox des Browsers läuft, ist WebAssembly an dieselben Restriktionen gebunden wie JavaScript selbst. Daher ist es zwar möglich, quasi beliebigen Code an den Endnutzer zu liefern und auszuführen, praktisch ist das aber sicherheitsunkritisch, da WebAssembly nicht einfach mehr darf als JavaScript und damit genauso sicher ist.

Doch WebAssembly ist kein Ersatz für JavaScript. Stattdessen ist es als Komplement dazu konzipiert, um performancekritische Aufgaben zu übernehmen. WebAssembly-Module sind aus JavaScript aufrufbar und können ihrerseits auch definierte JavaScript-Funktionen aufrufen, wodurch eine Kommunikation in beide Richtungen möglich ist. Das ist auch notwendig, da WebAssembly selbst keinen direkten Zugriff auf das DOM des Browsers hat und entsprechend auf JavaScript für den Zugriff auf eine Webanwendung angewiesen ist.

Da solche Kontextwechsel auch Performanceeinbußen bedeuten, sind die typischen Anwendungsfälle für WebAssembly CPU-lastige Aufgaben, die über selbige deutlich schneller ausgeführt werden können. In Demos zu WebAssembly sieht man häufig Physik-Engines oder ganze Spiele-Engines, die oft sogar eine direkte Portierung einer vorhandenen Engine nach WebAssembly sind. Andere Beispiele sind Bild- oder Videobearbeitung, bei denen rechenlastige Transformationen durchgeführt werden, sowie auch Bibliotheken mit kryptografischen Funktionen.

Blazor, das als Webframework ständig mit dem DOM kommunizieren muss, geht dabei einen ganz anderen Weg und zeigt, dass auch dieser Anwendungsfall für WebAssembly durchaus seine Berechtigung hat.

Erste Schritte mit Blazor

Trotz des stark experimentellen Charakters und des frühen Entwicklungsstadiums gelingen die ersten Schritte mit Blazor erstaunlich gut. Blazor ist vollständig in das Tooling von ASP.NET Core integriert und erfordert lediglich eine aktuelle .NET-Core-SDK-Version, ein aktuelles Visual Studio sowie die „Blazor Language Services“-Erweiterung, die das Tooling in Visual Studio verfügbar macht. Alternativ kann man auch über die dotnet-Kommandozeile ein Vorlagenpaket installieren, wodurch Blazor-Projekte auch über dotnet new angelegt werden können.

Beim Erzeugen eines neuen Projekts stehen zwei Vorlagen zur Auswahl: „Blazor“ und „Blazor (ASP.NET Core hosted)” (Abb. 1). Letzteres ist eine Kombination aus einer Blazor-Anwendung und einem ASP.NET Core Web API und eignet sich sehr gut, um zu sehen, wie Blazor eine Full-Stack-Lösung für die Webentwicklung bieten kann. Zudem enthält die Vorlage ein Library-Projekt, das sowohl von dem Server- als auch von dem Clientprojekt referenziert wird. Daran lässt sich exemplarisch zeigen, wie der gleiche .NET-Code vom Server und auch vom Client benutzt werden kann. Das ist insbesondere für Datentransferobjekte spannend, da für die Clientseite nicht noch extra Typdefinitionen angelegt oder generiert werden müssen, sondern dieselben Definitionen des Servers wiederverwendet werden können.

Abb. 1: Beim Anlegen eines ASP.NET-Core-Webprojekts sind zwei Blazor-Vorlagen auswählbar

Abb. 1: Beim Anlegen eines ASP.NET-Core-Webprojekts sind zwei Blazor-Vorlagen auswählbar

Nach der Auswahl der Vorlage und dem Erstellen des Blazor-Projekts kann die Anwendung sofort gestartet werden. Die Vorlage bietet bereits einige Funktionen, die clientseitig ausgeführt werden. Angelehnt an die Angular-Vorlage von ASP.NET Core enthält sie einen Klickzähler sowie exemplarisch eine Liste, die Daten über einen HTTP Request nachlädt.

Schaut man sich das Projekt etwas genauer an, zeigt sich, wie Blazor-Anwendungen aufgebaut sind: Den größten Anteil des Projekts nehmen csproj-Dateien ein (Abb. 2). Dabei handelt es sich um Blazor-Komponenten, die mithilfe von Razor-Syntax erstellt werden. Listing 1 zeigt exemplarisch die Counter-Komponente.

Abb. 2: Startseite der Beispielanwendung

Abb. 2: Startseite der Beispielanwendung

@page "/counter"

<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>

@functions {
  int currentCount = 0;

  void IncrementCount()
  {
    currentCount++;
  }
}

Blazor-Komponenten bestehen üblicherweise aus zwei Teilen: Zum einen aus HTML-Code, der für die Struktur und Darstellung der Komponente verantwortlich ist, und zum anderen aus einem @functions-Block für die Interaktionslogik. Der @functions-Block wird bereits für ASP.NET Core Razor Pages verwendet und erlaubt generell das Definieren von Methoden und Eigenschaften, die mit der Razor View verknüpft werden. In Blazor sind das die Members der Klasse, die für die Blazor-Komponente beim Kompilieren erzeugt wird. Innerhalb des Razor-Templates können diese Members dann direkt referenziert werden. Beispielsweise greift die Counter-Komponente über @currentCount auf den aktuellen Zählerstand zu, und onclick=“@IncrementCount“ auf dem Button-Element registriert die Methode als Event Handler für das Click Event des Buttons.

Am Anfang des Templates befindet sich außerdem noch eine @page-Direktive. Dadurch wird die Komponente für den Router sichtbar und ist unter der angegebenen Route verfügbar. Komponenten müssen nicht über eine Route angesteuert werden, sondern können auch direkt in anderen Komponenten benutzt werden. Dazu sind sämtliche Blazor-Komponenten automatisch als Tag verwendbar. Das sieht man beispielsweise in der Index-Komponente, die wiederum die SurveyPrompt-Komponente nutzt:

@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

Über den SurveyPrompt-Tag wird die Komponente aus der SurveyPrompt.cshtml an der gewünschten Stelle instanziiert. Durch Angabe von HTML-Attributen können alle Eigenschaften der Komponente gesetzt werden, die mit dem [Parameter]-Attribut markiert wurden. Somit ist es möglich, konfigurierbare Komponenten zu erstellen.

Da jede cshtml-Datei eine Blazor-Komponente ist und alle Blazor-Komponenten über Tags benutzbar sind, lässt sich die Index-Komponente auch einfach um die Counter-Komponente erweitern. Das bloße Hinzufügen eines Counter-Tags <Counter /> reicht, um der Indexseite einen Zähler hinzuzufügen.

Eine weitere Blazor-Komponente

Ein typischer Anwendungsfall bei Webanwendungen ist die Eingabe von Daten. Wie viele andere Frameworks nutzt Blazor dafür Data Binding, eine Technik, bei der eine Datenquelle mit einer Komponente verknüpft wird und Änderungen an den Daten automatisch synchronisiert werden. Blazor nutzt dazu ein spezielles bind-Attribut auf Elementen. Beispielsweise würde der folgende Code ein input-Element erzeugen und den Inhalt an eine Eigenschaft TextValue der Komponente binden. Änderungen an dem Wert der Eigenschaft oder auch Änderungen des Textfelds durch Benutzereingaben würden dann automatisch jeweils den anderen Binding-Partner aktualisieren:

<input type="text" bind="@TextValue" />

Data Binding kann nun benutzt werden, um ein etwas funktionaleres Komponentenbeispiel zu implementieren. Wie üblich bei der Vorstellung eines Clientframeworks soll hier eine To-do-Liste implementiert werden. Dazu wird eine neue Komponente durch Anlegen einer Datei TodoList.cshtml erstellt. Wie bei den Komponenten zuvor braucht auch diese einen @functions-Block, in dem der C#-Code hinterlegt werden kann. Dort wird zunächst eine TodoItem-Klasse definiert, die zwei Eigenschaften Text und IsDone besitzt. Objekte dieses Typs repräsentieren ein Element der To-do-Liste. Die IsDone-Eigenschaft sagt aus, ob das To-do-Element bereits abgeschlossen ist oder nicht. Um die To-do-Elemente zu speichern, wird außerdem eine lokale Liste items benötigt. Diese kann dann über eine entsprechende @foreach-Schleife in der To-do-List-Komponente angezeigt werden:

<ul>
    @foreach (var item in items)
    {
        <li&gt,@item.Text</li>
    }
</ul>

Damit es außerdem möglich ist, neue To-do-Elemente hinzuzufügen, bedarf es eines kleinen Formulars. In diesem Fall reichen ein Textfeld und ein Button zum Hinzufügen der eingegebenen Aufgabe:

<input type="text" bind="newText" />
<button class="btn btn-primary" onclick="@AddItem">Add</button>

Das Textfeld benutzt dabei Data Binding, um den eingegebenen Text in einem Feld der Komponente zu speichern. Das Click Event des Buttons ist wie zuvor der bei der Counter-Komponente an eine Methode gebunden. Auf der C#-Seite im @functions-Block müssen nun genau dieses Feld und die Methode definiert sein:

string newText;
void AddItem()
{
  items.Add(new TodoItem { Text = newText });
  newText = null;
}

Als weitere Anforderung sollen To-do-Elemente als erledigt markiert werden können. Dazu kann man einfach eine Checkbox benutzen, die per Data Binding mit der IsDone-Eigenschaft des aktuellen Eintrags verknüpft ist. Somit zeigt die Checkbox immer den aktuellen Zustand des Eintrags an, und beim Klicken auf die Checkbox wird dieser dann automatisch aktualisiert.

Da Blazor vollwertiges .NET unterstützt, können Razor-Templates auch auf komplexere Dinge zurückgreifen. Beispielsweise könnte man mit LINQ ermitteln, wie viele To-do-Items aktuell noch nicht erledigt sind, und die Nummer anzeigen. Das ist einfach durch den LINQ-Ausdruck @items.Count(x => !x.IsDone) möglich.

Listing 2 zeigt den finalen Code der To-do-List-Blazor-Komponente. Die Komponente konfiguriert außerdem die Route /todo-list und kann somit auch direkt im Browser aufgerufen werden. Schließlich kann man noch die NavMenu.cshtml anpassen, sodass die Navigation einen Link zur To-do-Liste enthält. Das Ergebnis sollte dann aussehen wie in Abbildung 3.

@page "/todo-list"

<h1>Todo list (@items.Count(x => !x.IsDone))</h1>
<ul>
  @foreach (var item in items)
  {
    <li><label><input type="checkbox" bind="item.IsDone" /> @item.Text</label></li>
  }
</ul>

<input type="text" bind="newText" />
<button class="btn btn-primary" onclick="@AddItem">Add</button>

@functions {
  string newText;
  List<TodoItem> items = new List<TodoItem>();

  void AddItem()
  {
    items.Add(new TodoItem { Text = newText });
    newText = null;
  }

  class TodoItem
  {
    public string Text { get; set; }
    public bool IsDone { get; set; }
  }
}
Abb. 3: Die finale To-do-Liste in der Blazor-Anwendung

Abb. 3: Die finale To-do-Liste in der Blazor-Anwendung

Fazit

Blazor ist allein schon technologisch ein sehr interessantes Projekt. Es gibt viele Experimente rund um WebAssembly, allerdings ist Blazor von der Art her etwas Besonderes. Während andere Experimente üblicherweise die Performancevorteile von WebAssembly nutzen, wird mit Blazor eine Brücke zu einer serverseitigen Technologie geschlagen und gleichzeitig fast vollständig auf JavaScript verzichtet.

Damit ist Blazor ein Webframework, das auf die üblichen Webtechnologien verzichtet. Das macht es sicherlich interessant für Entwickler, die wenig Kontakt mit JavaScript haben oder sich generell mehr im .NET-Ökosystem zuhause fühlen. Gleichzeitig isoliert es Blazor aber auch von dem restlichen Ökosystem für clientseitige Webentwicklung. Werkzeuge, Frameworks, Libraries und selbst Paradigmen, die sonst clientseitig Anwendung finden, sind nicht direkt auf Blazor übertragbar. Stattdessen muss sich um Blazor selbst ein neues Ökosystem bilden.

Seit der initialen Ankündigung passiert genau das. Es gibt viele Interessenten in der Community, die die Entwicklung von Blazor unterstützen und eigene Ideen beisteuern. Selbst die Blazor-Dokumentation ist anfangs durch eine Initiative der Community gestartet. Darüber hinaus gibt es auch schon viele weitere Ressourcen zum Thema Blazor. In dieser Hinsicht ist es schon sehr beeindruckend, dass solch ein experimentelles Framework so schnell so viel Unterstützung bekommt.

Dennoch darf man nicht vergessen, dass Blazor weiterhin offiziell als Experiment gehandelt wird und es keine klare Zukunft seitens Microsoft gibt. Ob es einmal ein finales und stabiles Release geben wird, das dann auch den üblichen Microsoft-Support bekommt, ist derzeit nicht bekannt. Microsoft nutzt diese Phase aktuell, um wirklich herumzuexperimentieren und verschiedenste Ansätze auszuprobieren. Somit ist Blazor aktuell absolut nicht für produktive Zwecke geeignet.

Wer allerdings mal mit einem etwas anderen WebAssembly-Projekt experimentieren möchte oder wer vielleicht auch einfach mal eine Pause von JavaScript braucht, dem ist zu empfehlen, Blazor einmal auszuprobieren. Durch das gute Tooling sind sinnvolle Ergebnisse sehr schnell erreicht und kleinere Anwendungen lassen sich so leicht zusammenbauen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

1 Kommentar auf "Microsoft Blazor: Razor Syntax und C# im Browser"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Lothy
Gast

Leider nur ein redundanter Artikel, den Rainer Stropek vor Wochen in ähnlicher Form gebracht hat.
Es wäre interessant zu wissen, warum Microsoft ein so vielversprechendes Projekt so schleifen lässt.
Schade…
Gruß
Lothy

X
- Gib Deinen Standort ein -
- or -