Bagikan melalui


validasi formulir ASP.NET Core Blazor

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Peringatan

Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara menggunakan validasi dalam Blazor formulir.

Validasi formulir

Dalam skenario validasi formulir dasar, EditForm instans dapat menggunakan instans yang dideklarasikan EditContext dan ValidationMessageStore instans untuk memvalidasi bidang formulir. Handler untuk OnValidationRequested peristiwa EditContext menjalankan logika validasi kustom. Hasil handler memperbarui ValidationMessageStore instans.

Validasi formulir dasar berguna dalam kasus di mana model formulir didefinisikan dalam komponen yang menghosting formulir, baik sebagai anggota langsung pada komponen atau di subkelas. Penggunaan komponen validator direkomendasikan di mana kelas model independen digunakan di beberapa komponen.

Dalam Blazor Web Apphal ini, validasi sisi klien memerlukan sirkuit aktif BlazorSignalR . Validasi sisi klien tidak tersedia untuk formulir dalam komponen yang telah mengadopsi penyajian sisi server statis (SSR statis). Formulir yang mengadopsi SSR statis divalidasi di server setelah formulir dikirimkan.

Dalam komponen berikut, HandleValidationRequested metode handler menghapus pesan validasi yang ada dengan memanggil ValidationMessageStore.Clear sebelum memvalidasi formulir.

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]
    private 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: Processing 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" 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]
    private 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: Processing 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;
        }
    }
}

Komponen Validator Anotasi Data dan validasi kustom

Komponen DataAnnotationsValidator melampirkan validasi anotasi data ke berskala EditContext. Mengaktifkan validasi anotasi data memerlukan DataAnnotationsValidator komponen. Untuk menggunakan sistem validasi yang berbeda dari anotasi data, gunakan implementasi kustom alih-alih DataAnnotationsValidator komponen. Implementasi kerangka kerja untuk DataAnnotationsValidator tersedia untuk inspeksi di sumber referensi:

Catatan

Tautan dokumentasi ke sumber referensi .NET biasanya memuat cabang default repositori, yang mewakili pengembangan saat ini untuk rilis .NET berikutnya. Untuk memilih tag rilis tertentu, gunakan daftar dropdown Beralih cabang atau tag. Untuk informasi lebih lanjut, lihat Cara memilih tag versi kode sumber ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Blazor melakukan dua jenis validasi:

  • Validasi bidang dilakukan saat tab pengguna keluar dari bidang. Selama validasi bidang, DataAnnotationsValidator komponen mengaitkan semua hasil validasi yang dilaporkan dengan bidang .
  • Validasi model dilakukan saat pengguna mengirimkan formulir. Selama validasi model, DataAnnotationsValidator komponen mencoba menentukan bidang berdasarkan nama anggota yang dilaporkan hasil validasi. Hasil validasi yang tidak terkait dengan masing-masing anggota dikaitkan dengan model, bukan bidang.

Komponen validator

Komponen validator mendukung validasi formulir dengan mengelola ValidationMessageStore untuk formulir EditContext.

Blazor Kerangka kerja menyediakan komponen untuk melampirkan DataAnnotationsValidator dukungan validasi ke formulir berdasarkan atribut validasi (anotasi data). Anda dapat membuat komponen validator kustom untuk memproses pesan validasi untuk formulir yang berbeda di halaman yang sama atau formulir yang sama pada langkah-langkah pemrosesan formulir yang berbeda (misalnya, validasi klien diikuti oleh validasi server). Contoh komponen validator yang diperlihatkan di bagian ini, CustomValidation, digunakan di bagian berikut dari artikel ini:

Dari validator bawaan anotasi data, hanya[Remote] atribut validasi yang tidak didukung di Blazor.

Catatan

