Blazor ASP.NET Core erweiterte Formularüberprüfung

Note

Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

Warning

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

In diesem Artikel werden wiederverwendbare Validatorkomponenten und Remoteüberprüfungen veranschaulicht. Informationen zu allgemeinen Formularüberprüfungs-APIs, einschließlich Datenanmerkungen, direkter EditContext Überprüfung, Meldungsanzeige, Formatieren, Status und Sendeverhalten, finden Sie unter ASP.NET Core Blazor Formularüberprüfung.

Modellbasierte Gültigkeitsprüfungsregeln, die von Blazor und minimalen APIs gemeinsam genutzt werden, finden Sie unter "Überprüfung" in ASP.NET Core.

Informationen zur Browserüberprüfung im statischen serverseitigen Rendering (statischer SSR) finden Sie unter ASP.NET Core Blazor clientseitige Formularüberprüfung in statischem SSR.

Erstellen einer Validatorkomponente

Eine Validator-Komponente kapselt die Validierung, die das EditContext und ValidationMessageStore eines Formulars verwendet. Dies ist nützlich, wenn dasselbe Überprüfungsverhalten von mehreren Formularen verwendet wird oder wenn Fehler von einem Dienst und nicht von Überprüfungsattributen im Modell stammen.

Die Komponente:

  • Empfängt die Form EditContext als kaskadierenden Parameter.
  • Erstellt einen Nachrichtenspeicher für seine Fehler.
  • Löscht veraltete Formularfehler, wenn die Überprüfung angefordert wird.
  • Löscht veraltete Fehler eines Felds, wenn sich das Feld ändert.
  • Macht Methoden zum Anzeigen und Löschen von Fehlern verfügbar.
  • Meldet seine Ereignishandler beim Verwerfen ab.

CustomValidation.razor:

@implements IDisposable

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

    private ValidationMessageStore messages = default!;

    protected override void OnInitialized()
    {
        if (CurrentEditContext is null)
        {
            throw new InvalidOperationException(
                "CustomValidation requires a cascading EditContext.");
        }

        messages = new ValidationMessageStore(CurrentEditContext);
        CurrentEditContext.OnValidationRequested +=
            HandleValidationRequested;
        CurrentEditContext.OnFieldChanged += HandleFieldChanged;
    }

    public void DisplayErrors(IDictionary<string, string[]> errors)
    {
        foreach (var error in errors)
        {
            messages.Add(
                CurrentEditContext!.Field(error.Key),
                error.Value);
        }

        CurrentEditContext!.NotifyValidationStateChanged();
    }

    public void ClearErrors()
    {
        messages.Clear();
        CurrentEditContext!.NotifyValidationStateChanged();
    }

    private void HandleValidationRequested(
        object? sender, ValidationRequestedEventArgs e) =>
        ClearErrors();

    private void HandleFieldChanged(
        object? sender, FieldChangedEventArgs e)
    {
        messages.Clear(e.FieldIdentifier);
        CurrentEditContext!.NotifyValidationStateChanged();
    }

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

Platzieren Sie die Komponente innerhalb einer EditForm Komponente, und erfassen Sie einen Komponentenverweis, wenn das Formular oder ein Dienst Fehler anzeigen soll:

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    ...
</EditForm>

@code {
    private CustomValidation? customValidation;
}

Die Komponente kann zusammen mit DataAnnotationsValidator verwendet werden. Jeder Validator verfügt über einen eigenen Nachrichtenspeicher, der demselben EditContextElement zugeordnet ist, und ValidationMessageValidationSummary Meldungen von beiden Validatoren anzeigen.

Um eine Geschäftsregel innerhalb der Validator-Komponente zu implementieren, anstatt externe Fehler zu akzeptieren, führen Sie die Regel von HandleValidationRequested oder HandleFieldChanged aus und fügen Sie deren Meldungen zu messages hinzu. Ein kleineres Beispiel, das dies direkt in einer Formularkomponente ausführt, finden Sie unter ASP.NET Core Blazor Formularüberprüfung.

Asynchrone Überprüfung hinzufügen

Dasselbe Komponentenmuster unterstützt asynchrone Arbeit:

  • Rufen Sie in einem OnValidationRequested-Handler e.AddAsyncValidator auf, um die Arbeit auf Formularebene zu registrieren. EditForm wartet, bevor OnInvalidSubmit oder OnValidSubmit aufgerufen wird.
  • Rufen Sie EditContext.RegisterAsyncFieldValidator in einem OnFieldChanged Handler auf, um die Überprüfung für dieses Feld zu starten. Starten einer weiteren Überprüfung für dasselbe Feld ersetzt und bricht den vorherigen Vorgang ab.

