Der lise Blog:
Einsichten, Ansichten, Aussichten

Tschüss JavaScript, hallo Blazor: Single Page Applications mit Blazor

In diesem Artikel werden wir eine Single Page Application (SPA) mit dem Blazor-Framework implementieren. Die Datenbank erzeugen wir im Database First Approach mit einem SQL-Skript und werden mit Hilfe von Dapper die Datenbankkommunikation ermöglichen.

Einführung

Single Page Applications (SPAs) sind Web-Applikationen, die eine einzelne HTML-Seite laden und dynamisch diese Seite aktualisieren, wenn der Benutzer mit dieser interagiert.

Blazor ist ein neues, noch experimentelles SPA-Framework, um interaktive clientseitige Webapplikationen mit .Net zu implementieren. Hierfür setzt Blazor auf offene Web-Standards ohne Plugins oder Source-to-Source-Kompilierung (Transpilierung). Die Grundlage für das Ausführen von .Net-Code im Browser ist WebAssembly (kurz wasm). WebAssembly ist ein kompaktes Byte-Code-Format, dass für schnelle Downloads und die bestmögliche Ausführungsgeschwindigkeit optimiert wurde. Um die Downloadgröße gering zu halten, werden nicht benötigte Funktionalitäten während der Kompilierung analysiert und entfernt.

Der WebAssembly-Code kann auf die gesamte Funktionalität des Browsers via Javascript-Interoperabilität zugreifen. Außerdem wird der WebAssembly-Code in der gleichen Sandbox ausgeführt wie der Javascript-Code, um bösartigen Manipulationen vorzubeugen.

Als Beispiel für diesen Artikel werden wir ein Mitarbeiter-Management-System erstellen und die nötigen CRUD Operationen dafür implementieren.

Und so wird die finale Applikation aussehen.

Voraussetzungen

Die Datenbank

Wir erstellen eine Datenbank namens EmployeeDB und darin eine Tabelle, die wir Employee nennen, um die Daten für die Mitarbeiter zu speichern. Also öffnen wir das SQL Server Management Studio, verbinden uns mit unserem lokalen Server, um mit folgendem Skript die Datenbank und die Tabelle Employee zu erzeugen.

CREATE DATABASE EmployeeDB
GO

USE EmployeeDB
GO

CREATE TABLE Employees (
    Id int IDENTITY(1,1) PRIMARY KEY,
    Name varchar(20) NOT NULL,
    City varchar(20) NOT NULL,
    Department varchar(20) NOT NULL,
    Gender varchar(6) NOT NULL
)
GO

Auflistung 1: SQL-Skript für DB und Tabelle

Die Projekterstellung

Bevor wir mit unserem Projekt starten, müssen wir zwei Voraussetzungen prüfen und öffnen dazu die Powershell.

Zum Einen, ob wir die .NET Core 3.0 Preview 2 SDK installiert haben. Dazu führen wir folgendes aus:

dotnet --info

Der Befehl listet alle installierten .NET Core SDKs/Runtimes auf. Die geforderte SDK für unser Projekt sollte, wie im Bild zu sehen, aufgelistet sein.

Zum Anderen benötigen wir die Blazor Templates, um das Projekt zu erzeugen. Die Templates installieren wir mit:

 

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::0.8.0-preview-19104-04

 

Sind die Templates installiert, können wir fortfahren und eine Verzeichnisstruktur für unser Projekt anlegen. Mein Projektverzeichnis habe ich zum Beispiel unter C:\Source\Repositories\ erstellt und EmployeeBlazor angelegt. Wo ihr eurer Projektverzeichnis erzeugt, ist im Prinzip egal, wichtig ist, dass wir über die Powershell in den Ordner wechseln, bevor wir den Befehl zum Erstellen des Projektes ausführen.

 

dotnet new blazorhosted

Das gerade erstellte Projekt können wir jetzt in Visual Studio 2019 öffnen. Dazu starten wir Visual Studio und werden mit einem Dialog begrüßt, der uns neue Projekte erstellen, vorhandene Projekte klonen oder laden lässt.

