Anpassen des Seitenmodells

In dieser Lerneinheit lernen Sie die Struktur einer einfachen PageModel-Klasse für eine Razor-Seite und die zugehörigen Elemente kennen. Dabei fügen Sie dem Create-Formular der Razor-Seite eine POST-Ereignishandlermethode hinzu. Abschließend sehen Sie sich die Modellklasse Product und die zugehörigen Datenattribute an, die die client- und serverseitige Validierung steuern.

Struktur einer grundlegenden PageModel-Klassendatei für eine Razor-Seite

Öffnen Sie die PageModel-Klassendatei Create.cshtml.cs, die sich im Verzeichnis ContosoPets.Ui/Pages/Products/ befindet. Beim Erstellen der neuen Razor-Seite Create wurde die zugehörige PageModel-Klassendatei Create.cshtml.cs generiert. Sehen Sie sich deren Inhalt an. Die Datei sollte den folgenden C#-Code enthalten:

namespace Contoso.UI.Pages.Products
{
    public class CreateModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

In der PageModel-Klassendatei der Razor-Seite werden alle Handler für Anforderungen definiert, die an die Seite gesendet werden. Außerdem werden die Daten zum Rendering der Seite festgelegt. Das PageModel sorgt dafür, dass diese Zuständigkeiten unabhängig von Ihrer Razor-Seite verwaltet werden. Außerdem ist Ihre Anwendung damit besonders modular und leichter zu warten. Die PageModel-Klasse wird konventionsgemäß Model genannt und gehört demselben Namespace wie die Razor-Seite an. In diesem Fall wird für die CreateModel-Klasse der Namespace ContosoPets.Ui.Pages.Products verwendet.

Derzeit verarbeitet die CreateModel-Klasse die HTTP-GET-Anforderung, ohne jedoch eine Aktion auszuführen.

Sie können für jedes HTTP-Verb Handlermethoden hinzufügen. Am häufigsten werden die folgenden Handler verwendet:

  • OnGet, um den für die Seite erforderlichen Zustand zu initialisieren
  • OnPost, um übermittelte Formularangaben zu verarbeiten

Die Create-Seite ist ein Formular und benötigt daher die OnPost handler method.

Hinzufügen einer OnPost-Handlermethode zur PageModel-Klasse von Create

Ersetzen Sie den Code in der PageModel-Klassendatei Create.cshtml.cs, die sich im Verzeichnis ContosoPets.Ui/Pages/Products/ befindet, durch die folgenden Zeilen, und speichern Sie anschließend Ihre Änderungen:

using ContosoPets.Ui.Models;
using ContosoPets.Ui.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;

namespace ContosoPets.Ui.Pages.Products
{
    public class CreateModel : PageModel
    {
        private readonly ProductService _productService;

        [BindProperty]
        public Product Product { get; set; }

        public CreateModel(ProductService productService)
        {
            _productService = productService;
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            await _productService.CreateProduct(Product);

            return RedirectToPage("Index");
        }
    }
}

Die CreateModel-Klasse verfügt nun über die Handlermethode OnPostAsync. OnPostAsync wird bei POST-Anforderungen ausgeführt, wenn der Benutzer mit POST Daten über das Create-Formular übermittelt. Das Namenssuffix Async ist optional. Es wird jedoch konventionsgemäß oft für asynchrone Funktionen verwendet.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    await _productService.CreateProduct(Product);

    return RedirectToPage("Index");
}

Der OnPost-Handler muss für diese Anwendung die folgenden Aufgaben ausführen:

  • Er stellt sicher, dass die Daten, die von Benutzern übermittelt und mit POST zum PageModel gesendet werden, gültig sind.
  • Wenn die PageModel-Änderungen nicht gültig sind, wird die Seite Create den Benutzern noch einmal angezeigt. In einer Meldung wird dann darauf hingewiesen, welche Eingaben erforderlich sind.
  • Wenn die PageModel-Aktualisierung gültig ist, werden Datenänderungen dem Dienst ProductService übergeben. ProductService ist für HTTP-Anforderungen und für Antworten an die Web-API zuständig.

Erstellen von Bindungen für das Product-Modell

Die CreateModel-Klasse muss auf das Product-Modell zugreifen können. Sie validiert und übergibt Product-Einträge über das Create-Formular. Im folgenden Codeausschnitt wird für diese Aufgaben das [BindProperty]-Attribut verwendet:

[BindProperty]
public Product Product { get; set; }

Bindungen für Eigenschaften können dafür sorgen, dass Sie weniger Code schreiben müssen. Dies liegt daran, dass mit derselben Eigenschaft Felder (beispielsweise in <input asp-for="Product.Name">) gerendert werden.

Integrierte serverseitige Validierung von Modellen mithilfe von Datenanmerkungen in ASP.NET Core