Atribut validasi anotasi data kustom dapat digunakan alih-alih komponen validator kustom dalam banyak kasus. Atribut kustom yang diterapkan ke model formulir diaktifkan dengan penggunaan DataAnnotationsValidator komponen. Ketika digunakan dengan validasi server, atribut kustom apa pun yang diterapkan ke model harus dapat dieksekusi di server. Untuk informasi selengkapnya, lihat bagian Atribut validasi kustom.

Buat komponen validator dari ComponentBase:

  • Formulir EditContext adalah parameter kaskading komponen.
  • Ketika komponen validator diinisialisasi, baru ValidationMessageStore dibuat untuk mempertahankan daftar kesalahan formulir saat ini.
  • Penyimpanan pesan menerima kesalahan saat kode pengembang dalam komponen formulir memanggil DisplayErrors metode . Kesalahan diteruskan ke DisplayErrors metode dalam Dictionary<string, List<string>>. Dalam kamus, kuncinya adalah nama bidang formulir yang memiliki satu atau beberapa kesalahan. Nilainya adalah daftar kesalahan.
  • Pesan dibersihkan ketika salah satu hal berikut ini telah terjadi:
    • Validasi diminta pada EditContext saat OnValidationRequested peristiwa dinaikkan. Semua kesalahan dibersihkan.
    • Bidang berubah dalam formulir saat OnFieldChanged peristiwa dinaikkan. Hanya kesalahan untuk bidang yang dibersihkan.
    • Metode ClearErrors ini dipanggil oleh kode pengembang. Semua kesalahan dibersihkan.

Perbarui namespace layanan di kelas berikut agar sesuai dengan namespace aplikasi Anda.

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

Penting

Menentukan namespace diperlukan saat berasal dari ComponentBase. Gagal menentukan namespace menghasilkan kesalahan build:

Tag helpers cannot target tag name '<global namespace>.{CLASS NAME}' because it contains a ' ' character.

Tempat {CLASS NAME} penampung adalah nama kelas komponen. Contoh validator kustom di bagian ini menentukan contoh namespace BlazorSamplelayanan .

Catatan

Ekspresi lambda anonim adalah penanganan aktivitas terdaftar untuk OnValidationRequested dan OnFieldChanged dalam contoh sebelumnya. Tidak perlu menerapkan IDisposable dan berhenti berlangganan delegasi peristiwa dalam skenario ini. Untuk informasi lebih lanjut, lihat siklus hidup komponen Razor ASP.NET Core.

Validasi logika bisnis dengan komponen validator

Untuk validasi logika bisnis umum, gunakan komponen validator yang menerima kesalahan formulir dalam kamus.

Validasi dasar berguna dalam kasus di mana model formulir didefinisikan dalam komponen yang menghosting formulir, baik sebagai anggota langsung pada komponen atau di subkelas. Penggunaan komponen validator direkomendasikan di mana kelas model independen digunakan di beberapa komponen.

Dalam contoh berikut:

  • Versi Starfleet Starship Database formulir (Starship3 komponen) yang dipersingkat dari bagian Formulir contoh dari artikel Komponen input digunakan yang hanya menerima klasifikasi dan deskripsi starship. Validasi anotasi data tidak dipicu pada pengiriman formulir karena DataAnnotationsValidator komponen tidak disertakan dalam formulir.
  • Komponen CustomValidation dari bagian komponen Validator dari artikel ini digunakan.
  • Validasi memerlukan nilai untuk deskripsi kapal (Description) jika pengguna memilih klasifikasi pengiriman "Defense" (Classification).

Saat pesan validasi diatur dalam komponen, pesan tersebut ditambahkan ke validator ValidationMessageStore dan ditampilkan dalam EditFormringkasan validasi.

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]
    private 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" 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]
    private 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");
        }
    }
}

Catatan

Sebagai alternatif untuk menggunakan komponen validasi, atribut validasi anotasi data dapat digunakan. Atribut kustom yang diterapkan ke model formulir diaktifkan dengan penggunaan DataAnnotationsValidator komponen. Ketika digunakan dengan validasi server, atribut harus dapat dieksekusi di server. Untuk informasi selengkapnya, lihat bagian Atribut validasi kustom.