Für asynchrone Überprüfung auf Formularebene:

private void HandleValidationRequested(
    object? sender, ValidationRequestedEventArgs e) =>
    e.AddAsyncValidator(ValidateAsync);

private async Task ValidateAsync(CancellationToken cancellationToken)
{
    var field = CurrentEditContext!.Field(nameof(Model.Username));
    messages.Clear(field);

    var available = await Http.GetFromJsonAsync<bool>(
        $"api/usernames/available?value={Uri.EscapeDataString(Model.Username)}",
        cancellationToken);

    if (!available)
    {
        messages.Add(field, "The username is already taken.");
    }

    CurrentEditContext.NotifyValidationStateChanged();
}

Für asynchrone Überprüfung auf Feldebene:

private void HandleFieldChanged(
    object? sender, FieldChangedEventArgs e)
{
    CurrentEditContext!.RegisterAsyncFieldValidator(
        e.FieldIdentifier,
        token => ValidateFieldAsync(e.FieldIdentifier, token));
}

Übergeben Sie das bereitgestellte Abbruchtoken an E/A. Löschen Sie vorherige Nachrichten vor dem Starten des Vorgangs, vermeiden Sie die Veröffentlichung teilweiser Ergebnisse nach einer Ausnahme, und rufen Sie nach dem Aktualisieren von Nachrichten auf NotifyValidationStateChanged .

Ein Vorgang, der abgebrochen wurde, weil er ersetzt wurde oder weil der Validierungsdurchlauf abgebrochen wurde, wird verworfen. Andere Ausnahmen platzieren das Feld oder Formular im fehlerhaften Zustand. Informationen zum Anzeigen des ausstehenden und fehlerhaften Zustands finden Sie unter ASP.NET Core Blazor Formularüberprüfung.

Eine vollständige Komponente, die die asynchrone Überprüfung auf Formularebene und pro Feld kombiniert, finden Sie im folgenden Beispiel:

@implements IDisposable
@inject HttpClient Http

@* A validator component that runs asynchronous validation, both when the whole form is
   validated on submit and per field as the user edits the Username field. *@

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

    [Parameter, EditorRequired]
    public RegistrationModel Model { get; set; } = default!;

    private ValidationMessageStore? messages;

    protected override void OnInitialized()
    {
        ArgumentNullException.ThrowIfNull(CurrentEditContext);
        messages = new ValidationMessageStore(CurrentEditContext);
        CurrentEditContext.OnValidationRequested += OnValidationRequested;
        CurrentEditContext.OnFieldChanged += OnFieldChanged;
    }

    // Registers asynchronous work for the whole form. EditForm awaits it before invoking
    // OnValidSubmit.
    private void OnValidationRequested(
        object? sender, ValidationRequestedEventArgs e) =>
        e.AddAsyncValidator(ValidateUsernameAsync);

    // Registers asynchronous work for a single field. A new registration supersedes and
    // cancels any validation already in flight for the field.
    private void OnFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (e.FieldIdentifier.FieldName != nameof(RegistrationModel.Username))
        {
            return;
        }

        CurrentEditContext!.RegisterAsyncFieldValidator(
            e.FieldIdentifier,
            token => CheckAsync(e.FieldIdentifier, token));
    }

    private Task ValidateUsernameAsync(CancellationToken token) =>
        CheckAsync(CurrentEditContext!.Field(nameof(Model.Username)), token);

    private async Task CheckAsync(FieldIdentifier field, CancellationToken token)
    {
        messages!.Clear(field);

        var available = await Http.GetFromJsonAsync<bool>(
            $"api/usernames/available?value={Uri.EscapeDataString(Model.Username)}",
            token);

        if (!available)
        {
            messages.Add(field, "The username is already taken.");
        }

        CurrentEditContext!.NotifyValidationStateChanged();
    }

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

Informationen zu asynchronen Überprüfungsattributen für das Modell finden Sie unter "Überprüfung" in ASP.NET Core.

Der Validator-Komponentencode wird ausgeführt, wo die Komponente ausgeführt wird. In Interactive WebAssembly wird sie im Browser ausgeführt, während sie auf dem Server über den Schaltkreis in Interactive Server ausgeführt wird.

Bei statischem SSR wird der Code der Validator-Komponente beim Absenden des Formulars auf dem Server ausgeführt und bietet zwischen den Anfragen keine Live-Feldvalidierung für .NET.