Wenn Sie eine ASP.NET Core-Webanwendung erstellen, werden Modellbindungen und Validierungsfunktionen in diese integriert. Die Bindung und Validierung erfolgt automatisch, bevor eine Handlermethode einer Razor-Seite ausgeführt wird. Die Handlermethode OnPostAsync muss daher ausschließlich das Ergebnis dieser Validierung überprüfen.

if (!ModelState.IsValid)
{
    return Page();
}

Im obigen Codeausschnitt stellt ModelState Fehler dar, die bei der Modellbindung und Validierung auftreten. Wenn ModelState nicht gültig ist, wird die Seite Create dem Benutzer noch einmal angezeigt. In der letzten Lerneinheit haben Sie erfahren, wie die Razor-Seite Create die in ASP.NET Core integrierte clientseitige Validierung von Formulareingaben nutzt, um dem Benutzer Feedback zur Eingabe zu geben.

Wenn ModelState gültig ist, ruft der OnPostAsync-Handler eine Instanz von ProductService auf. ProductService ist für HTTP-Anforderungen und Antworten an die Web-API verantwortlich.

await _productService.CreateProduct(Product);

return RedirectToPage("Index");

Definieren von Validierungsregeln für das Produktmodell mithilfe von Datenanmerkungen

Für dieses Projekt wird die zentrale Modelldatei Product.cs verwendet, mit der das Product-Modell validiert und Vorgänge definiert werden. Sie wird von allen PageModels einer Razor-Seite verwendet, die für die Benutzeroberfläche für CRUD-Produktvorgänge zuständig sind. Außerdem wird die Datei zur Validierung von Produktdaten verwendet, die über die Web-API empfangen werden. Die Datei wird konventionsgemäß im Verzeichnis Models/ gespeichert. Der Namespace der Product-Modellklasse ist ContosoPets.Ui.Models.

Mithilfe der folgenden using-Anweisung kann Ihre neue CreateModel-Klasse auf beliebige Modelltypen im Namespace ContosoPets.Ui.Models und auch auf das Product-Modell zugreifen:

using ContosoPets.Ui.Models;

Sehen Sie sich die Product-Modellklasse an:

using System.ComponentModel.DataAnnotations;

namespace ContosoPets.Ui.Models
{
    public class Product
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        [Range(minimum:0.01, maximum:9999.99)]
        public decimal Price { get; set; }
    }
}

Datenanmerkungen sind einfache Attribute, mit denen ein bestimmtes Verhalten festgelegt wird, das für Modelleigenschaften erzwungen werden soll.

Für die Product-Klasse werden die Attribute [Required] und [Range] verwendet:

  • Das [Required]-Attribut gibt an, dass eine Eigenschaft über einen Wert verfügen muss.
  • Das [Range]-Attribut schränkt einen Wert auf einen bestimmten Bereich ein.

Wenn Sie weitere Validierungsregeln erzwingen möchten, können Sie Attribute ganz einfach im Product-Modell an einer Stelle ändern, ohne dass Änderungen an den PageModel-Klassendateien im Projekt erforderlich sind. Das ist äußerst nützlich.

Mit System.ComponentModel.DataAnnotations stehen Ihnen zahlreiche Datenanmerkungsattribute zur Verfügung. In diesem Modul haben Sie ein kurzes, vereinfachtes Beispiel gesehen.

Das Product-Modell als Datenübertragungsobjekt

Das Product-Modell wird gleichzeitig als Datenübertragungsobjekt (Data Transfer Object, DTO) verwendet. Ein DTO ist ein Objekt, das die Daten definiert, die über das Netzwerk gesendet werden. In diesem Fall ist die Web-API der Empfänger dieser Daten. Die ProductService-Klasse in ContosoPets.UI, die alle HTTP-Anforderungen verarbeitet, verwendet das Product-Modell als DTO. In diesem werden alle gültigen Product-Daten festgelegt, die an die Web-API gesendet und von dieser empfangen werden können.

Übergabe des ContosoPets.UI-ProductService-Diensts per Dependency Injection zur Verarbeitung von HTTP-Anforderungen

Im letzten Schritt übergibt die OnPost-Methode in Ihrer CreateModel-Klasse die validierten Daten der Dienstklasse ProductService. Die ProductService-Klasse ist ein Beispiel für eine typisierte HTTPClient-Dienstarchitektur. Einfach ausgedrückt ist die ProductService-Klasse dafür verantwortlich, alle an die Web-API gesendeten HTTP-Anforderungen zu verarbeiten, sodass der Code an nur einer Stelle verwaltet wird. Beim Start wird die Klasse als Dienst registriert und kann dadurch an beliebiger Stelle injiziert werden. In diesem Projekt wird der Dienst allen PageModel-Klassen übergeben, die CRUD-Vorgänge für Razor-Seiten initialisieren. In der nächsten Lerneinheit lernen Sie ein Beispiel für den Lebenszyklus der ProductService-HTTP-Anforderungslogik kennen.