Validasi server dengan komponen validator

Bagian ini difokuskan pada Blazor Web App skenario, tetapi pendekatan untuk semua jenis aplikasi yang menggunakan validasi server dengan API web mengadopsi pendekatan umum yang sama.

Bagian ini difokuskan pada skenario yang dihosting Blazor WebAssembly , tetapi pendekatan untuk semua jenis aplikasi yang menggunakan validasi server dengan API web mengadopsi pendekatan umum yang sama.

Validasi server didukung selain validasi klien:

  • Proses validasi klien dalam formulir dengan DataAnnotationsValidator komponen .
  • Ketika formulir melewati validasi klien (OnValidSubmit dipanggil), kirim ke EditContext.Model API server backend untuk pemrosesan formulir.
  • Validasi model proses di server.
  • API server mencakup validasi anotasi data kerangka kerja bawaan dan logika validasi kustom yang disediakan oleh pengembang. Jika validasi lolos di server, proses formulir dan kirim kembali kode status keberhasilan (200 - OK). Jika validasi gagal, kembalikan kode status kegagalan (400 - Bad Request) dan kesalahan validasi bidang.
  • Nonaktifkan formulir saat berhasil atau tampilkan kesalahan.

Validasi dasar berguna dalam kasus di mana model formulir didefinisikan dalam komponen yang menghosting formulir, baik sebagai anggota langsung pada komponen atau di subkelas. Penggunaan komponen validator direkomendasikan di mana kelas model independen digunakan di beberapa komponen.

Contoh berikut didasarkan pada:

  • dengan Blazor Web App komponen Interactive WebAssembly yang dibuat dari Blazor Web App templat proyek.
  • Model Starship (Starship.cs) dari bagian Formulir contoh dari artikel Komponen input.
  • Komponen yang CustomValidation ditunjukkan di bagian Komponen validator.

Tempatkan Starship model (Starship.cs) ke dalam proyek pustaka kelas bersama sehingga proyek klien dan server dapat menggunakan model. Tambahkan atau perbarui namespace agar sesuai dengan namespace aplikasi bersama (misalnya, namespace BlazorSample.Shared). Karena model memerlukan anotasi data, konfirmasikan bahwa pustaka kelas bersama menggunakan kerangka kerja bersama atau tambahkan System.ComponentModel.Annotations paket ke proyek bersama.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

Dalam proyek Blazor Web Apputama , tambahkan pengontrol untuk memproses permintaan validasi starship dan mengembalikan pesan validasi yang gagal. Perbarui namespace dalam pernyataan terakhir using untuk proyek pustaka kelas bersama dan namespace untuk kelas pengontrol. Selain validasi anotasi data klien dan server, pengontrol memvalidasi bahwa nilai disediakan untuk deskripsi kapal (Description) jika pengguna memilih Defense klasifikasi pengiriman (Classification).

  • Solusi yang dihostingBlazor WebAssembly dibuat dari Blazor WebAssembly templat proyek. Pendekatan ini didukung untuk salah satu solusi yang dihosting Blazor aman yang dijelaskan dalam dokumentasi keamanan yang dihostingBlazor WebAssembly.
  • Model Starship (Starship.cs) dari bagian Formulir contoh dari artikel Komponen input.
  • Komponen yang CustomValidation ditunjukkan di bagian Komponen validator.

Tempatkan Starship model (Starship.cs) ke dalam proyek solusi Shared sehingga aplikasi klien dan server dapat menggunakan model. Tambahkan atau perbarui namespace agar sesuai dengan namespace aplikasi bersama (misalnya, namespace BlazorSample.Shared). Karena model memerlukan anotasi data, tambahkan System.ComponentModel.Annotations paket ke Shared proyek.

Catatan

Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