Wir entscheiden uns, das gerade erstellte Projekt zu öffnen und navigieren in den nächsten Dialog, in unser Projektverzeichnis, um die Solution-Datei zu öffnen.

Visual Studio wird nun die Solution laden und notwendige Abhängigkeiten wie Nuget-Pakete für das Projekt bereitstellen. Wenn alles fertiggestellt wurde, sollte unsere Projektstruktur im Projektmappen-Explorer wie folgt aussehen:

  1. EmployeeBlazor.Client – enthält den clientseitigen Code und die Seiten, die im Browser gerendert werden
  2. EmployeeBlazor.Server – enthält den serverseitigen Code, wie zum Beispiel die Funktionalitäten zur Kommunikation mit der Datenbank und die Web API
  3. EmployeeBlazor.Shared – enthält den Code, der sowohl auf Client- als auch auf der Serverseite genutzt wird. Das Projekt enthält unsere Daten-Klassen.

Zum Test, ob die Erstellung des Projekts funktioniert hat und alle Abhängigkeiten aufgelöst wurden, kompilieren wir unsere Solution und starten die Webanwendung. Ist alles reibungslos verlaufen, sollten wir im eingestellten Standardbrowser folgendes zu sehen bekommen.

Bereinigung der Solution

Wie zu sehen ist, gibt es zwei Beispiel-Komponenten, die über das Projekttemplate erzeugt werden. Da wir diese nicht benötigen, werden wir die Solution bereinigen.

Im Projekt EmployeeBlazor.Shared löschen wir die Datei WeatherForecast.cs. Der SampleDataController im Projekt EmployeeBlazor.Server ist von der Datei abhängig, weshalb wir auch diesen löschen. Damit sind diese zwei Projekte schon bereinigt.

Weiter geht es zum Projekt EmployeeBlazor.Client, wo wir zunächst im Ordner Pages die Komponenten Counter.cshtml und FetchData.cshtml löschen.

Danach öffnen wir die Datei Index.cshtml, die sich im gleichen Ordner befindet, löschen die Komponente SurveyPrompt und ersetzen den Text für die Überschrift mit „Willkommen beim Mitarbeiter-Management-System”.

Da wir keine Abhängigkeit mehr von der Komponente SurveyPrompt haben, fällt auch diese unserer Bereinigung zum Opfer und wird aus dem Ordner Shared entfernt.

Letztlich fehlt noch die Überarbeitung des Navigationsmenüs. Die Komponenten Counter und Fetch Data wurden von entfernt, weshalb wir die Menüeinträge ebenfalls entfernen müssen. Der Code für das Navigationsmenü sollte nach dem Löschen wie folgt aussehen:

<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">EmployeeBlazor</a>
<button class="navbar-toggler" onclick="@ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" onclick="@ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
</ul>
</div>
@functions {
bool collapseNavMenu = true;
string NavMenuCssClass => collapseNavMenu ?"collapse": null;
void ToggleNavMenu()
{
collapseNavMenu =!collapseNavMenu;
}
}

Auflistung 2: Das bereinigte Navigationsmenü

Wir speichern alles, kompilieren die Solution neu und hoffen, dass wir nichts vergessen haben. Nach erfolgreicher Kompilierung starten wir das Projekt und unsere Webanwendung sollte wie in Abbildung 9 aussehen.

Das grundlegende Projekt ist erstellt und vorbereitet. Kommen wir endlich zur Implementierung.

Das Datenmodell und das Repository-Interface

Das Server- sowie das Client-Projekt werden mit der gleichen Datenklasse arbeiten. Darum erstellen wir im Projekt EmployeeBlazor.Shared das Verzeichnis Models und fügen die Klasse Employee hinzu.

using System;
using System.Collections.Generic;
using System.Text;
namespace EmployeeBlazor.Shared.Models { public class Employee { public int Id { get; set;} public string Name { get; set;} public string City { get; set;} public string Department { get; set;} public string Gender { get; set;}
}
}

Auflistung 3: Datenklasse Employee