Remoteüberprüfung von Interactive WebAssembly

Die Remotewalidierung sendet Formulardaten aus einer interaktiven WebAssembly-Komponente an einen Serverendpunkt und fügt die zurückgegebenen Feldfehler dem EditContext des Formulars hinzu. Es ist nützlich, wenn eine Regel private Serverdaten, einen externen Dienst oder eine andere Logik erfordert, die nicht im Browser ausgeführt werden soll.

Das Formular:

  1. Führt die Überprüfung von Datenanmerkungen lokal aus.
  2. Sendet lokal gültige Eingaben an den Endpunkt von OnValidSubmit.
  3. Empfängt Feldschlüsselüberprüfungsfehler vom Server.
  4. Fügt dem Formular über die Validator-Komponente Remote-Fehler hinzu.

OnValidSubmit bedeutet nur, dass die lokale Überprüfung erfolgreich war. Verarbeiten oder speichern Sie das Modell nur, nachdem die Remoteüberprüfung ebenfalls erfolgreich war.

Important

Senden Sie keine privaten Validierungsdaten oder Geschäftsregeln an den Browser. Der Server muss jede Anforderung unabhängig überprüfen, da die clientseitige Überprüfung umgangen werden kann.

In diesem Beispiel wird remote überprüft, wann das Formular gesendet wird. Verwenden Sie für Live-Remoteprüfungen auf Feldebene das Muster zur asynchronen Feldvalidierung aus Asynchrone Validierung hinzufügen.

Wenn das WebAssembly-Formular vorab gerendert wird, müssen die clientseitigen Dienste auch während der Vorbearbeitung verfügbar sein. Die verfügbaren Ansätze finden Sie unter Prerender ASP.NET Core Razor Komponenten.

Überprüfen mit einer minimalen API

Rufen Sie AddValidation im Serverprojekt auf, um die unterstützten Endpunktparameter zu validieren, bevor der Handler ausgeführt wird.

Die Microsoft.Extensions.Validation APIs, die für generierte Validierungsmetadaten verwendet werden, sind in .NET 10 experimentell. Ausführliche Informationen finden Sie unter "Überprüfung" in ASP.NET Core.

Wenn das Modell im .Client Projekt deklariert wird, registrieren Sie die generierten Validierungsmetadaten in beiden Projekten, wie in der Überprüfung in ASP.NET Core beschrieben.

Der Endpunkt fügt eine private Geschäftsregel hinzu und gibt Fehler zurück, die mit dem Namen des Modellelements als Schlüssel versehen sind:

app.MapPost("/api/starships/validate", (StarshipModel model) =>
{
    Dictionary<string, string[]> errors = [];

    if (model.Classification == "Defense" &&
        string.IsNullOrWhiteSpace(model.Description))
    {
        errors[nameof(model.Description)] =
            ["A defense ship requires a description."];
    }

    if (errors.Count > 0)
    {
        return Results.ValidationProblem(errors);
    }

    return Results.NoContent();
});
app.MapPost("/api/starships/validate", (StarshipModel model) =>
{
    Dictionary<string, string[]> errors = [];

    if (model.Classification == "Defense" &&
        string.IsNullOrWhiteSpace(model.Description))
    {
        errors[nameof(model.Description)] =
            ["A defense ship requires a description."];
    }

    if (errors.Count > 0)
    {
        return Results.ValidationProblem(errors);
    }

    return Results.NoContent();
});

Die automatische Überprüfung lehnt ungültige Datenanmerkungen ab, bevor der Handler ausgeführt wird. ValidationProblem gibt 400 Bad Request mit einer errors-Eigenschaft zurück, die feldschlüsselbezogene Meldungen enthält. Bei erfolgreicher Validierung wird 204 No Content zurückgegeben.

Überprüfen mit einem API-Controller

Platzieren Sie das gemeinsam genutzte Modell in einer gehosteten Blazor WebAssembly Lösung im Shared Projekt, und überprüfen Sie es mit einem API-Controller im Server Projekt. Das [ApiController] Attribut lehnt vor der Ausführung der Aktion automatisch ungültige Datenanmerkungen ab.

[ApiController]
[Route("api/starships/validate")]
public class StarshipValidationController : ControllerBase
{
    [HttpPost]
    public IActionResult Validate(StarshipModel model)
    {
        if (model.Classification == "Defense" &&
            string.IsNullOrWhiteSpace(model.Description))
        {
            ModelState.AddModelError(
                nameof(model.Description),
                "A defense ship requires a description.");
        }

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

        return NoContent();
    }
}