Server Dalam proyek, tambahkan pengontrol untuk memproses permintaan validasi starship dan mengembalikan pesan validasi yang gagal. Perbarui namespace dalam pernyataan terakhir using untuk Shared proyek dan namespace untuk kelas pengontrol. Selain validasi anotasi data klien dan server, pengontrol memvalidasi bahwa nilai disediakan untuk deskripsi kapal (Description) jika pengguna memilih Defense klasifikasi pengiriman (Classification).

Validasi untuk Defense klasifikasi pengiriman hanya terjadi pada server di pengontrol karena formulir mendatang tidak melakukan validasi yang sama sisi klien ketika formulir dikirimkan ke server. Validasi server tanpa validasi klien umum dalam aplikasi yang memerlukan validasi logika bisnis privat input pengguna di server. Misalnya, informasi privat dari data yang disimpan untuk pengguna mungkin diperlukan untuk memvalidasi input pengguna. Data privat jelas tidak dapat dikirim ke klien untuk validasi klien.

Catatan

Pengontrol StarshipValidation di bagian ini menggunakan Microsoft Identity 2.0. API Web hanya menerima token untuk pengguna yang memiliki cakupan "API.Access" untuk API ini. Kustomisasi tambahan diperlukan jika nama cakupan API berbeda dari API.Access.

Untuk informasi selengkapnya tentang keamanan, lihat:

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(
    ILogger<StarshipValidationController> logger) 
    : ControllerBase
{
    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);
    }
}

Konfirmasi atau perbarui namespace pengontrol sebelumnya (BlazorSample.Server.Controllers) agar sesuai dengan namespace pengontrol aplikasi.

Ketika kesalahan validasi pengikatan model terjadi di server, ApiController (ApiControllerAttribute) biasanya mengembalikan respons permintaan buruk default dengan ValidationProblemDetails. Respons berisi lebih banyak data daripada hanya kesalahan validasi, seperti yang ditunjukkan dalam contoh berikut ketika semua bidang Starfleet Starship Database formulir tidak dikirimkan dan formulir gagal validasi:

{
  "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)."]
  }
}

Catatan

Untuk menunjukkan respons JSON sebelumnya, Anda harus menonaktifkan validasi klien formulir untuk mengizinkan pengiriman formulir bidang kosong atau menggunakan alat untuk mengirim permintaan langsung ke API server, seperti Pengembang Browser Firefox.

Jika API server mengembalikan respons JSON default sebelumnya, klien dapat mengurai respons dalam kode pengembang untuk mendapatkan turunan simpul errors untuk pemrosesan kesalahan validasi formulir. Tidak nyaman untuk menulis kode pengembang untuk mengurai file. Mengurai JSON secara manual memerlukan menghasilkan Dictionary<string, List<string>> kesalahan setelah memanggil ReadFromJsonAsync. Idealnya, API server hanya boleh mengembalikan kesalahan validasi, seperti yang ditunjukkan contoh berikut:

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

Untuk mengubah respons API server agar hanya mengembalikan kesalahan validasi, ubah delegasi yang dipanggil pada tindakan yang dianotasi dengan ApiControllerAttribute dalam Program file. Untuk titik akhir API (/StarshipValidation), kembalikan BadRequestObjectResult dengan ModelStateDictionary. Untuk titik akhir API lainnya, pertahankan perilaku default dengan mengembalikan hasil objek dengan baru ValidationProblemDetails.

Microsoft.AspNetCore.Mvc Tambahkan namespace layanan ke bagian Program atas file dalam proyek Blazor Web Apputama :

using Microsoft.AspNetCore.Mvc;

Program Dalam file, tambahkan atau perbarui metode ekstensi berikut AddControllersWithViews dan tambahkan panggilan berikut ke ConfigureApiBehaviorOptions:

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

Jika Anda menambahkan pengontrol ke proyek utama untuk pertama kalinya Blazor Web App , petakan titik akhir pengontrol saat Anda menempatkan kode sebelumnya yang mendaftarkan layanan untuk pengontrol. Contoh berikut menggunakan rute pengontrol default:

app.MapDefaultControllerRoute();

Catatan

Contoh sebelumnya secara eksplisit mendaftarkan layanan pengontrol dengan memanggil AddControllersWithViews untuk secara otomatis mengurangi serangan Pemalsuan Permintaan Lintas Situs (XSRF/CSRF). Jika Anda hanya menggunakan AddControllers, antiforgery tidak diaktifkan secara otomatis.

Untuk informasi selengkapnya tentang respons kesalahan perutean dan kegagalan validasi pengontrol, lihat sumber daya berikut ini:

.Client Dalam proyek, tambahkan komponen yang CustomValidation ditampilkan di bagian Komponen validator. Perbarui namespace agar sesuai dengan aplikasi (misalnya, namespace BlazorSample.Client).

.Client Dalam proyek, formulir diperbarui Starfleet Starship Database untuk menampilkan kesalahan validasi server dengan bantuan CustomValidation komponen. Saat API server mengembalikan pesan validasi, api ditambahkan ke CustomValidation komponen ValidationMessageStore. Kesalahan tersedia dalam formulir EditContext untuk ditampilkan oleh ringkasan validasi formulir.

Dalam komponen berikut, perbarui namespace proyek bersama (@using BlazorSample.Shared) ke namespace layanan proyek bersama. Perhatikan bahwa formulir memerlukan otorisasi, sehingga pengguna harus masuk ke aplikasi untuk menavigasi ke formulir.

Microsoft.AspNetCore.Mvc Tambahkan namespace layanan ke bagian Program atas file di Server aplikasi:

using Microsoft.AspNetCore.Mvc;

Program Dalam file, temukan AddControllersWithViews metode ekstensi dan tambahkan panggilan berikut ke ConfigureApiBehaviorOptions:

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

Catatan

Contoh sebelumnya secara eksplisit mendaftarkan layanan pengontrol dengan memanggil AddControllersWithViews untuk secara otomatis mengurangi serangan Pemalsuan Permintaan Lintas Situs (XSRF/CSRF). Jika Anda hanya menggunakan AddControllers, antiforgery tidak diaktifkan secara otomatis.

Client Dalam proyek, tambahkan komponen yang CustomValidation ditampilkan di bagian Komponen validator. Perbarui namespace agar sesuai dengan aplikasi (misalnya, namespace BlazorSample.Client).

Client Dalam proyek, formulir diperbarui Starfleet Starship Database untuk menampilkan kesalahan validasi server dengan bantuan CustomValidation komponen. Saat API server mengembalikan pesan validasi, api ditambahkan ke CustomValidation komponen ValidationMessageStore. Kesalahan tersedia dalam formulir EditContext untuk ditampilkan oleh ringkasan validasi formulir.

Dalam komponen berikut, perbarui namespace Shared proyek (@using BlazorSample.Shared) ke namespace layanan proyek bersama. Perhatikan bahwa formulir memerlukan otorisasi, sehingga pengguna harus masuk ke aplikasi untuk menavigasi ke formulir.

Starship10.razor:

Catatan

Formulir berdasarkan EditForm aktifkan dukungan antiforgery secara otomatis. Pengontrol harus digunakan AddControllersWithViews untuk mendaftarkan layanan pengontrol dan secara otomatis mengaktifkan dukungan antiforgery untuk API web.

@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]
    private 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.";
        }
    }
}

Proyek .Client Blazor Web App harus juga mendaftarkan HttpClient permintaan HTTP POST ke pengontrol API web backend. Konfirmasi atau tambahkan yang berikut ini ke .Client file proyek Program :

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

Contoh sebelumnya mengatur alamat dasar dengan builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), yang mendapatkan alamat dasar untuk aplikasi dan biasanya berasal dari <base> nilai tag href di halaman host.

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

Catatan

