Überprüfung von ASP.NET Core Blazor-Formularen

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel wird erläutert, wie Sie die Überprüfung in Blazor-Formularen verwenden.

Formularprüfung

In grundlegenden Formularvalidierungsszenarien kann eine EditForm-Instanz deklarierte EditContext- und ValidationMessageStore-Instanzen verwenden, um Formularfelder zu überprüfen. Ein Handler für das OnValidationRequested-Ereignis von EditContext führt eine benutzerdefinierte Validierungslogik aus. Das Ergebnis des Handlers aktualisiert die ValidationMessageStore-Instanz.

Die grundlegende Formularvalidierung ist in Fällen nützlich, in denen das Modell des Formulars in der Komponente definiert ist, die das Formular hostet, entweder als Member direkt in der Komponente oder in einer Unterklasse. Die Verwendung einer Validierungskomponente wird empfohlen, wenn eine unabhängige Modellklasse für mehrere Komponenten verwendet wird.

In der folgenden Komponente löscht die HandleValidationRequested-Handlermethode alle vorhandenen Validierungsnachrichten, indem sie vor dem Überprüfen des Formulars ValidationMessageStore.Clear aufruft.

Starship8.razor:

@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}

Validierungssteuerelementkomponente für Datenanmerkungen und benutzerdefinierte Validierung

Die DataAnnotationsValidator-Komponente fügt einem kaskadierten EditContext Validierung durch Datenanmerkungen hinzu. Zum Aktivieren der Validierung durch Datenanmerkungen ist die DataAnnotationsValidator-Komponente erforderlich. Wenn Sie ein anderes Validierungssystem als Datenanmerkungen verwenden möchten, verwenden Sie eine benutzerdefinierte Implementierung anstelle der DataAnnotationsValidator-Komponente. Die Frameworkimplementierungen für DataAnnotationsValidator können Sie sich in der Verweisquelle ansehen:

Hinweis