Registrieren und Zuordnen von Controllern im Serverprojekt. Der Controller gibt bei fehlgeschlagener Validierung 400 Bad Request mit einer Antwort ValidationProblemDetails zurück und bei erfolgreicher Validierung 204 No Content.

Aufrufen des Endpunkts und Anzeigen von Fehlern

Registrieren Sie im WebAssembly-Projekt ein HttpClient unter Verwendung der Basisadresse der App:

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

Platzieren Sie die Komponente CustomValidation aus Eine Validator-Komponente erstellen im Formular:

@page "/"
@using System.Net
@using System.Net.Http.Json
@using BlazorWebAppRemoteValidation.Client.Models
@inject HttpClient Http

<PageTitle>Remote validation</PageTitle>

<h1>Remote validation</h1>

<p>Local validation runs before the form calls the remote endpoint. The endpoint validates the model again and applies a private business rule that requires a description for defense ships.</p>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="remoteErrors" />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText id="identifier" @bind-Value="Model.Identifier" />
        </label>
        <ValidationMessage For="() => Model.Identifier" />
    </p>

    <p>
        <label>
            Classification:
            <InputSelect id="classification" @bind-Value="Model.Classification">
                <option value="">Select...</option>
                <option value="Exploration">Exploration</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
        <ValidationMessage For="() => Model.Classification" />
    </p>

    <p>
        <label>
            Description:
            <InputText id="description" @bind-Value="Model.Description" />
        </label>
        <ValidationMessage For="() => Model.Description" />
    </p>

    <button id="submit" type="submit">Submit</button>
</EditForm>

@if (accepted)
{
    <p id="accepted" role="status">The server accepted the form.</p>
}

@code {
    private StarshipModel Model { get; } = new();
    private CustomValidation? remoteErrors;
    private bool accepted;

    private async Task Submit()
    {
        accepted = false;

        using var response = await Http.PostAsJsonAsync(
            "api/starships/validate", Model);

        if (response.IsSuccessStatusCode)
        {
            accepted = true;
            return;
        }

        if (response.StatusCode == HttpStatusCode.BadRequest)
        {
            var problem = await response.Content
                .ReadFromJsonAsync<ValidationProblemResponse>()
                ?? throw new InvalidOperationException(
                    "The validation response didn't contain a response body.");

            remoteErrors!.DisplayErrors(problem.Errors);
            return;
        }

        response.EnsureSuccessStatusCode();
    }

    private sealed record ValidationProblemResponse(
        Dictionary<string, string[]> Errors);
}
@using System.Net
@using System.Net.Http.Json
@inject HttpClient Http

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="remoteErrors" />
    <ValidationSummary />

    ...
</EditForm>

@code {
    private StarshipModel Model { get; } = new StarshipModel();
    private CustomValidation? remoteErrors;

    private async Task Submit()
    {
        using var response = await Http.PostAsJsonAsync(
            "api/starships/validate", Model);

        if (response.IsSuccessStatusCode)
        {
            // Process or save the model.
            return;
        }

        if (response.StatusCode == HttpStatusCode.BadRequest)
        {
            var problem = await response.Content
                .ReadFromJsonAsync<ValidationProblemResponse>();

            if (problem is not null)
            {
                remoteErrors!.DisplayErrors(problem.Errors);
            }

            return;
        }

        response.EnsureSuccessStatusCode();
    }

    private sealed class ValidationProblemResponse
    {
        public Dictionary<string, string[]> Errors { get; set; } =
            new Dictionary<string, string[]>();
    }
}

Die Validatorkomponente löscht einen Remotefeldfehler, wenn sich das Feld ändert, sodass der Benutzer den Wert korrigieren und erneut übermitteln kann. Schützen Sie den Endpunkt gemäß den Sicherheitsanforderungen der Anwendung; Authentifizierung und Autorisierung liegen außerhalb des Gültigkeitsbereichs dieses Überprüfungsbeispiels.

Das vollständige Beispiel für die Remotevalidierung umfasst den Host-Endpunkt, das freigegebene Modell, die Registrierung der assemblyübergreifenden Validierung, die Validatorkomponente und das interaktive WebAssembly-Formular.

Das .NET 10-Beispiel für die Remoteüberprüfung veranschaulicht den gleichen Überprüfungsfluss in einer interaktiven Auto-App mit Authentifizierung und einem serverseitigen Proxy.

Blazor ASP.NET Core Formularüberprüfung