Blazor ASP.NET Core zaawansowane sprawdzanie poprawności formularza

Note

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Warning

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

W tym artykule przedstawiono składniki modułu sprawdzania poprawności wielokrotnego użytku i zdalną walidację. Aby zapoznać się z typowymi interfejsami API weryfikacji formularzy, w tym adnotacjami danych, bezpośrednią EditContext walidacją, wyświetlaniem komunikatów, stylem, stanem i przesyłaniem, zobacz sprawdzanie poprawności formularzy ASP.NET CoreBlazor.

Aby zapoznać się z regułami walidacji opartymi na modelu, współużytkowanymi przez Blazor i minimalne interfejsy API, zobacz Walidacja w ASP.NET Core.

Aby uzyskać informacje na temat walidacji w przeglądarce w statycznym renderowaniu po stronie serwera (statycznym SSR), zobacz ASP.NET Core Blazor walidacja formularzy po stronie klienta w statycznym SSR.

Kompilowanie składnika modułu sprawdzania poprawności

Komponent walidatora obejmuje logikę walidacji, która używa EditContext i ValidationMessageStore formularza. Jest to przydatne, gdy to samo zachowanie weryfikacji jest używane przez kilka formularzy lub gdy błędy docierają z usługi, a nie z atrybutów weryfikacji w modelu.

Składnik:

  • Otrzymuje EditContext formularza jako parametr kaskadowy.
  • Tworzy repozytorium komunikatów o swoich błędach.
  • Czyści nieaktualne błędy w formularzu po zażądaniu walidacji.
  • Czyści nieaktualne błędy pola, gdy pole się zmieni.
  • Uwidacznia metody wyświetlania i czyszczenia błędów.
  • Anuluje subskrypcję programów obsługi zdarzeń po usunięciu.

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;
        }
    }
}

Umieść składnik wewnątrz elementu EditForm i przechwyć odwołanie do składnika, gdy formularz lub usługa powinna wyświetlać błędy:

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

    ...
</EditForm>

@code {
    private CustomValidation? customValidation;
}

Składnik może być używany razem z elementem DataAnnotationsValidator. Każdy walidator ma własny magazyn komunikatów skojarzony z tym samym EditContext, a ValidationMessage i ValidationSummary wyświetlają komunikaty z obu walidatorów.

Aby zaimplementować regułę biznesową wewnątrz składnika modułu sprawdzania poprawności zamiast akceptowania błędów zewnętrznych, uruchom regułę z HandleValidationRequested lub HandleFieldChanged i dodaj komunikaty do elementu messages. Mniejszy przykład, który pokazuje to bezpośrednio w komponencie formularza, znajduje się tutaj: walidacja formularzy w platformie ASP.NET CoreBlazor.

Dodawanie walidacji asynchronicznej

Ten sam wzorzec składnika obsługuje pracę asynchroniczną:

  • W procedurze obsługi OnValidationRequested wywołaj e.AddAsyncValidator, aby zarejestrować operacje na poziomie formularza. EditForm czeka na nią przed wywołaniem OnValidSubmit lub OnInvalidSubmit.
  • W procedurze obsługi OnFieldChanged wywołaj EditContext.RegisterAsyncFieldValidator, aby rozpocząć walidację tego pola. Uruchomienie kolejnej weryfikacji dla tego samego pola zastępuje i anuluje poprzednią operację.

W przypadku walidacji asynchronicznej na poziomie formularza:

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();
}

W przypadku walidacji asynchronicznej na poziomie pola:

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

Przekaż podany token anulowania do operacji we/wy. Wyczyść poprzednie komunikaty przed rozpoczęciem operacji, unikaj publikowania częściowych wyników w przypadku wyjątku i wywołaj NotifyValidationStateChanged po zaktualizowaniu komunikatów.

Operacja, która została anulowana, ponieważ została zastąpiona lub ponieważ anulowano etap weryfikacji, jest odrzucana. Inne wyjątki powodują przejście pola lub formularza w stan błędu. Aby wyświetlić stan oczekujący i błędny, zobacz sekcję weryfikacja formularzy w ASP.NET CoreBlazor.

Kompletny składnik, który łączy walidację asynchroniczną na poziomie formularza oraz dla poszczególnych pól, znajdziesz w poniższym przykładzie:

@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;
        }
    }
}

Aby uzyskać atrybuty walidacji asynchronicznej w modelu, zobacz Walidacja w ASP.NET Core.

Kod składnika walidatora jest uruchamiany tam, gdzie uruchamiany jest ten składnik. W trybie Interactive WebAssembly działa w przeglądarce, natomiast w trybie Interactive Server działa na serwerze za pośrednictwem połączenia.

W trybie statycznego SSR kod komponentu walidatora jest uruchamiany na serwerze podczas przesyłania formularza i nie zapewnia walidacji pól .NET w czasie rzeczywistym pomiędzy żądaniami.