Ähnlich wie bei der Datenklasse, werden das Server- und Client-Projekt über das gleiche Interface die Datenkommunikation handhaben, nur die Implementierungen werden sich unterscheiden. Deshalb legen wir einen neuen Unterordner im Projekt EmployeeBlazor.Shared an. Dazu machen wir einen Rechtsklick auf das Projekt im Projektmappen-Explorer, wählen aus dem Kontextmenü Hinzufügen > Neuer Ordner aus und vergeben für den Ordner den Namen Interfaces.

Zu diesem Ordner fügen wir das Interface IEmployeeRepository hinzu. Das erreichen wir mit Rechtsklick und Auswahl von Hinzufügen > Neues Element im Kontextmenü. Anschließend suchen wir im geöffneten Dialog nach Schnittstelle, benennen die Datei IEmployeeRepository.cs und schließen den Dialog über den Button Hinzufügen.

Wir öffnen die Datei IEmployeeRepository.cs und ergänzen das Interface, so dass der Code wie folgt aussieht.

using EmployeeBlazor.Shared.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace EmployeeBlazor.Shared.Interfaces
{
public interface IEmployeeRepository
{ Task<IEnumerable<Employee>> GetAll(); Task<Employee> Get(int id); Task Insert(Employee employee); Task Update(Employee employee); Task Delete(int id);
}
}

Auflistung 4: Interface IEmployeeRepository

Zu diesem Zeitpunkt sollte das Projekt EmployeeBlazor.Client folgende Struktur aufweisen.

Das Server-Repository

Wir wollen Dapper benutzen, um mit der Datenbank zu kommunizieren. Hierzu müssen wir erst die NuGet-Pakete Dapper und Dapper.Contrib zum Projekt EmployeeBlazor.Server hinzufügen. Die Paket-Manager-Konsole öffnen wir über EXTRAS > NuGet-Paket-Manager > Paket-Manager-Konsole und wählen in der Liste für das Standardprojekt den Eintrag EmployeeBlazor.Server aus. Danach installieren wir die zwei NuGet-Pakete:

install-package dapper
install.package dapper.contrib

Nun legen wir einen neuen Unterordner im Projekt EmployeeBlazor.Server an und nennen den Ordner Repositories. Dem Ordner fügen wir nun mit Rechtsklick und Hinzufügen > Klasse die Klasse EmployeeRepository hinzu. Wir öffnen die Datei EmployeeRepository.cs und ergänzen die Klasse, so dass der Code wie folgt aussieht.

using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using Dapper.Contrib.Extensions;
using EmployeeBlazor.Shared.Interfaces;
using EmployeeBlazor.Shared.Models;
using Microsoft.Extensions.Configuration;
namespace EmployeeBlazor.Server.Repositories { public class EmployeeRepository : IEmployeeRepository { private readonly IConfiguration _config; public EmployeeRepository(IConfiguration config)
{
_config = config;
}
public IDbConnection Connection
{
get {
return new
SqlConnection(_config.GetConnectionString("DefaultConnection"));
}
}
public async Task Insert(Employee employee)
{
using(IDbConnection conn = Connection)
{
conn.Open();
await conn.InsertAsync<Employee>(employee);
}
}
public async Task Delete(int id)
{
using(IDbConnection conn = Connection)
{
conn.Open();
await conn.DeleteAsync<Employee>(new Employee {Id = id });
}
}
public async Task<Employee> Get(int id)
{
using(IDbConnection conn = Connection)
{
conn.Open();
var result = await conn.GetAsync<Employee>(id);
return result;
}
}
public async Task<IEnumerable<Employee>> GetAll()
{
using(IDbConnection conn = Connection)
{
conn.Open();
var result = await conn.GetAllAsync<Employee>();
return result;
}
}
public async Task Update(Employee employee)
{
using(IDbConnection conn = Connection)
{
conn.Open();
await conn.UpdateAsync<Employee>(employee);
}
}
}
}

Auflistung 5: Klasse EmployeeRepository des Servers