Die ProductService-Klasse wurde mit der folgenden using-Anweisung der PageModel-Klasse des Create-Formulars verfügbar gemacht:

using ContosoPets.Ui.Services;

Der ProductService-Dienst wird dem CreateModel direkt über einen Konstruktorparameter mithilfe von Dependency Injection übergeben:

public class CreateModel : PageModel
{
    private readonly ProductService _productService;

    [BindProperty]
    public Product Product { get; set; }

    public CreateModel(ProductService productService)
    {
        _productService = productService;
    }

ASP.NET Core unterstützt das IoC-Muster (Inversion of Control; Umkehr des Kontrollflusses) mithilfe von Dependency Injection (DI). Mit diesem Muster kann der ProductService-Dienst direkt dem Konstruktor der Klasse übergeben werden, in der der Dienst verwendet wird. Das Framework übernimmt die Verantwortung dafür, eine Instanz der Klasse zu erstellen und diese freizugeben, wenn sie nicht mehr benötigt wird. In der ProductService-Klasse wird der Konstruktor CreateModel definiert, den der Dienst für die App bereitstellt. Diese Schnittstelle wird durch den konkreten Typ ProductService implementiert. Das IoC-Entwurfsmuster ermöglicht es ASP.NET Core-Entwicklern, die inhärenten Nachteile einer Klasse zu vermeiden, die eine direkte Abhängigkeit einer anderen Klasse entgegennimmt.

Im folgenden Codeausschnitt wird die Methode CreateModel aufgerufen. Dabei wird das Product-DTO übergeben, das über eine HTTP-Anforderung an die Web-API gesendet wird.

await _productService.CreateProduct(Product);

Den Lebenszyklus der HTTP-Anforderungslogik in der ProductService-Klasse des Projekts ContosoPets.UI lernen Sie im weiteren Verlauf des Moduls kennen.

Die Razor-Seite Create.cshtml und die zugehörigen CreateModel.cshtml.cs-Klassendateien sind nun vollständig. Im Folgenden ermöglichen Sie es Benutzern, zu dieser Seite zu navigieren.

Hinzufügen eines Anchor Tag Helper zur Razor-Seite /Pages/Products/Index.cshtml

  1. Aktualisieren Sie den Datei-Explorer, indem Sie auf das Aktualisierungssymbol im Editor klicken.

    Cloud Shell-Aktualisierungssymbol

  2. Öffnen Sie die Datei ContosoPets.Ui/Pages/Products/Index.cshtml der Razor-Seite, und fügen Sie ihr das unten hervorgehobene <a>-Markup hinzu:

    @page "{productId:int?}"
    @model ContosoPets.Ui.Pages.Products.IndexModel
    @{
        ViewData["Title"] = "Admin Index";
    }
    
    <h1>Product Administration</h1>
    
    <table class="table table-striped">
        <thead>
            <tr>
                <th scope="col">Product</th>
                <th scope="col">Price</th>
                <th scope="col"></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in Model.Products)
            {
                <tr>
                    <td>@product.Name</td>
                    <td>@product.Price.ToString("C")</td>
                    <td>
                        <a asp-page="Edit" asp-route-id="@product.Id">Edit</a> |
                        <a href="#" onclick="deleteProduct('@product.Id', antiForgeryToken())">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    
    <span id="spanError" class="text-danger"></span><br />
    
    <a asp-page="./Create">Add Product</a>
    
    @section Scripts
    {
        <script src="~/js/product.js" asp-append-version="true"></script>
        <script language="javascript">
            const antiForgeryToken = () => '@Model.AntiforgeryToken';
        </script>
    }
    
  3. Speichern Sie die Änderungen.

Im hervorgehobenen Code wird ein Anchor Tag Helper für Ankerelemente verwendet, durch das die Razor-Seite ContosoPets.Ui/Pages/Products/Create.cshtml, die sich im selben Verzeichnis wie die Indexseite befindet, für den Benutzer aufgerufen wird. Das Anchor Tag Helper für Ankerelemente erweitert das HTML-Standardankertag (<a ... ></a>) um neue Attribute. [asp-page-handler] ist beispielsweise für das Routing zu bestimmten Seitenhandlern verantwortlich. Ein weiteres Attribut, das hier verwendet wird, ist [asp-page], mit dem der Wert eines href-Attributs für ein Ankerelement auf eine bestimmte Seite festgelegt wird.

Im Folgenden lernen Sie den Lebenszyklus der HTTP-Anforderungslogik in der ProductService-Klasse des Projekts ContosoPets.UI kennen.