Dokumentationslinks zur .NET-Referenzquelle laden in der Regel den Standardbranch des Repositorys, der die aktuelle Entwicklung für das nächste Release von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus. Weitere Informationen finden Sie unter How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Auswählen eines Versionstags von ASP.NET Core-Quellcode (dotnet/AspNetCore.Docs #26205)).

Blazor führt zwei Validierungstypen aus:

  • Die Feldvalidierung wird ausgeführt, wenn der Benutzer auf eine Stelle außerhalb des Felds tippt. Während der Feldvalidierung ordnet die DataAnnotationsValidator-Komponente alle gemeldeten Validierungsergebnisse dem Feld zu.
  • Modellvalidierung wird ausgeführt, wenn der Benutzer ein Formular sendet. Während der Modellvalidierung versucht die DataAnnotationsValidator-Komponente, das Feld basierend auf dem Namen des Members zu ermitteln, das vom Validierungsergebnis gemeldet wird. Validierungsergebnisse, die keinem einzelnen Member zugeordnet werden, werden dem Modell und keinem Feld zugeordnet.

Validierungssteuerelementkomponente

Validierungssteuerelementkomponenten unterstützen Formularvalidierung durch die Verwaltung eines ValidationMessageStore für den EditContext eines Formulars.

Das Blazor-Framework stellt die DataAnnotationsValidator-Komponente zum Anfügen von Validierungsunterstützung zu Formularen auf der Grundlage von Validierungsattributen (Datenanmerkungen) zur Verfügung. Sie können Komponenten für benutzerdefinierte Validierungssteuerelemente erstellen, um Validierungsmeldungen für verschiedene Formulare auf derselben Seite oder im selben Formular in verschiedenen Schritten der Formularverarbeitung zu verarbeiten, z. B. Clientvalidierung gefolgt von Servervalidierung. Das in diesem Abschnitt gezeigte Beispiel für eine Validierungssteuerelementkomponente (CustomValidation) wird in den folgenden Abschnitten dieses Artikels verwendet:

Hinweis

In vielen Fällen können benutzerdefinierte Datenanmerkungs-Validierungsattribute anstelle von benutzerdefinierten Validierungssteuerelementkomponenten verwendet werden. Benutzerdefinierte Attribute, die auf das Modell des Formulars angewendet werden, werden mit der Verwendung der DataAnnotationsValidator-Komponente aktiviert. Bei Verwendung mit Servervalidierung müssen alle benutzerdefinierten Attribute, die auf das Modell angewendet werden, auf dem Server ausführbar sein. Weitere Informationen finden Sie unter Modellvalidierung in ASP.NET Core-MVC.

Erstellen Sie eine Validierungssteuerelementkomponente aus ComponentBase:

  • Der EditContext des Formulars ist ein kaskadierenden Parameter der Komponente.
  • Wenn die Validierungssteuerelementkomponente initialisiert wird, wird ein neuer ValidationMessageStore erstellt, um eine aktuelle Liste von Formularfehlern zu verwalten.
  • Der Nachrichtenspeicher empfängt Fehler, wenn Entwicklercode in der Komponente des Formulars die DisplayErrors-Methode aufruft. Die Fehler werden in einem Dictionary<string, List<string>>-Element an die DisplayErrors-Methode übergeben. Im Wörterbuch ist der Schlüssel der Name des Formularfelds, das mindestens einen Fehler aufweist. Der Wert ist die Fehlerliste.
  • Meldungen werden gelöscht, wenn eines der folgenden Ereignisse aufgetreten ist:
    • Validierung wird für den EditContext angefordert, wenn das OnValidationRequested-Ereignis ausgelöst wird. Alle Fehler werden gelöscht.
    • Ein Feld ändert sich im Formular, wenn das OnFieldChanged-Ereignis ausgelöst wird. Nur die Fehler für das Feld werden gelöscht.
    • Die ClearErrors-Methode wird vom Entwicklercode aufgerufen. Alle Fehler werden gelöscht.

Aktualisieren Sie den Namespace in der folgenden Klasse so, dass er dem Namespace Ihrer App entspricht.

CustomValidation.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

public class CustomValidation : ComponentBase
{
    private ValidationMessageStore? messageStore;

    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }

    protected override void OnInitialized()
    {
        if (CurrentEditContext is null)
        {
            throw new InvalidOperationException(
                $"{nameof(CustomValidation)} requires a cascading " +
                $"parameter of type {nameof(EditContext)}. " +
                $"For example, you can use {nameof(CustomValidation)} " +
                $"inside an {nameof(EditForm)}.");
        }

        messageStore = new(CurrentEditContext);

        CurrentEditContext.OnValidationRequested += (s, e) => 
            messageStore?.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore?.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        if (CurrentEditContext is not null)
        {
            foreach (var err in errors)
            {
                messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        CurrentEditContext?.NotifyValidationStateChanged();
    }
}

Wichtig

Beim Ableiten von ComponentBase ist die Angabe eines Namespace erforderlich. Wenn kein Namespace angegeben wird, führt dies zu einem Buildfehler:

Tag-Hilfsprogramme können den Tagnamen „<Globaler Namespace>“ {KLASSENNAME} nicht als Ziel festlegen, da er ein „ “-Zeichen enthält.

Der Platzhalter {CLASS NAME} ist der Name der Komponentenklasse. Im Beispiel für ein benutzerdefiniertes Validierungssteuerelement in diesem Abschnitt wird der Beispielnamespace BlazorSample angegeben.

Hinweis

Im obigen Beispiel sind anonyme Lambdaausdrücke registrierte Ereignishandler für OnValidationRequested und OnFieldChanged. In diesem Szenario muss IDisposable nicht implementiert und das Abonnement der Ereignisdelegate nicht beendet werden. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Geschäftslogikvaliderung mit einer Validierungskomponente

Verwenden Sie zur allgemeinen Validierung der Geschäftslogik eine Validierungssteuerelementkomponente, die Formularfehler in einem Wörterbuch empfängt.

Die grundlegende Validierung ist in Fällen nützlich, in denen das Modell des Formulars in der Komponente definiert ist, die das Formular hostet, entweder als Member direkt in der Komponente oder in einer Unterklasse. Die Verwendung einer Validierungskomponente wird empfohlen, wenn eine unabhängige Modellklasse für mehrere Komponenten verwendet wird.

Im folgenden Beispiel:

  • Es wird eine verkürzte Version des Starfleet Starship Database-Formulars (Starship3-Komponente) aus dem Abschnitt Beispielformular im Artikel Eingabekomponenten verwendet, die nur die Klassifizierung und Beschreibung des Raumfahrzeugs akzeptiert. Die Validierung durch Datenanmerkungen wird bei der Formularübermittlung nicht ausgelöst, weil die DataAnnotationsValidator-Komponente nicht im Formular enthalten ist.
  • Die CustomValidation-Komponente aus dem Abschnitt Validierungssteuerelementkomponenten dieses Artikels wird verwendet.
  • Die Validierung erfordert einen Wert für die Beschreibung des Schiffs (Description), wenn der Benutzer die Schiffsklassifizierung (Classification) „Defense“ auswählt.

Wenn Validierungsmeldungen in der Komponente festgelegt werden, werden sie dem ValidationMessageStore des Validierungssteuerelements hinzugefügt und in der Validierungszusammenfassung von EditForm angezeigt.

Starship9.razor:

@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}
@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}

Hinweis

Als Alternative zur Verwendung von Validierungkomponenten können Datenanmerkungs-Validierungsattribute verwendet werden. Benutzerdefinierte Attribute, die auf das Modell des Formulars angewendet werden, werden mit der Verwendung der DataAnnotationsValidator-Komponente aktiviert. Bei Verwendung mit Servervalidierung müssen die Attribute auf dem Server ausführbar sein. Weitere Informationen finden Sie unter Modellvalidierung in ASP.NET Core-MVC.

Servervaliderung mit einer Validierungskomponente