Sebagai alternatif untuk penggunaan komponen validasi, atribut validasi anotasi data dapat digunakan. Atribut kustom yang diterapkan ke model formulir diaktifkan dengan penggunaan DataAnnotationsValidator komponen. Ketika digunakan dengan validasi server, atribut harus dapat dieksekusi di server. Untuk informasi selengkapnya, lihat bagian Atribut validasi kustom.

Catatan

Pendekatan validasi server di bagian ini cocok untuk salah satu contoh solusi yang dihosting Blazor WebAssembly dalam kumpulan dokumentasi ini:

InputText berdasarkan peristiwa input

InputText Gunakan komponen untuk membuat komponen kustom yang menggunakan oninput peristiwa (input) alih-alih onchange peristiwa (change). Penggunaan kejadian memicu input validasi bidang pada setiap penekanan tombol.

Komponen berikut CustomInputText mewarisi komponen kerangka kerja InputText dan mengatur pengikatan peristiwa ke oninput peristiwa (input).

CustomInputText.razor:

@inherits InputText

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

Komponen CustomInputText dapat digunakan di mana saja InputText digunakan. Komponen berikut menggunakan komponen bersama CustomInputText .

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]
    private Starship? Model { get; set; }

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

    private void Submit() => Logger.LogInformation("Submit: Processing 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" 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]
    private Starship? Model { get; set; }

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

    private void Submit() => Logger.LogInformation("Submit: Processing 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; }
    }
}

Komponen Ringkasan Validasi dan Pesan Validasi

Komponen ValidationSummary ini meringkas semua pesan validasi, yang mirip dengan Pembantu Tag Ringkasan Validasi:

<ValidationSummary />

Pesan validasi output untuk model tertentu dengan Model parameter :

<ValidationSummary Model="Model" />

Komponen ValidationMessage<TValue> menampilkan pesan validasi untuk bidang tertentu, yang mirip dengan Pembantu Tag Pesan Validasi. Tentukan bidang untuk validasi dengan For atribut dan ekspresi lambda yang menamai properti model:

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

Komponen ValidationMessage<TValue> dan ValidationSummary mendukung atribut arbitrer. Atribut apa pun yang tidak cocok dengan parameter komponen ditambahkan ke elemen atau <ul> yang dihasilkan<div>.

Mengontrol gaya pesan validasi di lembar gaya aplikasi (wwwroot/css/app.css atau wwwroot/css/site.css). Kelas default validation-message mengatur warna teks pesan validasi menjadi merah:

.validation-message {
    color: red;
}

Menentukan apakah bidang formulir valid

Gunakan EditContext.IsValid untuk menentukan apakah bidang valid tanpa mendapatkan pesan validasi.

Didukung, tetapi tidak disarankan:

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

Direkomendasikan:

var isValid = editContext.IsValid(fieldIdentifier);

Atribut validasi kustom

Untuk memastikan bahwa hasil validasi dikaitkan dengan bidang dengan benar saat menggunakan atribut validasi kustom, berikan konteks MemberName validasi saat membuat ValidationResult.

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

Masukkan layanan ke dalam atribut validasi kustom melalui ValidationContext. Contoh berikut menunjukkan bentuk koki salad yang memvalidasi input pengguna dengan injeksi dependensi (DI).

Kelas menunjukkan SaladChef daftar bahan starship yang disetujui untuk sepuluh salad Forward.

SaladChef.cs:

namespace BlazorSample;

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

Daftar SaladChef di kontainer DI aplikasi dalam Program file:

builder.Services.AddTransient<SaladChef>();

Metode IsValid kelas berikut SaladChefValidatorAttribute mendapatkan SaladChef layanan dari DI untuk memeriksa input pengguna.

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

Komponen berikut memvalidasi input pengguna dengan menerapkan SaladChefValidatorAttribute ([SaladChefValidator]) ke string bahan salad (SaladIngredient).

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

Atribut kelas CSS validasi kustom

Atribut kelas CSS validasi kustom berguna saat mengintegrasikan dengan kerangka kerja CSS, seperti Bootstrap.