In der Klasse EmployeeRepository sind die Methoden für den Datenzugriff implementiert. Die Methoden GetAll und Get liefern uns die Datensätze für alle Mitarbeiter bzw. einen Datensatz für die angegebene Id. Zum Hinzufügen eines neuen Datensatzes wird die Methode Insert verwendet und die Methode Update dient zum Aktualisieren eines vorhandenen Datensatzes. Schließlich gibt es noch die Methode Delete, um einen Datensatz zu löschen. Somit sind alle unsere CRUD-Methoden im Repository implementiert. Der Einfachheit halber wurde auf ein spezifisches Exception-Handling verzichtet und eventuell auftretende Ausnahmen werden einfach im Call-Stack nach oben weitergereicht.

An dieser Stelle funktioniert die Verbindung zur Datenbank noch nicht. Im Code des Repositories wird auf die Einstellung DefaultConnection verwiesen. Also müssen wir zunächst die Datei appsettings.json zum Projekt EmployeeBlazor.Server hinzufügen. Über einen Rechtsklick auf das Projekt, wählen wir anschließend aus dem Kontextmenü Hinzufügen > Neues Element. Im geöffneten Dialog suchen wir nach App-Einstellungsdatei, übernehmen den Standardwert appsettings.json und beenden den Dialog über den Button Hinzufügen. Der Inhalt der Einstellungsdatei sollte nach unseren individuellen Anpassungen der Eigenschaft DefaultConnection etwa wie folgt aussehen.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=localhost;Initial Catalog=EmployeeDB;Persist
Security Info=True;User ID=sa;Password=admin"

  }
}

Auflistung 6: Einstellung für die Datenbankverbindung

Der Web-API-Controller

Auf dem Ordner EmployeeBlazor.Server/Controllers machen wir einen Rechtsklick und wählen über das Kontextmenü Hinzufügen > Neues Element. Im folgenden Dialog suchen wir nach API Controller Class, wählen das Template aus und nennen die Klasse EmployeeController. Schließlich beenden wir den Dialog mit einem Klick auf den Button Erstellen. Nachdem Visual Studio das Klassengerüst für uns erzeugt hat, öffnen wir dieses und ergänzen die CRUD-Methoden. Die Methoden versehen wir mit den Attributen für die HTTP-Verbs sowie die Route unter der der API-Endpunkt ansprechbar ist.

using System.Collections.Generic;
using System.Threading.Tasks;
using EmployeeBlazor.Server.Repositories;
using EmployeeBlazor.Shared.Models;
using Microsoft.AspNetCore.Mvc;
namespace EmployeeBlazor.Server.Controllers
{
[Route("api/[controller]")]
public class EmployeeController : Controller
{
private readonly IEmployeeRepository _repository;
public EmployeeController(IEmployeeRepository repo)
{
_repository = repo;
}
// GET: api/<controller>
[HttpGet]
public async Task<IEnumerable<Employee>> Get()
{return await _repository.GetAll();
}
// GET api/<controller>/5
[HttpGet("{id}")]
public async Task<Employee> Get(int id)
{
return await _repository.Get(id);
}
// POST api/<controller>
[HttpPost]
public async void Post([FromBody]Employee employee)
{
if(ModelState.IsValid)
{
await _repository.Insert(employee);
}
}
// PUT api/<controller>
[HttpPut]
public async void Put([FromBody]Employee employee)
{
if(ModelState.IsValid)
{
await _repository.Update(employee);
}
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
public async void Delete(int id)
{
await _repository.Delete(id);
}
}
}

Auflistung 7: API-Controller-Klasse EmployeeController

Bevor wir mit dem Clientcode fortfahren können, müssen wir noch unser EmployeeRepository als Service in der ConfigureServices-Methode der Klasse Startup registrieren. So kann die Anforderung von IEmployeeRepository durch eine Instanz von EmployeeRepository beim Erzeugen eines EmployeeController aufgelöst werden.

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IEmployeeRepository, EmployeeRepository>();
services.AddMvc().AddNewtonsoftJson();
services.AddResponseCompression();
}

Auflistung 8: Server-Konfiguration der Services für Dependency Injection

Das war der letzte Baustein unseres Backends und wir können mit Clientcode fortfahren. Zu diesem Zeitpunkt sollte das Projekt EmployeeBlazor.Server folgende Struktur aufweisen.

 