In diesem Abschnitt geht es primär um Blazor-Web-App-Szenarien, aber der gleiche allgemeine Ansatz wird für jeden App-Typ verwendet, der eine Servervalidierung mit der Web-API nutzt.

In diesem Abschnitt geht es primär um gehostete Blazor WebAssembly-Szenarien, aber der gleiche allgemeine Ansatz wird für jede Art von App verwendet, die eine Servervalidierung mit der Web-API nutzt.

Die Servervalidierung wird zusätzlich zur Clientvalidierung unterstützt:

  • Verarbeiten Sie die Clientvalidierung im Formular mit der DataAnnotationsValidator-Komponente.
  • Wenn das Formular die Clientvalidierung besteht (OnValidSubmit wird aufgerufen), senden Sie EditContext.Model zur Formularverarbeitung an eine Back-End-Server-API.
  • Verarbeiten Sie die Modellvalidierung auf dem Server.
  • Die Server-API umfasst sowohl die integrierte Framework-Datenanmerkungsvalidierung als auch benutzerdefinierte Validierungslogik, die vom Entwickler bereitgestellt wird. Wenn die Validierung auf dem Server bestanden wird, wird das Formular verarbeitet und ein Erfolgsstatuscode zurückgesendet (200 - OK). Wenn die Validierung fehlschlägt, werden ein Fehlerstatuscode (400 - Bad Request) und die Feldvalidierungsfehler zurückgegeben.
  • Deaktivieren Sie entweder das Formular bei Erfolg, oder zeigen Sie die Fehler an.

Die grundlegende Validierung ist in Fällen nützlich, in denen das Modell des Formulars in der Komponente definiert ist, die das Formular hostet, entweder als Member direkt in der Komponente oder in einer Unterklasse. Die Verwendung einer Validierungskomponente wird empfohlen, wenn eine unabhängige Modellklasse für mehrere Komponenten verwendet wird.

Das folgende Beispiel beruht auf Folgendem:

Platzieren Sie das Starship-Modell (Starship.cs) in einem freigegebenen Klassenbibliotheksprojekt, damit sowohl Client- als auch Serverprojekte das Modell verwenden können. Fügen Sie den Namespace hinzu, oder aktualisieren Sie ihn so, dass er mit dem Namespace der freigegebenen App (z. B. namespace BlazorSample.Shared) übereinstimmt. Da für das Modell Datenanmerkungen erforderlich sind, vergewissern Sie sich, dass die freigegebene Klassenbibliothek das freigegebene Framework verwendet, oder fügen Sie das System.ComponentModel.Annotations-Paket dem freigegebenen Projekt hinzu.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

Fügen Sie im Hauptprojekt der Blazor-Web-App einen Controller hinzu, um die Anforderungen für die Überprüfung des Raumfahrzeugs zu verarbeiten und bei Überprüfungsfehlern Meldungen zurückzugeben. Aktualisieren Sie die Namespaces in der letzten using-Anweisung für das freigegebene Klassenbibliotheksprojekt sowie den namespace für die Controllerklasse. Zusätzlich zur Client- und Servervalidierung durch Datenanmerkungen überprüft der Controller, ob ein Wert für die Beschreibung des Schiffs (Description) angegeben ist, wenn der oder die Benutzer*in die Schiffsklassifizierung Defense (Classification) auswählt.

Platzieren Sie das Starship-Modell (Starship.cs) im Shared -Projekt der Lösung, sodass sowohl die Client- als auch die Server-App das Modell verwenden kann. Fügen Sie den Namespace hinzu, oder aktualisieren Sie ihn so, dass er mit dem Namespace der freigegebenen App (z. B. namespace BlazorSample.Shared) übereinstimmt. Fügen Sie das Paket System.ComponentModel.Annotations dem Projekt Shared hinzu, da das Modell Datenanmerkungen erfordert.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

Fügen Sie im Server -Projekt einen Controller hinzu, um die Anforderungen für die Validierung von „Starship“ zu verarbeiten und bei Validierungsfehlern Meldungen zurückzugeben. Aktualisieren Sie die Namespaces in der letzten using-Anweisung für das Shared -Projekt sowie den namespace für die Controllerklasse. Zusätzlich zur Client- und Servervalidierung durch Datenanmerkungen überprüft der Controller, ob ein Wert für die Beschreibung des Schiffs (Description) angegeben ist, wenn der oder die Benutzer*in die Schiffsklassifizierung Defense (Classification) auswählt.

Die Validierung für die Schiffsklassifizierung Defense erfolgt nur serverseitig im Controller, da das anstehende Formular clientseitig nicht dieselbe Validierung ausführt, wenn das Formular an den Server übermittelt wird. Die Serverüberprüfung ohne Clientüberprüfung ist in Apps üblich, die eine private Überprüfung der Geschäftslogik von Benutzereingaben auf dem Server erfordern. Beispielsweise können private Informationen aus Daten, die für einen Benutzer gespeichert sind, erforderlich sein, um Benutzereingaben zu überprüfen. Private Daten können offensichtlich nicht zur Clientvalidierung an den Client gesendet werden.