Untuk menentukan atribut kelas CSS validasi kustom, mulailah dengan menyediakan gaya CSS untuk validasi kustom. Dalam contoh berikut, gaya (validField) dan tidak valid (invalidField) yang valid ditentukan.

Tambahkan kelas CSS berikut ke lembar gaya aplikasi:

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

Buat kelas yang berasal dari FieldCssClassProvider pemeriksaan pesan validasi bidang dan terapkan gaya valid atau tidak valid yang sesuai.

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

Atur CustomFieldClassProvider kelas sebagai Penyedia Kelas CSS Bidang pada instans formulir EditContext dengan SetFieldCssClassProvider.

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]
    private Starship? Model { get; set; }

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

    private void Submit() => Logger.LogInformation("Submit: Processing 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" 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]
    private Starship? Model { get; set; }

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

    private void Submit() => Logger.LogInformation("Submit: Processing 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; }
    }
}

Contoh sebelumnya memeriksa validitas semua bidang formulir dan menerapkan gaya ke setiap bidang. Jika formulir hanya boleh menerapkan gaya kustom ke subset bidang, buat terapkan CustomFieldClassProvider gaya secara kondisional. Contoh berikut CustomFieldClassProvider2 hanya menerapkan gaya ke Name bidang . Untuk bidang apa pun dengan nama yang tidak cocok Name, string.Empty dikembalikan, dan tidak ada gaya yang diterapkan. Dengan menggunakan refleksi, bidang dicocokkan dengan properti atau nama bidang anggota model, bukan yang id ditetapkan ke entitas HTML.

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

Catatan

Mencocokkan nama bidang dalam contoh sebelumnya peka huruf besar/kecil, sehingga anggota properti model yang ditunjuk "Name" harus cocok dengan pemeriksaan kondisional pada "Name":

  • Cocok dengan benar: fieldId.FieldName == "Name"
  • Gagal mencocokkan: fieldId.FieldName == "name"
  • Gagal mencocokkan: fieldId.FieldName == "NAME"
  • Gagal mencocokkan: fieldId.FieldName == "nAmE"

Tambahkan properti tambahan ke Model, misalnya:

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

Tambahkan ke Description CustomValidationForm formulir komponen:

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

EditContext Perbarui instans dalam metode komponen OnInitialized untuk menggunakan Penyedia Kelas CSS Bidang baru:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Karena kelas validasi CSS tidak diterapkan ke Description bidang , kelas tersebut tidak ditata. Namun, validasi bidang berjalan normal. Jika tersedia lebih dari 10 karakter, ringkasan validasi menunjukkan kesalahan:

Deskripsi terlalu panjang.

Dalam contoh berikut:

  • Gaya CSS kustom diterapkan ke Name bidang .

  • Bidang lain menerapkan logika yang mirip Blazordengan logika default dan menggunakan Blazorgaya validasi CSS bidang default, modified dengan valid atau invalid. Perhatikan bahwa untuk gaya default, Anda tidak perlu menambahkannya ke lembar gaya aplikasi jika aplikasi didasarkan pada Blazor templat proyek. Untuk aplikasi yang Blazor tidak didasarkan pada templat proyek, gaya default dapat ditambahkan ke lembar gaya aplikasi:

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

EditContext Perbarui instans dalam metode komponen OnInitialized untuk menggunakan Penyedia Kelas CSS Bidang sebelumnya:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Menggunakan CustomFieldClassProvider3:

  • Bidang menggunakan Name gaya CSS validasi kustom aplikasi.
  • Bidang Description menggunakan logika yang mirip Blazordengan logika dan Blazorgaya validasi CSS bidang default.

Validasi tingkat kelas dengan IValidatableObject

Validasi tingkat kelas dengan IValidatableObject (dokumentasi API) didukung untuk Blazor model formulir. IValidatableObject validasi hanya dijalankan ketika formulir dikirimkan dan hanya jika semua validasi lainnya berhasil.