Das Client-Repository

Als Erstes werden wir uns um die Kommunikation mit der Web-API kümmern. Dafür erstellen wir einen neuen Ordner DataAccess im Projekt EmployeeBlazor.Client. Wir fügen dem gerade erstellten Ordner die Klasse DataRepository hinzu.

Wir öffnen die neue Klassendatei und leiten die Klasse, wie beim Server-Repository, vom Interface IEmployeeRepository ab. Danach implementieren wir die Methoden des Interfaces. Im Gegensatz zum Server-Repository, das eine direkte Verbindung zur Datenbank hat, wird das Client-Repository mit Hilfe der Klasse HttpClient die Daten über unsere Web-API beziehen.

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using EmployeeBlazor.Shared.Interfaces;
using EmployeeBlazor.Shared.Models;
using Microsoft.AspNetCore.Components;
namespace EmployeeBlazor.Client.DataAccess
{
public class DataRepository : IEmployeeRepository
{
private readonly HttpClient _httpClient;
public DataRepository(HttpClient client)
{
_httpClient = client;
}
public async Task Delete(int id)
{
await _httpClient.DeleteAsync("/api/employee/"+ id);
}
public async Task<Employee> Get(int id){
return await _httpClient.GetJsonAsync<Employee>("/api/employee/"+ id);
}
public async Task<IEnumerable<Employee>> GetAll()
{
return await _httpClient.GetJsonAsync<IEnumerable<Employee>>("/api/employee");
}
public async Task Insert(Employee employee)
{
await _httpClient.SendJsonAsync(HttpMethod.Post,"/api/employee", employee);
}
public async Task Update(Employee employee)
{
await _httpClient.SendJsonAsync(HttpMethod.Put,"/api/employee", employee);
}
}
}

Auflistung 9: Klasse DataRepository des Clients

Bevor wir mit der Blazor-Komponente fortfahren können, müssen wir unser DataRepository als Service in der ConfigureServices-Methode der Klasse Startup registrieren, analog zum Projekt EmployeeBlazor.Server. So kann die Anforderung von IEmployeeRepository durch eine Instanz von DataRepository beim Erzeugen der Blazor-Komponente aufgelöst werden.

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IEmployeeRepository, DataRepository>();
}

Auflistung 10: Client-Konfiguration der Services für Dependency Injection

Die Blazor-Komponente

Um etwas im Browser sehen zu können, benötigen wir eine Blazor-Komponente. Diese fügen wir im Ordner EmployeeBlazor.Client/Pages hinzu. Dazu verwenden wir den Dialog für das Hinzufügen von neuen Elementen, wie in Abbildung 12 zu sehen. Die zu erstellende Razor Page, was die Basis für unsere Blazor-Komponente darstellt, nennen wir EmployeeData.cshtml. Nach einem Klick auf den Button Hinzufügen finden wir zwei neue Dateien im Ordner EmployeeBlazor.Client/Pages: EmployeeData.cshtml und EmployeeData.cshtml.cs.

Code-Behind der Blazor-Komponente

Wir öffnen die Datei EmployeeData.cshtml.cs und ersetzen den Inhalt mit dem folgenden Code.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EmployeeBlazor.Shared.Interfaces;
using EmployeeBlazor.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Services;
namespace EmployeeBlazor.Client.Pages
{
public class EmployeeDataBase : ComponentBase
{
[Inject]
protected IEmployeeRepository DataRepository { get; set;}
[Inject]
protected IUriHelper UriHelper { get; set;}
[Parameter]
public int Id { get; protected set;}=0;
[Parameter]
public string Action { get; protected set;}
protected string Title { get; set;}
protected List<Employee> employeeList;
protected Employee currentEmployee;
protected override async Task OnParametersSetAsync()
{
if(Action =="fetch")
{
await FetchEmployees();
StateHasChanged();
}
elseif(Action =="create")
{
Title ="Mitarbeiter hinzufügen";
currentEmployee = new Employee();
}
elseif(Id !=0)
{
if(Action =="edit")
{
Title ="Mitarbeiter editieren";
}
elseif(Action =="delete")
{
Title ="Mitarbeiter löschen";
}
currentEmployee = await DataRepository.Get(Id);
}
}
protected async Task FetchEmployees()
{
Title ="Mitarbeiter";
var employees = await DataRepository.GetAll();
employeeList = employees.ToList();
}
protected async Task UpsertEmployee()
{
if(currentEmployee.Id !=0)
{
await DataRepository.Update(currentEmployee);
}
else
{
await DataRepository.Insert(currentEmployee);
}
UriHelper.NavigateTo("/employee/fetch");
}
protected async Task DeleteEmployee()
{
await DataRepository.Delete(Id); UriHelper.NavigateTo("/employee/fetch");
}
protected void Cancel()
{
Title ="Mitarbeiter"; UriHelper.NavigateTo("/employee/fetch");
}
}
}