Walidacja zdalna w Interactive WebAssembly

Walidacja zdalna wysyła dane formularza z interaktywnego komponentu WebAssembly do punktu końcowego na serwerze i dodaje zwrócone błędy dotyczące pól do elementu EditContext formularza. Jest to przydatne, gdy reguła wymaga prywatnych danych serwera, usługi zewnętrznej lub innej logiki, która nie powinna być uruchamiana w przeglądarce.

Formularz:

  1. Uruchamia walidację adnotacji danych lokalnie.
  2. Wysyła lokalnie prawidłowe dane wejściowe do punktu końcowego z OnValidSubmit.
  3. Otrzymuje z serwera błędy walidacji powiązane z kluczami pól.
  4. Dodaje błędy zdalne do formularza za pośrednictwem składnika modułu sprawdzania poprawności.

OnValidSubmit oznacza tylko, że weryfikacja lokalna zakończyła się pomyślnie. Przetwórz lub zapisz model dopiero po pomyślnym zakończeniu walidacji zdalnej.

Ważna

Nie wysyłaj prywatnych danych weryfikacji ani reguł biznesowych do przeglądarki. Serwer musi niezależnie zweryfikować każde żądanie, ponieważ można pominąć walidację po stronie klienta.

Ten przykład sprawdza poprawność zdalnie po przesłaniu formularza. W przypadku kontroli zdalnych na żywo dla poszczególnych pól użyj asynchronicznego wzorca sprawdzania poprawności pól z sekcji Dodaj walidację asynchroniczną.

Jeśli formularz WebAssembly jest renderowany wstępnie, jego usługi po stronie klienta również muszą być dostępne podczas renderowania wstępnego. Aby zapoznać się z dostępnymi podejściami, zobacz Wstępne renderowanie składników ASP.NET Core Razor.

Weryfikacja za pomocą Minimal API

Wywołaj metodę AddValidation w projekcie serwera, aby zweryfikować obsługiwane parametry punktu końcowego przed uruchomieniem programu obsługi.

Interfejsy API Microsoft.Extensions.Validation, używane na potrzeby wygenerowanych metadanych walidacji, mają charakter eksperymentalny w platformie .NET 10. Aby uzyskać szczegółowe informacje, zobacz Walidacja w ASP.NET Core.

Jeśli model jest zadeklarowany w projekcie.Client, zarejestruj wygenerowane metadane weryfikacji w obu projektach zgodnie z opisem w temacie Walidacja w ASP.NET Core.

Punkt końcowy dodaje prywatną regułę biznesową i zwraca błędy, gdzie kluczem jest nazwa składowej modelu:

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();
});

Automatyczna walidacja odrzuca nieprawidłowe adnotacje danych przed uruchomieniem programu obsługi. ValidationProblem zwraca 400 Bad Request z właściwością errors, zawierającą komunikaty przypisane do kluczy pól. Pomyślna walidacja zwraca wartość 204 No Content.

Zweryfikuj za pomocą kontrolera API

W rozwiązaniu hostowanym Blazor WebAssembly umieść udostępniony model w Shared projekcie i zweryfikuj go za pomocą kontrolera interfejsu API w projekcie Server . Atrybut [ApiController] automatycznie odrzuca nieprawidłowe adnotacje danych przed uruchomieniem akcji.

[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();
    }
}

Zarejestruj i zmapuj kontrolery w projekcie serwera. Kontroler zwraca 400 Bad Request odpowiedź, gdy walidacja zakończy się niepowodzeniem ValidationProblemDetails i 204 No Content gdy zakończy się powodzeniem.

Wywoływanie punktu końcowego i wyświetlanie błędów

Zarejestruj element HttpClient w projekcie WebAssembly przy użyciu podstawowego adresu aplikacji:

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

Umieść komponent CustomValidation z sekcji Utwórz komponent walidatora w formularzu:

@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[]>();
    }
}

Składnik modułu sprawdzania poprawności czyści błąd pola zdalnego po zmianie tego pola, aby użytkownik mógł poprawić wartość i przesłać ponownie. Ochrona punktu końcowego zgodnie z wymaganiami dotyczącymi zabezpieczeń aplikacji; uwierzytelnianie i autoryzacja znajdują się poza zakresem tego przykładu weryfikacji.

Kompletny przykład zdalnego sprawdzania poprawności obejmuje punkt końcowy hosta, model udostępniony, rejestrację walidacji między zestawami, składnik modułu sprawdzania poprawności oraz formularz Interactive WebAssembly.

Przykład zdalnej walidacji w .NET 10 pokazuje ten sam przebieg walidacji w aplikacji Interactive Auto z uwierzytelnianiem i serwerowym proxy.

Dodatkowe zasoby

sprawdzanie poprawności formularzy Blazor ASP.NET Core