Webprogrammierung mit Blazor WebAssembly, Teil 1: Web-API-Aufrufe und Rendering

Seite 3: Rendern von HTML aus Daten

Inhaltsverzeichnis

Auf dieser Basis lässt sich nun schon der Hauptteil der Benutzeroberfläche rendern. Die Listings 3 und 4 zeigen die Implementierung von Index.razor.cs und der zugehörigen Template-Datei Index.Razor.

Listing 3: Index.razor.cs

using Microsoft.AspNetCore.Components;
using MiracleListAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;


namespace Web.Pages
{
 public partial class Index
 {
  [Inject]
  AuthenticationManager am { get; set; }
  [Inject]
  MiracleListAPI.MiracleListProxy proxy { get; set; }

  #region Einfache Properties zur Datenbindung
  List<BO.Category> categorySet { get; set; }
  List<BO.Task> taskSet { get; set; }
  BO.Category category { get; set; }
  BO.Task task { get; set; }
  #endregion

  /// <summary>
  /// Lebenszyklusereignis: Komponente wird initialisiert
  /// </summary>
  /// <returns></returns>
  protected override async Task OnInitializedAsync()
  {
  // TODO: Muss später im Anmeldebildschirm erfolgen
   if (await am.Login())
   {
    await ShowCategorySet();
   }
  }

  public async Task ShowCategorySet()
  {
   categorySet = await proxy.CategorySetAsync(am.Token);
   // zeige erste Kategorie an, wenn es Kategorien gibt!
   if (this.categorySet.Count > 0) await ShowTaskSet(this.categorySet[0]);
  }

  public async Task ShowTaskSet(BO.Category c)
  {
   this.category = c;
   this.taskSet = await proxy.TaskSetAsync(c.CategoryID, am.Token);
  }

  public async Task ShowTaskDetail(BO.Task t)
  {
   this.task = t;
  }


 } // end class Index
}

Hier werden die ersten beiden Spalten aus Abbildung 2 erzeugt. Für die dritte Spalte wird im zweiten Teil des Tutorials die Bearbeitungsansichtskomponente TaskEdit.razor mit dem Tag <TaskEdit> eingebunden. Das Razor-Template in Listing 4 beginnt mit der Deklaration der Route "/". Hier folgt später mit der Direktive @attribute [Authorize] die Festlegung, dass nur angemeldete Benutzer diese Komponente aufrufen dürfen.

Listing 4: Index.razor.cs

@page "/"

<div class="row">

 <!-- ### Spalte 1: Kategorien-->
 @if (categorySet != null)
 {
 <div class="WLPanel col-xs-4 col-sm-4 col-md-3 col-lg-2 @(this.task!=null ? "hidden-sm hidden-xs": ""  )">
  <!-- ---------- Überschrift Spalte 1-->
  <h4> @(categorySet.Count()) <span>Categories</span></h4>
  <!-- ---------- neue Kategorie eingeben-->
  @*TODO*@
  <!-- ---------- Kategorieliste ausgeben-->

  <ol class="list-group scroll">
   @foreach (var c in categorySet)
   {
    <li class="list-group-item" @onclick="() => ShowTaskSet(c)" title="Task Category #@c.CategoryID" style="Background-color:@(this.category != null && c.CategoryID == this.category.CategoryID ? "#E0EEFA" : "white")">
     @c.Name
     <span id="Remove" style="float:right;" class="glyphicon glyphicon-remove-circle"></span>
    </li>
   }
  </ol>
 </div>

  <!-- ### Spalte 2: Aufgabenliste-->
  <div class="WLPanel @(this.task==null ? "col-xs-8 col-sm-8 col-md-9 col-lg-10 ": "hidden-xs col-sm-6 col-md-5 col-lg-6"  )">
   <!-- ---------- Überschrift Spalte 2-->
   <h4 id="TaskHeadline">@(taskSet == null ? 0 : taskSet.Count()) <span>Tasks in</span><i> @category?.Name</i></h4>
   <!-- ---------- neue Aufgaben eingeben-->
   @*TODO*@
   <!-- ---------- Aufgabenliste ausgeben-->
   @if (taskSet != null)
   {<ol id="TaskSet" class="list-group scroll">
     @foreach (var t in taskSet)
     {
      <li class="list-group-item" style="Background-color: @((t.TaskID == this.task?.TaskID) ? "#E0EEFA" : "white")" title="Task #@t.TaskID">
       <span id="Remove" style="float:right;" class="glyphicon glyphicon-remove-circle" ></span>

       <input type="checkbox" name="@("done" + t.TaskID)" id="@("done" + t.TaskID)" checked="@t.Done" class="MLcheckbox"  />

       <b>@t.Title</b>

       @if (t.Due.HasValue)
        if (t.Due.Value < DateTime.Now)
        {
         <div style="color:red">Due since @t.Due.Value.ToShortDateString()</div>
        }
        else
        {
         <div>Due at @t.Due.Value.ToShortDateString()</div>
        }
      </li>
     }
    </ol>
   }
  </div>
 }
@*TODO <TaskEdit> *@
</div>

Im Programmcode findet man eine Reihe von @if-Bedingungen, die fallweise ganze Blöcke nur dann rendern, wenn die Daten auch verfügbar sind. Das ist in Blazor grundsätzlich wichtig, da viele Operationen asynchron sind (s. die Web-API-Aufrufe) und es möglich ist, dass die Daten beim ersten Rendern noch nicht verfügbar sind, sodass es zu Null-Referenz-Fehlern kommen kann. Wenn die Daten dann etwas später eintreffen, stößt Blazor automatisch ein erneutes Rendern an.

Fallunterscheidungen mit dem bedingten C#-Ausdruck ( Bedingung ? ja : nein ) findet man auch innerhalb einzelner HTML-Attribute, zum Beispiel bei style für die farbliche Hervorhebung (hellblau) der aktuellen Kategorie und der aktuellen Aufgabe sowie bei class für das fallweise Ausblenden ganzer Spalten.

Mit @foreach bildet man in Razor eine Schleife, zum Beispiel über die Liste der Kategorien und die Liste der Aufgaben. Die Code-Behind-Datei (s. Listing 4) stellt zur Datenbindung zwei Properties für die Listen von Aufgabenkategorie (List<BO.Category> categorySet) und die Listen der Aufgaben innerhalb einer Kategorie (List<BO.Task> taskSet) bereit. In der Vorlage wird jeweils mit @foreach über diese Listen iteriert, um <li>-Tags zu erzeugen, die mithilfe von Bootstrap-CSS-Klassen (list-group-item) als große, leicht auch mit dem Finger ansteuerbare Blöcke erscheinen. Bei einem Klick (@onclick) auf einen der Blöcke wird jeweils eine Methode ShowTaskSet(c) und ShowTaskDetail(t) erzeugt, die das aktuelle Objekt in den Properties Category beziehungsweise im Task vermerkt. In OnInitializedAsync() ist festgelegt, dass beim Initialisieren der Razor Component die ShowCategorySet() gerufen wird, die die Kategorienliste lädt und die erste Kategorie anzeigt, wenn eine vorhanden ist.