Blazor paket validasi anotasi data

Microsoft.AspNetCore.Components.DataAnnotations.Validation adalah paket yang mengisi kesenjangan pengalaman validasi menggunakan DataAnnotationsValidator komponen. Paket saat ini bersifat eksperimental.

Peringatan

Paket Microsoft.AspNetCore.Components.DataAnnotations.Validation ini memiliki kandidat rilis versi terbaru di NuGet.org. Terus gunakan paket kandidat rilis eksperimental saat ini. Fitur eksperimental disediakan untuk tujuan mengeksplorasi kelayakan fitur dan mungkin tidak dikirimkan dalam versi stabil. Tonton repositori GitHub Pengumuman, dotnet/aspnetcore repositori GitHub, atau bagian topik ini untuk pembaruan lebih lanjut.

atribut [CompareProperty]

CompareAttribute tidak berfungsi dengan baik dengan DataAnnotationsValidator komponen karena DataAnnotationsValidator tidak mengaitkan hasil validasi dengan anggota tertentu. Ini dapat mengakibatkan perilaku yang tidak konsisten antara validasi tingkat bidang dan ketika seluruh model divalidasi pada pengiriman. Paket Microsoft.AspNetCore.Components.DataAnnotations.Validation eksperimental memperkenalkan atribut validasi tambahan, ComparePropertyAttribute, yang bekerja di sekitar batasan ini. Dalam aplikasi Blazor , [CompareProperty] adalah pengganti langsung untuk [Compare] atribut .

Model berlapis, jenis koleksi, dan jenis kompleks

Blazor menyediakan dukungan untuk memvalidasi input formulir menggunakan anotasi data dengan bawaan DataAnnotationsValidator. Namun, satu-satunya DataAnnotationsValidator memvalidasi properti tingkat atas model yang terikat ke formulir yang bukan properti jenis koleksi atau kompleks.

Untuk memvalidasi seluruh grafik objek model terikat, termasuk properti jenis koleksi dan kompleks, gunakan ObjectGraphDataAnnotationsValidator yang disediakan oleh paket eksperimentalMicrosoft.AspNetCore.Components.DataAnnotations.Validation:

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

Anotasi properti model dengan [ValidateComplexType]. Dalam kelas model berikut, ShipDescription kelas berisi anotasi data tambahan untuk divalidasi saat model terikat ke formulir:

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

Aktifkan tombol kirim berdasarkan validasi formulir

Untuk mengaktifkan dan menonaktifkan tombol kirim berdasarkan validasi formulir, contoh berikut:

  • Menggunakan versi singkat dari formulir (Starship3komponen) sebelumnya Starfleet Starship Database dari bagian Formulir contoh dari artikel Komponen input yang hanya menerima nilai untuk Id kapal. Properti lain Starship menerima nilai default yang valid saat instans jenis Starship dibuat.
  • Menggunakan formulir EditContext untuk menetapkan model saat komponen diinisialisasi.
  • Memvalidasi formulir dalam panggilan balik konteks OnFieldChanged untuk mengaktifkan dan menonaktifkan tombol kirim.
  • IDisposable Menerapkan dan berhenti berlangganan penanganan aktivitas dalam Dispose metode . Untuk informasi lebih lanjut, lihat siklus hidup komponen Razor ASP.NET Core.

Catatan

Saat menetapkan ke EditForm.EditContext, jangan juga menetapkan EditForm.Model ke EditForm.

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: Processing 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" 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: Processing 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;
        }
    }
}

Jika formulir tidak dimuat sebelumnya dengan nilai yang valid dan Anda ingin menonaktifkan Submit tombol pada pemuatan formulir, atur formInvalid ke true.

Efek samping dari pendekatan sebelumnya adalah bahwa ringkasan validasi (ValidationSummary komponen) diisi dengan bidang yang tidak valid setelah pengguna berinteraksi dengan satu bidang. Atasi skenario ini dengan salah satu cara berikut:

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