Hinweis

Der StarshipValidation-Controller in diesem Abschnitt verwendet Microsoft Identity 2.0. Die Web-API akzeptiert nur Token für Benutzer, die den Bereich API.Access für diese API besitzen. Eine zusätzliche Anpassung ist erforderlich, wenn sich der Bereichsname der API von API.Access unterscheidet.

Weitere Informationen zur Sicherheit finden Sie unter:

Controllers/StarshipValidation.cs:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
    private readonly ILogger<StarshipValidationController> logger;

    public StarshipValidationController(
        ILogger<StarshipValidationController> logger)
    {
        this.logger = logger;
    }

    static readonly string[] scopeRequiredByApi = new[] { "API.Access" };

    [HttpPost]
    public async Task<IActionResult> Post(Starship model)
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        try
        {
            if (model.Classification == "Defense" && 
                string.IsNullOrEmpty(model.Description))
            {
                ModelState.AddModelError(nameof(model.Description),
                    "For a 'Defense' ship " +
                    "classification, 'Description' is required.");
            }
            else
            {
                logger.LogInformation("Processing the form asynchronously");

                // async ...

                return Ok(ModelState);
            }
        }
        catch (Exception ex)
        {
            logger.LogError("Validation Error: {Message}", ex.Message);
        }

        return BadRequest(ModelState);
    }
}

Überprüfen Sie, ob der Namespace des vorherigen Controllers (BlazorSample.Server.Controllers) mit dem Namespace des Controllers der App übereinstimmt, oder aktualisieren Sie ihn.

Wenn ein Validierungsfehler bei der Modellbindung auf dem Server auftritt, gibt ein ApiController (ApiControllerAttribute) normalerweise eine Standardantwort „Ungültige Anforderung“ mit ValidationProblemDetails zurück. Die Antwort enthält mehr Daten als nur die Validierungsfehler (wie im folgenden Beispiel gezeigt), wenn alle Felder des Starfleet Starship Database-Formulars nicht übermittelt wurden und die Validierung des Formulars fehlschlägt:

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Id": ["The Id field is required."],
    "Classification": ["The Classification field is required."],
    "IsValidatedDesign": ["This form disallows unapproved ships."],
    "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
  }
}

Hinweis

Um die vorangehende JSON-Antwort zu veranschaulichen, müssen Sie entweder die Clientvalidierung des Formulars deaktivieren, um die Übermittlung leerer Feldformulare zu ermöglichen, oder ein Tool verwenden, um eine Anforderung direkt an die Server-API zu senden, z. B. Firefox Browser Developer.

Wenn die Server-API die vorherige JSON-Standardantwort zurückgibt, ist es möglich, dass der Client die Antwort im Entwicklercode analysiert, um die untergeordneten Elemente des errors-Knotens für die Verarbeitung von Formularvalidierungsfehlern abzurufen. Es ist unpraktisch, Entwicklercode zu schreiben, um die Datei zu analysieren. Die manuelle Analyse des JSON-Codes erfordert, dass nach dem Aufruf von ReadFromJsonAsync ein Dictionary<string, List<string>> der Fehler erzeugt wird. Im Idealfall sollte die Server-API nur die Überprüfungsfehler zurückgeben, wie im folgenden Beispiel gezeigt:

{
  "Id": ["The Id field is required."],
  "Classification": ["The Classification field is required."],
  "IsValidatedDesign": ["This form disallows unapproved ships."],
  "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}

Um die Antwort der Server-API so zu ändern, dass sie nur die Validierungsfehler zurückgibt, ändern Sie den Delegaten, der bei Aktionen aufgerufen wird, die mit ApiControllerAttribute in der Datei Program annotiert sind. Geben Sie für den API-Endpunkt (/StarshipValidation) ein BadRequestObjectResult mit ModelStateDictionary zurück. Behalten Sie für alle anderen API-Endpunkte das Standardverhalten bei, indem Sie das Objektergebnis mit neuen ValidationProblemDetails zurückgeben.

Fügen Sie den Microsoft.AspNetCore.Mvc-Namespace am Anfang der Datei Program im Hauptprojekt der Blazor-Web-App hinzu:

using Microsoft.AspNetCore.Mvc;

Fügen Sie in der Datei Program die folgende AddControllersWithViews-Erweiterungsmethode hinzu, oder aktualisieren Sie sie, und fügen Sie den folgenden Aufruf von ConfigureApiBehaviorOptions ein:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Wenn Sie dem Hauptprojekt der Blazor-Web-App zum ersten Mal Controller hinzufügen, ordnen Sie Controllerendpunkte zu, wenn Sie den vorherigen Code einfügen, der Dienste für Controller registriert. Im folgenden Beispiel werden Standardcontrollerrouten verwendet:

app.MapDefaultControllerRoute();

Hinweis

Im obigen Beispiel werden Controllerdienste explizit registriert, indem AddControllersWithViews aufgerufen wird, um automatisch XSRF/CSRF-Angriffe (Cross-Site Request Forgery, websiteübergreifende Anforderungsfälschung) abzumildern. Wenn Sie nur AddControllers verwenden, wird der Schutz vor Fälschung nicht automatisch aktiviert.

Weitere Informationen zur Reaktion auf Fehler beim Controllerrouting und bei Überprüfungen finden Sie in den folgenden Ressourcen:

Fügen Sie im .Client-Projekt die im Abschnitt Validierungssteuerelementkomponenten gezeigte CustomValidation-Komponenten hinzu. Aktualisieren Sie den Namespace so, dass er mit dem der App (z. B.namespace BlazorSample.Client) übereinstimmt.

Im .Client -Projekt wird das Starfleet Starship Database-Formular aktualisiert, um Servervalidierungsfehler mithilfe der CustomValidation-Komponente anzuzeigen. Wenn die Server-API Validierungsmeldungen zurückgibt, werden diese dem ValidationMessageStore der CustomValidation-Komponente hinzugefügt. Die Fehler sind im EditContext des Formulars zur Anzeige durch die Validierungszusammenfassung des Formulars verfügbar.

Ändern Sie in der folgenden Komponente den Namespace des freigegebenen Projekts (@using BlazorSample.Shared) in den Namespace des freigegebenen Projekts. Beachten Sie, dass das Formular eine Autorisierung erfordert, weshalb der Benutzer bei der App angemeldet sein muss, um zum Formular navigieren zu können.

Fügen Sie den Microsoft.AspNetCore.Mvc-Namespace am Anfang der Datei Program in der Server -App hinzu:

using Microsoft.AspNetCore.Mvc;

Suchen Sie in der Datei Program nach der Erweiterungsmethode AddControllersWithViews und fügen Sie den folgenden Aufruf von ConfigureApiBehaviorOptions hinzu:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Hinweis

Im obigen Beispiel werden Controllerdienste explizit registriert, indem AddControllersWithViews aufgerufen wird, um automatisch XSRF/CSRF-Angriffe (Cross-Site Request Forgery, websiteübergreifende Anforderungsfälschung) abzumildern. Wenn Sie nur AddControllers verwenden, wird der Schutz vor Fälschung nicht automatisch aktiviert.

Fügen Sie im Client-Projekt die im Abschnitt Validierungssteuerelementkomponenten gezeigte CustomValidation-Komponenten hinzu. Aktualisieren Sie den Namespace so, dass er mit dem der App (z. B.namespace BlazorSample.Client) übereinstimmt.

Im Client -Projekt wird das Starfleet Starship Database-Formular aktualisiert, um Servervalidierungsfehler mithilfe der CustomValidation-Komponente anzuzeigen. Wenn die Server-API Validierungsmeldungen zurückgibt, werden diese dem ValidationMessageStore der CustomValidation-Komponente hinzugefügt. Die Fehler sind im EditContext des Formulars zur Anzeige durch die Validierungszusammenfassung des Formulars verfügbar.

Aktualisieren Sie in der folgenden Komponente den Namespace des Shared-Projekts (@using BlazorSample.Shared) auf den Namespace des gemeinsam genutzten Projekts. Beachten Sie, dass das Formular eine Autorisierung erfordert, weshalb der Benutzer bei der App angemeldet sein muss, um zum Formular navigieren zu können.

Starship10.razor:

Hinweis

Standardmäßig aktivieren Formulare, die auf EditForm basieren, automatisch die Unterstützung für den Schutz vor Fälschung. Der Controller sollte zum Registrieren von Controllerdiensten AddControllersWithViews verwenden und die Unterstützung für den Schutz vor Fälschung für die Web-API automatisch aktivieren.

@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

Das .Client-Projekt einer Blazor-Web-App muss auch einen HttpClient für HTTP POST-Anforderungen an einen Back-End-Web-API-Controller registrieren. Überprüfen Sie Folgendes in der Datei Program des .Client-Projekts, oder fügen Sie es hinzu:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

Im vorherigen Beispiel wird die Basisadresse mit builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) festgelegt, wodurch die Basisadresse für die App abgerufen und in der Regel vom href-Wert des <base>-Tags auf der Hostseite abgeleitet wird.

@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

Hinweis

Als Alternative zur Verwendung einer Validierungssteuerelementkomponente können Datenanmerkungs-Validierungsattribute verwendet werden. Benutzerdefinierte Attribute, die auf das Modell des Formulars angewendet werden, werden mit der Verwendung der DataAnnotationsValidator-Komponente aktiviert. Bei Verwendung mit Servervalidierung müssen die Attribute auf dem Server ausführbar sein. Weitere Informationen finden Sie unter Modellvalidierung in ASP.NET Core-MVC.

Hinweis

Der Servervalidierungsansatz in diesem Abschnitt eignet sich für jedes der Beispiele für gehostete Blazor WebAssembly-Lösungen in dieser Dokumentation:

InputText basierend auf dem Eingabeereignis

Verwenden Sie die InputText-Komponente, um eine benutzerdefinierte Komponente zu erstellen, die das oninput-Ereignis (input) anstelle des onchange-Ereignisses (change) nutzt. Die Verwendung des input-Ereignisses löst die Feldvalidierung bei jeder Tastatureingabe aus.

Die folgende CustomInputText-Komponente erbt die InputText-Komponente des Frameworks und legt die Ereignisbindung auf das oninput-Ereignis (input) fest.

CustomInputText.razor:

@inherits InputText

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />

Die CustomInputText-Komponente kann überall dort verwendet werden, wo InputText verwendet wird. Die folgende Komponente verwendet die gemeinsam genutzte CustomInputText-Komponente.

Starship11.razor:

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <CustomInputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <CustomInputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

Komponenten der Validierungszusammenfassung und der Validierungsnachricht

Die ValidationSummary-Komponente fasst alle Validierungsnachrichten zusammen. Das ähnelt dem Taghilfsprogramm für Validierungszusammenfassungen:

<ValidationSummary />

Validierungsmeldungen für ein bestimmtes Modell werden mit dem Model-Parameter ausgegeben:

<ValidationSummary Model="Model" />

Die ValidationMessage<TValue>-Komponente zeigt Validierungsnachrichten für ein bestimmtes Feld an. Das ähnelt dem Taghilfsprogramm für Validierungsmeldungen. Das Validierungsfeld wird mit dem For-Attribut und einem Lambdaausdruck angegeben, der die Modelleigenschaft benennt:

<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />

Die ValidationMessage<TValue>- und ValidationSummary-Komponenten unterstützen arbiträre Attribute. Attribute, die nicht mit einem Komponentenparameter übereinstimmen, werden dem gerenderten <div>- oder <ul>-Element hinzugefügt.

Steuern Sie den Stil von Validierungsmeldungen im Stylesheet (wwwroot/css/app.css oder wwwroot/css/site.css) der App. Die validation-message-Standardklasse legt die Textfarbe von Validierungsmeldungen auf Rot fest:

.validation-message {
    color: red;
}

Ermitteln der Gültigkeit eines Formularfelds

Verwenden Sie EditContext.IsValid, um zu ermitteln, ob ein Feld gültig ist, ohne Validierungsmeldungen abzurufen.

wird unterstützt, aber nicht empfohlen:

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

empfohlen:

var isValid = editContext.IsValid(fieldIdentifier);

Benutzerdefinierte Validierungsattribute

Übergeben Sie die MemberName-Eigenschaft des Validierungskontexts bei der Erstellung der ValidationResult-Klasse, um sicherzustellen, dass ein Validierungsergebnis korrekt einem Feld zugeordnet wird, wenn ein benutzerdefiniertes Validierungsattribut verwendet wird.

CustomValidator.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
        ValidationContext validationContext)
    {
        ...

        return new ValidationResult("Validation message to user.",
            new[] { validationContext.MemberName });
    }
}

Fügen Sie über ValidationContext Dienste in benutzerdefinierte Validierungsattribute ein. Das folgende Beispiel demonstriert ein SaladChef-Formular, das Benutzereingaben mit Abhängigkeitsinjektion (Dependency Injection, DI) validiert.

Die SaladChef-Klasse gibt die genehmigte Starship-Zutatenliste für einen Ten Forward-Salat an.

SaladChef.cs:

namespace BlazorSample;

public class SaladChef
{
    public string[] SaladToppers = { "Horva", "Kanda Root",
    "Krintar", "Plomeek", "Syto Bean" };
}

Registrieren Sie SaladChef im DI-Container der App in der Datei Program:

builder.Services.AddTransient<SaladChef>();

Die IsValid-Methode der folgenden SaladChefValidatorAttribute-Klasse ruft den Dienst SaladChef aus DI ab, um die Eingaben des Benutzers zu prüfen.

SaladChefValidatorAttribute.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();

        if (saladChef.SaladToppers.Contains(value?.ToString()))
        {
            return ValidationResult.Success;
        }

        return new ValidationResult("Is that a Vulcan salad topper?! " +
            "The following toppers are available for a Ten Forward salad: " +
            string.Join(", ", saladChef.SaladToppers));
    }
}

Die folgende Komponente validiert Benutzereingaben, indem sie das SaladChefValidatorAttribute ([SaladChefValidator]) auf die Zeichenfolge für Salatzutaten (SaladIngredient) anwendet.

Starship12.razor:

@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">
    <DataAnnotationsValidator />
    <div>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() =>
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off">
    <DataAnnotationsValidator />
    <p>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </p>
    <button type="submit">Submit</button>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() => 
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}

CSS-Klassenattribute für die benutzerdefinierte Validierung

CSS-Klassenattribute für die benutzerdefinierte Validierung sind nützlich, wenn eine Integration in CSS-Frameworks wie Bootstrap erfolgt.