Auflistung 11: Code-Behind der Blazor-Komponente

Schauen wir uns den Code etwas genauer an. Wir haben die Klasse EmployeeDataBase definiert, die alle Methoden beinhaltet, die die eigentliche Seite EmployeeData.cshtml benutzen wird.

Als Erstes injizieren wir eine Instanz von IEmployeeRepository, um die Möglichkeit zu haben, unsere Daten abrufen zu können. Weiterhin injizieren wir eine Instanz von IUriHelper, mit dem wir unsere Navigation per URL-Weiterleitung abbilden können. Danach haben wir die zwei Parameter Id und Action deklariert. Diese dienen zur Definition unserer Routen in der Seite EmployeeData.cshtml. Weiterhin haben wir noch die Eigenschaft Title deklariert, um in der Seitenüberschrift anzuzeigen, was wir gerade tun wollen.

Die Methode OnParameterSetAsync wird jedes Mal aufgerufen, wenn die Parameter für die Seite gesetzt werden. Anhand des Parameters Action stellen wir fest, welche Aktion ausgeführt werden soll. Ist die Aktion fetch, dann rufen wir die Methode FetchEmployees auf, um eine aktualisierte Liste mit Daten aus der Datenbank zu holen. Die Methode FetchEmployees setzt zum einen die Eigenschaft Title auf Mitarbeiter und zum anderen ruft sie die Methode StateHasChanged auf, um die Aktualisierung der UI auszulösen. Stellen wir fest, dass die Aktion create ist, dann setzen wir die Eigenschaft Title auf Mitarbeiter hinzufügen und erzeugen eine neue Objektinstanz von Employee. Weiterhin prüfen wir, ob der Parameter Id ungleich 0 ist. In diesem Fall handelt es sich um eine der Aktionen edit oder delete. Auch hier setzen wir die Eigenschaft Title entsprechend auf Mitarbeiter editieren bzw. Mitarbeiter löschen. Anschließend holen wir die Daten für die gesetzte Id über unsere Web-API.

Die Methode UpsertEmployee prüft zuerst, ob ein neuer Mitarbeiter angelegt oder ein bestehender Mitarbeiter aktualisiert werden soll. Ist die Eigenschaft Id der Employee-Instanz ungleich 0, dann soll der entsprechende Datensatz aktualisiert werden und die Methode schickt einen PUT-Request an unsere Web-API. Ähnliches gilt, wenn die Id der Employee-Instanz gleich 0 ist. Dann soll ein neuer Datensatz angelegt werden und wir schicken einen POST-Request an unsere Web-API. Nach dem Aufruf des PUT- oder POST-Requests navigieren wir zurück zu unserer Mitarbeiterliste, um die Aktualisierung der Anzeige auszulösen.

Die Methode DeleteEmployee wird einen DELETE-Request anhand der gesetzten Id an unsere Web-API weiterleiten und anschließend zurück zur Mitarbeiterliste navigieren, um die Aktualisierung der Liste zu bewirken.

Die Methode Cancel setzt die Eigenschaft Title auf Mitarbeiter und navigiert zurück zur Mitarbeiterliste.

UI der Blazor-Komponente