Um CSS-Klassenattribute für die benutzerdefinierte Validierung anzugeben, geben Sie zunächst CSS-Stile für die benutzerdefinierte Validierung an. Im folgenden Beispiel werden Stile für „gültig“ (validField) und für „ungültig“ (invalidField) angegeben.

Fügen Sie die folgenden CSS-Klassen zur Formatvorlage der App hinzu:

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

Erstellen Sie eine von FieldCssClassProvider abgeleitete Klasse, die auf Feldvalidierungsmeldungen prüft und den entsprechenden Stil für „gültig“ oder „ungültig“ anwendet.

CustomFieldClassProvider.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        return isValid ? "validField" : "invalidField";
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "validField" : "invalidField";
    }
}

Legen Sie die CustomFieldClassProvider-Klasse als CSS-Klassenanbieter für Felder auf der EditContext-Instanz des Formulars mit SetFieldCssClassProvider fest.

Starship13.razor:

@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

Im vorherigen Beispiel wird die Gültigkeit aller Formularfelder überprüft und auf jedes Feld ein Stil angewendet. Wenn das Formular benutzerdefinierte Stile nur auf eine Teilmenge der Felder anwenden soll, lassen Sie CustomFieldClassProvider Stile bedingt anwenden. Im folgenden CustomFieldClassProvider2-Beispiel wird ein Stil nur auf das Feld Name angewendet. Für Felder, deren Namen nicht Name entsprechen, wird string.Empty zurückgegeben, und es wird kein Stil angewendet. Mithilfe der Reflexion wird das Feld mit der Eigenschaft oder dem Feldnamen des Modellelements abgeglichen, und nicht mit einer der HTML-Entität zugewiesenen id.

CustomFieldClassProvider2.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = editContext.IsValid(fieldIdentifier);

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}

Hinweis

Beim Abgleich des Feldnamens im vorherigen Beispiel wird die Groß-/Kleinschreibung beachtet, sodass ein Modelleigenschaftenmember, der als „Name“ festgelegt ist, einer bedingten Überprüfung von „Name“ entsprechen muss:

  • Stimmt ordnungsgemäß überein:fieldId.FieldName == "Name"
  • Stimmt nicht überein:fieldId.FieldName == "name"
  • Stimmt nicht überein:fieldId.FieldName == "NAME"
  • Stimmt nicht überein:fieldId.FieldName == "nAmE"

Fügen Sie Model eine zusätzliche Eigenschaft hinzu, z. B.:

[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; } 

Fügen Sie dem Formular der CustomValidationForm-Komponente die Description hinzu:

<InputText @bind-Value="Model!.Description" />

Aktualisieren Sie die EditContext-Instanz in der OnInitialized-Methode der Komponente, um den neuen CSS-Klassenanbieter für Felder zu verwenden:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Da eine CSS-Validierungsklasse nicht auf das Feld Description angewendet wird, wird sie nicht formatiert. Die Feldvalidierung wird jedoch normal ausgeführt. Wenn mehr als 10 Zeichen angegeben werden, gibt die Validierungszusammenfassung den Fehler an:

Die Beschreibung ist zu lang.

Im folgenden Beispiel:

  • Der benutzerdefinierte CSS-Stil wird auf das Name-Feld angewendet.

  • Alle anderen Felder wenden Logik ähnlich der Standardlogik von Blazor an und verwenden die CSS-Validierungsstile modified mit valid oder invalid des Standardfelds von Blazor. Beachten Sie, dass Sie für die Standardstile diese nicht dem Stylesheet der App hinzufügen müssen, wenn die App auf einer Blazor-Projektvorlage basiert. Für Apps, die nicht auf einer Blazor-Projektvorlage basieren, können die Standardstile dem Stylesheet der App hinzugefügt werden:

    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }
    
    .invalid {
        outline: 1px solid red;
    }
    

CustomFieldClassProvider3.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}

Aktualisieren Sie die EditContext-Instanz in der OnInitialized-Methode der Komponente, um den vorherigen CSS-Klassenanbieter für Felder zu verwenden:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Verwenden von CustomFieldClassProvider3:

  • Das Name-Feld verwendet die benutzerdefinierten CSS-Validierungsstile der App.
  • Das Description-Feld verwendet Logik ähnlich der Logik von Blazor und die CSS-Validierungsstile des Standardfelds von Blazor.

Validierungspakete für Datenanmerkungen in Blazor

Microsoft.AspNetCore.Components.DataAnnotations.Validation ist ein Paket, das Lücken bei der Validierung mithilfe der DataAnnotationsValidator-Komponente schließt. Das Paket ist aktuell experimentell.

Warnung

Das Paket Microsoft.AspNetCore.Components.DataAnnotations.Validation enthält die neueste Version des Release Candidate auf NuGet.org. Verwenden Sie bis auf Weiteres das experimentelle Release Candidate-Paket. Experimentelle Features werden für die Untersuchung der Funktionsfähigkeit von Features bereitgestellt und dürfen nicht in einer stabilen Version enthalten sein. Weitere Aktualisierungen finden Sie im GitHub-Repository „Ankündigungen“, im dotnet/aspnetcoreRepository von GitHub oder in diesem Themenabschnitt.

[CompareProperty]-Attribut

CompareAttribute funktioniert nicht gut mit der DataAnnotationsValidator-Komponente, da hier das Validierungsergebnis nicht einem bestimmten Member zugeordnet wird. Das führt zu einem nicht konsistenten Verhalten zwischen Validierung auf Feldebene und der Validierung des gesamten Modells bei Sendevorgängen. Mit dem experimentellenMicrosoft.AspNetCore.Components.DataAnnotations.Validation-Paket wird das zusätzliche Validierungsattribut (ComparePropertyAttribute) eingeführt, das diese Einschränkungen umgeht. In einer Blazor-App ist [CompareProperty] ein direkter Ersatz für das Attribut [Compare].

Verschachtelte Modelle, Sammlungstypen und komplexe Typen

Blazor bietet Unterstützung für die Validierung von Formulareingaben mithilfe von Datenanmerkungen mit dem integrierten DataAnnotationsValidator. DataAnnotationsValidator validiert jedoch nur Eigenschaften des Modells auf oberster Ebene, die an das Formular gebunden sind, bei denen es sich aber um keine Sammlungs- oder komplexen Eigenschaften handelt.

Verwenden Sie den ObjectGraphDataAnnotationsValidator, der im experimentellenMicrosoft.AspNetCore.Components.DataAnnotations.Validation-Paket bereitgestellt wird, um den gesamten Objektgraph des gebundenen Modells zu überprüfen, einschließlich der Sammlungseigenschaften und komplexen Eigenschaften:

<EditForm ...>
    <ObjectGraphDataAnnotationsValidator />
    ...
</EditForm>

Fügen Sie Modelleigenschaften Anmerkungen mit [ValidateComplexType] hinzu. In den folgenden Modellklassen enthält die ShipDescription-Klasse zusätzliche Datenanmerkungen für die Validierung, wenn das Modell an das Formular gebunden ist:

Starship.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    ...

    [ValidateComplexType]
    public ShipDescription ShipDescription { get; set; } = new();

    ...
}

ShipDescription.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription
{
    [Required]
    [StringLength(40, ErrorMessage = "Description too long (40 char).")]
    public string? ShortDescription { get; set; }

    [Required]
    [StringLength(240, ErrorMessage = "Description too long (240 char).")]
    public string? LongDescription { get; set; }
}

Aktivieren der Schaltfläche zum Senden basierend auf der Formularvalidierung

Um die Schaltfläche zum Senden basierend auf der Formularvalidierung zu aktivieren oder deaktivieren, geht das folgende Beispiel so vor:

  • Verwendet eine gekürzte Version des obigen Starfleet Starship Database-Formulars (Starship3-Komponente) aus dem Abschnitt Beispielformular im Artikel Eingabekomponenten, der nur einen Wert für die ID des Raumfahrzeugs akzeptiert. Die anderen Starship-Eigenschaften erhalten gültige Standardwerte, wenn eine Instanz des Starship-Typs erstellt wird.
  • Es verwendet den EditContext des Formulars, um das Modell zuzuweisen, wenn die Komponente initialisiert wird.
  • Es validiert das Formular im OnFieldChanged-Rückruf des Kontexts, um die Schaltfläche zum Senden zu aktivieren oder zu deaktivieren.
  • Es implementiert IDisposable, und beenden das Abonnement des Ereignishandlers in der Dispose-Methode. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Hinweis

Weisen Sie bei der Zuweisung zu EditForm.EditContext nicht auch EditForm.Model dem EditForm zu.

Starship14.razor:

@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
            {
                Id = "NCC-1701",
                Classification = "Exploration",
                MaximumAccommodation = 150,
                IsValidatedDesign = true,
                ProductionDate = new DateTime(2245, 4, 11)
            };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}

Wenn ein Formular nicht mit gültigen Werten vorab geladen wird und Sie die Schaltfläche Submit beim Laden des Formulars deaktivieren möchten, legen Sie formInvalid auf true fest.

Eine Nebenwirkung des vorangehenden Ansatzes ist, dass eine Validierungszusammenfassung (ValidationSummary-Komponente) mit ungültigen Feldern aufgefüllt wird, nachdem der Benutzer mit einem beliebigen Feld interagiert hat. Behandeln Sie dieses Szenario auf eine der folgenden Arten:

  • Verwenden Sie keine ValidationSummary-Komponente des Formulars.
  • Lassen Sie die ValidationSummary-Komponente einblenden, wenn auf die Schaltfläche zum Senden geklickt wird, z. B. in einer Submit-Methode.
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
    <DataAnnotationsValidator />
    <ValidationSummary style="@displaySummary" />

    ...

    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    private string displaySummary = "display:none";

    ...

    private void Submit()
    {
        displaySummary = "display:block";
    }
}