Nun kommen wir zur eigentlichen Ansicht, die der Benutzer im Browser sieht. Hierfür öffnen wir die Datei EmployeeData.cshtml und ersetzen den Inhalt mit folgendem Code.

@page "/employee/{Action}/{Id:int}"
@page "/employee/{Action}"
@inherits EmployeeDataBase
<h1>@Title</h1>
@if (Action == "fetch")
{
<p>
<ahref="/employee/create">Mitarbeiter hinzufügen</a>
</p>
}
@if (Action == "create" || Action == "edit")
{
<divclass="col-md-auto">
<tableclass="table">
<tr>
<td>
<labelfor="Name"class="control-label">Name</label>
</td>
<td>
<inputtype="text"class="form-control" bind="@currentEmployee.Name"/>
</td>
</tr>
<tr>
<td>
<labelfor="Department"class="control-label">Abteilung</label>
</td>
<td>
<inputtype="text"class="form-control" bind="@currentEmployee.Department"/>
</td>
</tr>
<tr>
<td>
<labelfor="Gender"class="control-label">Geschlecht</label>
</td>
<td>
<select asp-for="Gender"class="form-control" bind="@currentEmployee.Gender">
<optionvalue="">-- Bitte Geschlecht wählen --</option>
<optionvalue="Male">Männlich</option>
<optionvalue="Female">Weiblich</option>
<optionvalue="Divers">Divers</option>
</select>
</td>
</tr>
<tr>
<td>
<labelfor="Stadt"class="control-label">City</label>
</td>
<td>
<inputtype="text"class="form-control" bind="@currentEmployee.City"/>
</td>
</tr>
</table>
<divclass="form-group">
<buttonclass="btn btn-success"onclick="@UpsertEmployee">Speichern</button>
<buttonclass="btn btn-danger"onclick="@Cancel">Abbrechen</button>
</div>
</div>
}
else if (Action == "delete")
{
<divclass="col-md-auto">
<tableclass="table">
<tr>
<td>Name</td>
<td>@currentEmployee.Name</td>
</tr>
<tr>
<td>Geschlecht</td>
<td>@currentEmployee.Gender</td>
</tr>
<tr>
<td>Abteilung</td>
<td>@currentEmployee.Department</td>
</tr>
<tr>
<td>Stadt</td>
<td>@currentEmployee.City</td>
</tr>
</table>
<divclass="form-group">
<buttonclass="btn btn-danger"onclick="@DeleteEmployee">Löschen</button>
<buttonclass="btn"onclick="@Cancel">Abbrechen</button>
</div>
</div>
} @if (employeeList == null) { <p><em>Lade Datensätze...</em></p>
else { <tableclass='table'>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Geschlecht</th>
<th>Abteilung</th>
<th>Stadt</th>
<th/>
</tr>
</thead>
<tbody>
@foreach (var emp in employeeList)
{
<tr>
<td>@emp.Id</td>
<td>@emp.Name</td>
<td>@emp.Gender</td>
<td>@emp.Department</td>
<td>@emp.City</td>
<td>
<ahref='/employee/edit/@emp.Id'>Edit</a> |
<ahref='/employee/delete/@emp.Id'>Delete</a>
</td>
</tr>
}
</tbody>
</table>
}

Auflistung 12: Definition der UI

Die ersten zwei Zeilen definieren unsere Routen für die Seite.

  1. /employee/{Action}/{Id}: Die Route akzeptiert eine Aktion sowie eine Id. Wird diese Route aufgerufen, dann soll ein Mitarbeiter, abhängig von Request-Typ, editiert oder gelöscht werden.
  2. /employee/{Action}: Diese Route akzeptiert nur eine Aktion. Sie wird aufgerufen, wenn die Mitarbeiterliste angezeigt oder ein neuer Mitarbeiter erzeugt werden soll.

Unsere Blazor-Komponente erbt von der Klasse EmployeeDataBase und somit haben wir Zugriff auf die Methoden, die wir im vorherigen Abschnitt implementiert haben.

Als Nächstes wird die Überschrift der Seite gesetzt. Diese ist dynamisch und ändert sich mit der jeweiligen Aktion, die auf der aktuellen Seite ausgeführt wird.

Den Link Mitarbeiter hinzufügen zeigen wir nur an, wenn die Aktion auf fetch gesetzt ist. Ist die Aktion auf create oder edit gesetzt, dann verbergen wir den Link und zeigen an der Stelle das Eingabeformular an. Im Formular gibt es zwei Buttons: Speichern und Abbrechen. Klicken wir Speichern, rufen wir die Methode UpsertEmployee auf. Beim Klick auf Abbrechen wird hingegen die Methode Cancel aufgerufen.

Erreichen wir die Seite mit der Aktion delete, so zeigen wir statisch die Informationen des Mitarbeiters an, der gelöscht werden soll. Auch hier sind zwei Buttons definiert: Löschen und Abbrechen. Der Button Abbrechen ruft, wie beim Eingabeformular, die Methode Cancel auf. Wird der Button Löschen betätigt, dann rufen wir die Methode DeleteEmployee auf.

Am Ende der Seite haben wir eine Tabelle definiert, die die Daten aller gespeicherten Mitarbeiter anzeigt. Jeder Datensatz hat zwei Links, um den jeweiligen Datensatz editieren bzw. löschen zu können. Die Tabelle wird immer angezeigt und bei jeder Aktion aktualisiert, sofern die Liste mit Datensätzen im EmployeeDataBase initialisiert ist.

Das Navigationsmenü

Im letzten Schritt fügen wir dem Navigationsmenü einen Eintrag für unsere Mitarbeiterseite hinzu. Dazu öffnen wir die Datei NavMenu.cshtml im Ordner EmployeeBlazor.Client/Shared und ersetzen den Inhalt mit folgendem Code:

<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">EmployeeBlazor</a>
<button class="navbar-toggler" onclick="@ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" onclick="@ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="/employee/fetch"
Match="NavLinkMatch.Prefix">
<span class="oi oi-list-rich" aria-hidden="true"></span> Mitarbeiter
</NavLink>
</li>
</ul>
</div>
@functions {
bool collapseNavMenu = true;
string NavMenuCssClass => collapseNavMenu ?"collapse": null;
void ToggleNavMenu()
{
collapseNavMenu =!collapseNavMenu;
}
}

Auflistung 13: Navigationsmenü mit Mitarbeiterseite

Zu diesem Zeitpunkt sollte das Projekt EmployeeBlazor.Client folgende Struktur aufweisen.

Fazit

Unsere Beispielanwendung ist fertig und kann ausprobiert werden. Der vollständige Code kann auf Github gefunden werden.

Wir haben gesehen, wie einfach sich SPAs mit dem neuen Blazor-Framework entwickeln lassen und ohne dafür eine einzige Zeile Javascript zu schreiben.

Die Verwendung von .Net im Browser für die clientseitige Webentwicklung bietet viele Vorteile:

  • C#: Den Code vollständig in C# schreiben anstatt Javascript.
  • Ökosystem: Die Möglichkeit, die vorhandenen .Net-Bibliotheken benutzen zu können.
  • Full-Stack: Logik kann im Server und Client gemeinsam genutzt werden.
  • Geschwindigkeit und Skalierbarkeit: .NET wurde für Leistung, Zuverlässigkeit und Sicherheit konzipiert.
  • Stabilität und Konsistenz: Setzt auf eine Sammlung von Sprachen, Frameworks und Tools, die stabil, funktionsreich und einfach zu verwenden sind.

Wir können gespannt sein, wie sich Blazor weiterentwickeln wird. Für Entwickler von Desktop-Anwendungen, die sich mit WPF auskennen, ist es eine einfache Möglichkeit der Um- und Eingewöhnung in die Welt der Webentwicklung.

Mehr Informationen zu Blazor:

Noch mehr lernen kann man bei uns übrigens mit der lise Academy. Dort geben wir unser Wissen weiter, damit Sie das nächste Level erreichen!

Bring mich auf aufs nächste Level! 

 

 

Foto: igorstevanovic, Shutterstock

Diesen Artikel weiterempfehlen

 Teilen  Teilen  Teilen  Teilen