注意
這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明如何在 Blazor 表單中使用驗證。
表單驗證
在基本表單驗證案例中,EditForm 執行個體可以使用宣告的 EditContext 和 ValidationMessageStore 執行個體來驗證表單欄位。 OnValidationRequested 的 EditContext 事件處理常式會執行自訂驗證邏輯。 處理常式的結果會更新 ValidationMessageStore 執行個體。
在表單模型定義於裝載表單的元件內,可能作為元件的直接成員或子類別的情況下,基本表單驗證很有用。 跨數個元件之間使用獨立模型類別時,建議使用驗證程式元件。
在 Blazor Web App中,用戶端驗證需要有效的 BlazorSignalR 線路。 用戶端驗證不適用於已採用靜態伺服器端轉譯 (靜態 SSR) 的元件中的表單。 採用靜態 SSR 的表單在提交表單後會在伺服器上進行驗證。
在下列元件中,HandleValidationRequested 處理常式方法會在驗證表單之前呼叫 ValidationMessageStore.Clear 來清除任何現有的驗證訊息。
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;
}
}
}
資料註釋驗證元件及自訂驗證
DataAnnotationsValidator 元件會將資料註釋驗證附加至串接的 EditContext。 啟用資料註釋驗證需要 DataAnnotationsValidator 元件。 若要使用與資料註釋不同的驗證系統,請使用自訂實作,而不是 DataAnnotationsValidator 元件。 DataAnnotationsValidator 的架構實作可在參考來源中檢查:
如需驗證行為的詳細資訊,請參閱 DataAnnotationsValidator 驗證行為 一節。
如果您需要在程式代碼中啟用 EditContext 的數據批註驗證支援,請在 EnableDataAnnotationsValidation上使用插入 IServiceProvider (@inject IServiceProvider ServiceProvider) 呼叫 EditContext。 如需進階範例,請參閱 ASP.NET Core NotifyPropertyChangedValidationComponent 架構 Blazor (BasicTestApp GitHub 存放庫) dotnet/aspnetcore 元件。 在範例的生產版本中,將服務提供者的 new TestServiceProvider() 自變數取代為插入的 IServiceProvider。
注意
.NET 參考文件的連結通常會載入儲存庫的預設分支,此分支代表 .NET 的下一個版本正在進行的開發工作。 若要為特定版本選擇標籤,請使用 [切換分支或標籤] 下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
Blazor 會執行兩種類型的驗證:
- 當使用者使用 Tab 離開欄位時,就會執行欄位驗證。 在欄位驗證期間,DataAnnotationsValidator 元件會將所有報告的驗證結果與欄位產生關聯。
- 當使用者提交表單時,就會執行模型驗證。 在模型驗證期間,DataAnnotationsValidator 元件會嘗試根據驗證結果所報告的成員名稱來判斷欄位。 未與個別成員相關聯的驗證結果會與模型 (而不是欄位) 建立關聯。
在自訂驗證案例中:
- 驗證會管理表單 ValidationMessageStore的 EditContext。
- DataAnnotationsValidator 元件可用來根據 驗證屬性(數據批注),將驗證支援附加至窗體。
有兩種達成自定義驗證的一般方法,本文接下來的兩節會說明:
-
使用
OnValidationRequested事件手動驗證:當驗證要求來自指派給 OnValidationRequested 事件的事件處理程式時,手動使用資料註解和自訂程式碼來驗證表單的欄位。 - 驗證程式元件:一或多個自定義驗證程式元件可用來處理相同頁面上不同表單的驗證,或在表單處理的不同步驟中處理相同表單(例如,用戶端驗證後面接著伺服器驗證)。
使用 OnValidationRequested 事件手動驗證
您可以使用指定給 EditContext.OnValidationRequested 事件的自訂事件處理程式手動驗證表單,以管理 ValidationMessageStore。
Blazor 架構提供 DataAnnotationsValidator 元件,根據 驗證屬性(數據批注),將額外的驗證支援附加至表單。
回想先前的 Starship8 元件範例,HandleValidationRequested 方法會指派給 OnValidationRequested,您可以在其中在 C# 程式代碼中執行手動驗證。 一些變更展示了如何結合現有的手動驗證與透過 DataAnnotationsValidator 的數據批註驗證,以及將驗證屬性套用至 Holodeck 模型。
參考元件定義檔頂端元件 System.ComponentModel.DataAnnotations 指示詞中的 Razor 命名空間:
@using System.ComponentModel.DataAnnotations
使用驗證屬性將 Id 屬性新增至 Holodeck 模型,以將字元串的長度限制為六個字元:
[StringLength(6)]
public string? Id { get; set; }
將 DataAnnotationsValidator 元件 (<DataAnnotationsValidator />) 新增至表單。 一般而言,元件會放在 <EditForm> 標籤底下,但您可以將它放在表單中的任何位置:
<DataAnnotationsValidator />
將 <EditForm> 標籤的表單提交行為從 OnSubmit 更改為 OnValidSubmit,這可確保在執行指派的事件處理方法之前檢查表單是否有效。
- OnSubmit="Submit"
+ OnValidSubmit="Submit"
在 <EditForm>中,新增 Id 屬性的欄位:
<div>
<label>
<InputText @bind-Value="Model!.Id" />
ID (6 characters max)
</label>
<ValidationMessage For="() => Model!.Id" />
</div>
進行上述變更之後,表單的行為會符合下列規格:
- 當
Id欄位只會失去焦點時,Id屬性上的數據批註驗證不會觸發驗證失敗。 當使用者選取 [Update] 按鈕時,就會執行驗證。 - 當使用者選取表單的 [
HandleValidationRequested] 按鈕時,任何您要在分配給表單 [OnValidationRequested] 事件的Update方法中執行的手動驗證將被執行。 在Starship8元件範例的現有程式代碼中,用戶必須選取或兩個複選框來驗證表單。 - 在數據批注和手動驗證通過之前,表單不會處理
Submit方法。
驗證元件
驗證程式元件藉由管理表單 ValidationMessageStore 的 EditContext 來支援表單驗證。
Blazor 架構會提供 DataAnnotationsValidator 元件,以根據驗證屬性 (資料註釋) 將驗證支援附加至表單。 您可以建立自訂驗證程式元件,以便為相同頁面上的不同表單或是表單處理的不同步驟 (例如,用戶端驗證後面接著伺服器驗證) 的相同表單處理驗證訊息。 本節所示的驗證程式元件範例 CustomValidation,會用於本文的下列各節:
在 資料註釋內建驗證器中,只有 [Remote] 不受支援。
注意
在許多情況下,可以使用自訂資料註釋驗證屬性,而不是自訂驗證程式元件。 套用至表單模型的自訂屬性會隨著使用 DataAnnotationsValidator 元件而啟動。 搭配使用伺服器驗證時,套用至模型的任何自訂屬性必須可在伺服器上執行。 如需詳細資訊,請參閱 自訂驗證屬性 章節。
從 ComponentBase 建立驗證程式元件:
- 表單的 EditContext 是元件的串聯參數。
- 初始化驗證程式元件時,會建立新的 ValidationMessageStore,以維護目前的表單錯誤清單。
- 當表單元件中的開發人員程式碼呼叫
DisplayErrors方法時,訊息存放區會收到錯誤。 錯誤會傳遞至DisplayErrors中的Dictionary<string, List<string>>方法。 在字典中,鍵是表示一個或多個錯誤的表單欄位名稱。 值是錯誤清單。 - 發生下列任何一項時,會清除訊息:
- 引發 EditContext 事件時,會在 OnValidationRequested 上要求驗證。 清除所有錯誤。
- 引發 OnFieldChanged 事件時,表單中的欄位會變更。 只清除欄位的錯誤。
-
ClearErrors方法會透過開發人員程式碼呼叫。 清除所有錯誤。
更新下列類別中的命名空間,以符合您應用程式的命名空間。
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();
}
}
重要
衍生自 時,ComponentBase 指定命名空間。 無法指定命名空間會導致建置錯誤:
Tag helpers cannot target tag name '<global namespace>.{CLASS NAME}' because it contains a ' ' character.
{CLASS NAME} 預留位置是元件類別名稱。 本節中的自訂驗證程式範例會指定範例命名空間 BlazorSample。
注意
匿名的 lambda 運算式在上述範例中被註冊為OnValidationRequested 和 OnFieldChanged 的事件處理常式。 在此案例中,不需要實作 IDisposable 和取消訂閱事件委派。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置。
使用驗證程式元件的商務邏輯驗證
針對一般商務邏輯驗證,請使用驗證程式元件來接收字典中的表單錯誤。
當表單的模型在裝載表單的元件內定義,並直接作為該元件或其子類別的成員時,基本驗證會很有用。 跨數個元件之間使用獨立模型類別時,建議使用驗證程式元件。
在以下範例中:
- 使用了
Starfleet Starship Database一文中Starship3小節的 表單 ( 元件) 的縮減版本,其僅接受星際飛船的分類和描述。 因為 DataAnnotationsValidator 元件未包含在表單中,因此在表單提交時不會觸發資料註釋驗證。 - 本文的
CustomValidation小節會使用 元件。 - 如果使用者選取 "
Description" 飛船分類 (Defense),則驗證需要提供飛船的描述值 (Classification)。
在元件中設定驗證訊息時,其會新增至驗證程式的 ValidationMessageStore,並顯示在 EditForm 的驗證摘要中。
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),
[ "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),
[ "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");
}
}
}
注意
除了使用驗證元件,也可以使用資料註釋驗證屬性。 套用至表單模型的自訂屬性會隨著使用 DataAnnotationsValidator 元件而啟動。 搭配伺服器驗證使用時,屬性必須可在伺服器上執行。 如需詳細資訊,請參閱 自訂驗證屬性 章節。
使用驗證程式元件的伺服器驗證
本節專注於Blazor Web App情境,但任何使用伺服器驗證搭配 Web API 的應用程式類型,都會採用相同的一般方法。
本節著重於裝載的 Blazor WebAssembly 案例,但使用伺服器驗證搭配 Web API 的任何類型的應用程式會採用相同的一般方法。
除了用戶端驗證之外,還支援伺服器驗證:
- 使用 DataAnnotationsValidator 元件處理表單中的用戶端驗證。
- 當表單通過用戶端驗證 (呼叫 OnValidSubmit),將 EditContext.Model 傳送至後端伺服器 API 以進行表單處理。
- 在伺服器上處理模型驗證。
- 伺服器 API 包含內建架構資料註釋驗證和開發人員提供的自訂驗證邏輯。 如果驗證在伺服器上通過,則處理表單,並送回成功狀態碼 (
200 - OK)。 如果驗證失敗,則傳回失敗狀態碼 (400 - Bad Request) 和欄位驗證錯誤。 - 在成功時停用表單,或顯示錯誤。
當表單的模型在裝載表單的元件內定義,並直接作為該元件或其子類別的成員時,基本驗證會很有用。 跨數個元件之間使用獨立模型類別時,建議使用驗證程式元件。
下列範例是基於:
- Blazor Web App 包含了從 Blazor Web App 專案範本建立的互動式 WebAssembly 元件。
-
Starship一文中Starship.cs小節的 模型 ()。 -
CustomValidation小節中顯示的 元件。
將 Starship 模型 (Starship.cs) 放入共用類別庫專案中,讓用戶端和伺服器專案都可使用模型。 新增或更新命名空間,以符合共用應用程式的命名空間 (例如 namespace BlazorSample.Shared)。 由於此模型需要資料註釋,請確認共用類別庫使用共用架構,或將 System.ComponentModel.Annotations 套件新增至共用專案。
在 Blazor Web App的主要專案中,新增控制器來處理星際飛船驗證要求,並傳回失敗驗證訊息。 更新共用類別庫專案的最後一個 using 陳述式中的命名空間,以及控制器類別的 namespace。 在用戶端和伺服器的資料註釋驗證之外,如果使用者選擇了Description 飛船分類 (Defense),控制器還會驗證是否提供了飛船描述 (Classification) 的值。
- 從 Blazor WebAssembly 建立的託管Blazor WebAssembly。 Blazor中所述的任何安全性托管Blazor WebAssembly都支援該方法。
-
Starship一文中Starship.cs小節的 模型 ()。 -
CustomValidation小節中顯示的 元件。
將 Starship 模型 (Starship.cs) 放入方案 Shared 的專案,讓用戶端和伺服器應用程式都可以使用模型。 新增或更新命名空間,以符合共用應用程式的命名空間 (例如 namespace BlazorSample.Shared)。 由於模型需要資料註釋,因此請將 System.ComponentModel.Annotations 封裝新增至 Shared 專案。
在 Server 專案中,新增控制器來處理星際飛船驗證要求,並傳回失敗的驗證訊息。 更新 using 專案最後一個 Shared 陳述式中的命名空間,以及控制器類別的 namespace。 在用戶端和伺服器的資料註釋驗證之外,如果使用者選擇了Description 飛船分類 (Defense),控制器還會驗證是否提供了飛船描述 (Classification) 的值。
Defense 船舶分類的驗證僅在控制器的伺服器上進行,因為即將推出的表單在提交至伺服器時,不會在用戶端執行相同的驗證。 沒有用戶端驗證的伺服器驗證,在要求在伺服器上進行使用者輸入的私人商務邏輯驗證的應用程式中很常見。 例如,可能需要針對使用者儲存資料的私人資訊,才能驗證使用者輸入。 私人資料顯然無法傳送至用戶端以進行用戶端驗證。
注意
本節中的 StarshipValidation 控制器會使用 Microsoft Identity 2.0。 Web API 只接受具有此 API "API.Access" 範圍的使用者之權杖。 如果 API 的範圍名稱與 API.Access 不同,則需要額外的自訂。
如需安全性的詳細資訊,請參閱:
- ASP.NET Core Blazor 驗證和授權 (以及Blazor安全性和 Identity 節點中的其他文章)
- Microsoft身分識別平台文件
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 = [ "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);
}
}
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);
}
}
確認或更新上述控制器的命名空間 (BlazorSample.Server.Controllers),以符合應用程式的控制器命名空間。
當伺服器上發生模型繫結驗證錯誤時,ApiController (ApiControllerAttribute) 通常會傳回預設錯誤要求回應與 ValidationProblemDetails。 當 Starfleet Starship Database 表單的所有欄位未提交且表單驗證失敗時,回應會包含不僅是驗證錯誤的資料,如下列範例所示:
{
"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)." ]
}
}
注意
若要示範上述 JSON 回應,您必須停用表單的用戶端驗證,以允許空白欄位表單提交,或使用工具將要求直接傳送至伺服器 API,例如 Firefox Browser Developer。
如果伺服器 API 傳回上述的預設 JSON 回應,用戶端可以在開發人員程式碼中剖析回應,以取得 errors 節點的子系,以進行表單驗證錯誤處理。 撰寫開發人員程式碼來剖析檔案並不方便。 手動剖析 JSON 需要在呼叫 Dictionary<string, List<string>> 之後產生一個錯誤列表 ReadFromJsonAsync。 理想情況下,伺服器 API 應該僅傳回驗證錯誤,如下列範例所示:
{
"Id": [ "The Id field is required." ],
"Classification": [ "The Classification field is required." ],
"IsValidatedDesign": [ "This form disallows unapproved ships." ],
"MaximumAccommodation": [ "Accommodation invalid (1-100000)." ]
}
若要修改伺服器 API 的回應,使其只傳回驗證錯誤,請變更在 ApiControllerAttribute 檔案中具有註釋 Program 的動作上叫用的委派。 針對 API 端點 (/StarshipValidation),返回一個 BadRequestObjectResult 並附帶 ModelStateDictionary。 針對任何其他 API 端點,傳回包含新的 ValidationProblemDetails 的物件結果,以保留預設行為。
將 Microsoft.AspNetCore.Mvc 命名空間新增至 Program主要專案中的 Blazor Web App 檔案頂端:
using Microsoft.AspNetCore.Mvc;
在 Program 檔案中,新增或更新下列 AddControllersWithViews 擴充方法,並新增下列對 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));
}
};
});
如果您是第一次將控制器新增至 Blazor Web App 的主要專案,當您放置上述註冊控制器服務的程式碼時,請映射控制器的端點。 下列範例使用預設控制器路由:
app.MapDefaultControllerRoute();
注意
上述範例透過呼叫 AddControllersWithViews 來顯式註冊控制器服務,以自動減輕跨網站偽造要求 (XSRF/CSRF) 攻擊。 如果您僅使用 AddControllers,則不會自動啟用防偽功能。
如需控制器路由和驗證失敗錯誤回應的詳細資訊,請參閱下列資源:
在 .Client 專案中,新增CustomValidation小節中顯示的 元件。 更新命名空間以符合應用程式 (例如,namespace BlazorSample.Client)。
在 .Client 專案中,Starfleet Starship Database 表單會更新,以在 CustomValidation 元件的協助下顯示伺服器驗證錯誤。 當伺服器 API 傳回驗證訊息時,它們會新增至 CustomValidation 元件的 ValidationMessageStore。 表單的錯誤可在 EditContext 中顯示,並由表單的驗證摘要呈現。
在下列元件中,將共用專案的命名空間 (@using BlazorSample.Shared) 更新為共用專案的命名空間。 請注意,表單需要授權,因此使用者必須登入應用程式,才能瀏覽至表單。
將 Microsoft.AspNetCore.Mvc 命名空間新增至 Program 應用程式中 Server 檔案的頂端:
using Microsoft.AspNetCore.Mvc;
在 Program 檔案中,找出 AddControllersWithViews 擴充方法,並新增 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));
}
};
});
注意
上述範例透過呼叫 AddControllersWithViews 來顯式註冊控制器服務,以自動減輕跨網站偽造要求 (XSRF/CSRF) 攻擊。 如果您僅使用 AddControllers,則不會自動啟用防偽功能。
在 Client 專案中,新增CustomValidation小節中顯示的 元件。 更新命名空間以符合應用程式 (例如,namespace BlazorSample.Client)。
在 Client 專案中,Starfleet Starship Database 表單會更新,以在 CustomValidation 元件的協助下顯示伺服器驗證錯誤。 當伺服器 API 傳回驗證訊息時,它們會新增至 CustomValidation 元件的 ValidationMessageStore。 表單的錯誤可在 EditContext 中顯示,並由表單的驗證摘要呈現。
在下列元件中,將 Shared 專案的命名空間 (@using BlazorSample.Shared) 更新為共用專案的命名空間。 請注意,表單需要授權,因此使用者必須登入應用程式,才能瀏覽至表單。
Starship10.razor:
注意
以 EditForm 為基礎的表單會自動啟用防偽支援。 控制器應使用 AddControllersWithViews 來註冊控制器服務,並自動啟用 Web API 的防偽支援。
@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
{
using 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.";
}
}
}
.Client 的 Blazor Web App 專案也必須向後端 Web API 控制器註冊 HTTP POST 要求的 HttpClient。 確認下列內容或將其新增至 .Client 專案的 Program 檔案:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
上述範例使用 builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) 設定應用程式的基底位址,該位址通常是由主機頁面中的 <base>href 標籤值衍生而來。
@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
{
using 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.";
}
}
}
注意
除了使用驗證元件,也可以使用資料註釋驗證屬性。 套用至表單模型的自訂屬性會隨著使用 DataAnnotationsValidator 元件而啟動。 搭配伺服器驗證使用時,屬性必須可在伺服器上執行。 如需詳細資訊,請參閱 自訂驗證屬性 章節。
注意
本節中的伺服器驗證方法適合此文件集中任何裝載的 Blazor WebAssembly 方案範例:
根據輸入事件的 InputText
使用 InputText 元件來建立使用 oninput 事件 (input) 而不是 onchange 事件 (change) 的自訂元件。 在每次按鍵輸入時使用input事件觸發欄位驗證。
下列 CustomInputText 元件會繼承架構的 InputText 元件,並將事件繫結設定為 oninput 事件 (input)。
CustomInputText.razor:
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
您可以在使用 CustomInputText 的任何位置使用 InputText 元件。 下列元件使用共用的 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; }
}
}
驗證摘要和驗證訊息元件
ValidationSummary 元件會摘要所有驗證訊息,其類似於驗證摘要標籤協助程式:
<ValidationSummary />
使用 Model 參數輸出特定模型的驗證訊息:
<ValidationSummary Model="Model" />
ValidationMessage<TValue> 元件會顯示特定欄位的驗證訊息,其類似於驗證訊息標籤協助程式。 使用 For 屬性指定用於驗證的欄位,以及命名模型屬性的 Lambda 運算式:
<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />
ValidationMessage<TValue> 和 ValidationSummary 元件支援任意屬性。 不符合元件參數的任何屬性都會新增至產生的 <div> 或 <ul> 元素。
控制應用程式的樣式表 (wwwroot/css/app.css 或 wwwroot/css/site.css) 中的驗證訊息樣式。 預設 validation-message 類別會將驗證訊息的文字色彩設定為紅色:
.validation-message {
color: red;
}
判斷表單欄位是否有效
在未取得驗證訊息的情況下,使用 EditContext.IsValid 來判斷欄位是否有效。
支援,但不建議:
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
建議:
var isValid = editContext.IsValid(fieldIdentifier);
自訂驗證屬性
若要在使用自訂驗證屬性時,確保驗證結果與欄位正確相關聯,請在建立 MemberName 時傳遞驗證內容的 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.",
[ validationContext.MemberName! ]);
}
}
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! });
}
}
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 });
}
}
透過 ValidationContext 將服務注入至自訂驗證屬性。 下列範例示範一個使用相依性注入 (DI) 驗證使用者輸入的沙拉主廚表格。
SaladChef 類別表示已核准用於 Ten Forward 沙拉的星際飛船食材清單。
SaladChef.cs:
namespace BlazorSample;
public class SaladChef
{
public string[] SaladToppers = { "Horva", "Kanda Root", "Krintar", "Plomeek",
"Syto Bean" };
}
在應用程式的 DI 容器中註冊 SaladChef,位於 Program 檔案中:
builder.Services.AddTransient<SaladChef>();
下列 IsValid 類別的 SaladChefValidatorAttribute 方法會從 DI 取得 SaladChef 服務,以檢查使用者的輸入。
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));
}
}
下列元件會藉由將 SaladChefValidatorAttribute ([SaladChefValidator]) 套用至沙拉食材字串 (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);
}
自訂驗證 CSS 類別屬性
與 CSS 架構整合時 (例如 Bootstrap),自訂驗證 CSS 類別屬性很有用。
若要指定自訂驗證 CSS 類別屬性,請從提供自訂驗證的 CSS 樣式開始。 在下列範例中,會指定有效 (validField) 和無效 (invalidField) 的樣式。
將下列 CSS 類別新增至應用程式的樣式表:
.validField {
border-color: lawngreen;
}
.invalidField {
background-color: tomato;
}
建立衍生自 FieldCssClassProvider 的類別,以檢查欄位驗證訊息,並套用適當的有效或無效樣式。
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";
}
}
使用 CustomFieldClassProvider 將 EditContext 類別設定為表單 SetFieldCssClassProvider 執行個體上的欄位 CSS 類別提供者。
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; }
}
}
上述範例會檢查所有表單欄位的有效性,並將樣式套用至每個欄位。 如果表單只應該將自訂樣式套用至欄位的子集,請有條件地讓 CustomFieldClassProvider 套用樣式。 下列 CustomFieldClassProvider2 範例只會將樣式套用至 Name 欄位。 針對名稱不符合 Name 的任何欄位,會傳回 string.Empty,而且不會套用任何樣式。 使用反射,欄位會對應到模型成員的屬性或欄位名稱,而不是指派給 HTML 實體的 id。
CustomFieldClassProvider2.cs:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = editContext.IsValid(fieldIdentifier);
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
注意
比對上述範例中的欄位名稱會區分大小寫,因此指定 "Name" 的模型屬性成員必須符合 "Name" 的條件式檢查:
-
正確比對:
fieldId.FieldName == "Name" -
無法比對:
fieldId.FieldName == "name" -
無法比對:
fieldId.FieldName == "NAME" -
無法比對:
fieldId.FieldName == "nAmE"
將其他屬性新增至 Model,例如:
[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; }
將 Description 新增至 CustomValidationForm 元件的表單:
<InputText @bind-Value="Model!.Description" />
更新元件的EditContext方法中的OnInitialized實例,使其使用新的欄位 CSS 類別提供者。
editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
因為 CSS 驗證類別未套用至 Description 欄位,不會設定樣式。 不過,欄位驗證會正常執行。 如果提供超過 10 個字元,驗證摘要會指出錯誤:
描述太長。
在以下範例中:
自訂 CSS 樣式會套用至
Name欄位。任何其他欄位會套用類似 Blazor 預設邏輯的邏輯,並使用 Blazor 的預設欄位 CSS 驗證樣式
modified搭配valid或invalid。 請注意,針對預設樣式,如果應用程式是以 Blazor 專案範本為基礎,則不需要將它們新增至應用程式的樣式表。 針對不是以 Blazor 專案範本為基礎的應用程式,可以將預設樣式新增至應用程式的樣式表:.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 方法中的 OnInitialized 執行個體,以使用以上的欄位 CSS 類別提供者:
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
使用 CustomFieldClassProvider3:
-
Name欄位會使用應用程式的自訂驗證 CSS 樣式。 -
Description欄位使用的邏輯類似 Blazor 的邏輯和 Blazor 的預設欄位 CSS 驗證樣式。
在 IValidatableObject 中進行類別級別驗證
使用 IValidatableObject 進行類別層級驗證(參見 API 文件)為 Blazor 表單模型提供支援。
IValidatableObject 只有在提交表單,且在所有其他驗證都成功時,才會執行驗證。
Blazor 資料註釋驗證套件
注意
不再建議將套件Microsoft.AspNetCore.Components.DataAnnotations.Validation用於以 .NET 10 或更新版本為目標的應用程式。 如需詳細資訊,請參閱 巢狀物件、集合類型和複雜類型 一節。
套件會Microsoft.AspNetCore.Components.DataAnnotations.Validation使用元件填DataAnnotationsValidator補驗證體驗差距。 套件目前是實驗性的。
警告
該 Microsoft.AspNetCore.Components.DataAnnotations.Validation 套件 具有最新版本的 候選版本 ,位於 NuGet.org。此時繼續使用 實驗性 候選版本套件。 實驗性功能僅供您探索功能的可行性,穩定版內不一定會有此功能。 觀看公告 GitHub 存放庫、dotnet/aspnetcore GitHub 存放庫或本主題章節,以取得進一步的更新。
[CompareProperty] 屬性
CompareAttribute 無法與 DataAnnotationsValidator 元件搭配運作,因為 DataAnnotationsValidator 不會將驗證結果與特定成員產生關聯。 這可能會導致欄位層級驗證與提交時驗證整個模型時的行為不一致。
Microsoft.AspNetCore.Components.DataAnnotations.Validation
實驗性套件引進了額外的驗證屬性,ComparePropertyAttribute可解決這些限制。 在 Blazor 應用程式中,[CompareProperty] 是 [Compare] 屬性的直接取代項目。
巢狀物件和集合類型
Blazor 表單驗證包含對巢狀物件及集合項目屬性驗證的內建支援 DataAnnotationsValidator。
若要建立已驗證的表單,請在 DataAnnotationsValidator 元件內使用 EditForm 元件,就像之前一樣。
若要加入巢狀物件和集合類型驗證功能:
- 在AddValidation檔案中呼叫
Program擴充方法來註冊服務。 - 在 C# 類別檔案中宣告表單模型類型,而不是在 Razor 元件中(
.razor)。 - 請使用
[ValidatableType]屬性來註解根表單模型類型。
若未遵循上述步驟,表單驗證行為將不包含巢狀模型和集合類型的驗證。
下列範例示範已改進表單驗證的客戶訂單(為簡潔起見,省略詳細說明):
在Program.cs中,於服務集合呼叫AddValidation:
builder.Services.AddValidation();
在以下 Order 類別中,[ValidatableType] 屬性是最上層模型類型所需的。 系統會自動探索其他類型。
OrderItem 和 ShippingAddress 不會為了簡潔起見而顯示,但是巢狀和集合驗證在顯示這些類型時的運作方式相同。
Order.cs:
using System.ComponentModel.DataAnnotations;
[ValidatableType]
public class Order
{
public Customer Customer { get; set; } = new();
public List<OrderItem> OrderItems { get; set; } = [];
}
public class Customer
{
[Required(ErrorMessage = "Name is required.")]
public string? FullName { get; set; }
[Required(ErrorMessage = "Email is required.")]
public string? Email { get; set; }
public ShippingAddress ShippingAddress { get; set; } = new();
}
在下列 OrderPage 元件中, DataAnnotationsValidator 元件存在於元件中 EditForm 。
OrderPage.razor:
<EditForm Model="Model">
<DataAnnotationsValidator />
<h3>Customer Details</h3>
<div class="mb-3">
<label>
Full Name
<InputText @bind-Value="Model!.Customer.FullName" />
</label>
<ValidationMessage For="@(() => Model!.Customer.FullName)" />
</div>
// ... form continues ...
</EditForm>
@code {
public Order? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
}
在 Razor 元件(.razor 檔案)之外宣告模型類型的需求,是因為新的驗證功能和 Razor 編譯器本身都使用了來源產生器。 目前,一個來源產生器的輸出無法做為另一個來源產生器的輸入。
關於使用不同組件驗證模型的指引,請參閱「 使用不同組裝組件的驗證模型 」章節。
巢狀物件、集合類型和複雜類型
注意
對於以 .NET 10 或更新版本為目標的應用程式,我們不再建議使用本節所述的Microsoft.AspNetCore.Components.DataAnnotations.Validation實驗套件和方法。 我們建議使用元件的 DataAnnotationsValidator 內建驗證功能。
Blazor 支援使用資料註釋搭配內建的 DataAnnotationsValidator 來驗證表單輸入。 不過,在 .NET 9 或更早版本中, DataAnnotationsValidator 只會驗證系結至表單之模型的最上層屬性,這些屬性不是集合或複雜類型屬性。
若要驗證系結模型的整個物件圖形,包括集合和複雜類型屬性,請使用 .NET 9 或更早版本中ObjectGraphDataAnnotationsValidator所提供的 :Microsoft.AspNetCore.Components.DataAnnotations.Validation
<EditForm ...>
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>
使用 [ValidateComplexType] 標註模型屬性。 在下列模型類別中,ShipDescription 類別包含模型繫結至表單時要驗證的其他資料註釋:
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; }
}
使用不同程序集的驗證模型
對於在不同組合中定義的模型驗證,例如資源庫或 .Client 專案 Blazor Web App:
- 如果程式庫是一般類別庫(不是基於
Microsoft.NET.Sdk.Web或Microsoft.NET.Sdk.RazorSDK),請新增Microsoft.Extensions.ValidationNuGet 套件的套件引用。 對於純類別函式庫,則需額外步驟,稍後將說明。 - 在程式庫或
.Client專案中建立方法,以接收 IServiceCollection 實例作為引數並呼叫 AddValidation 它。 - 在應用程式中,同時呼叫 method 和 AddValidation.
上述方法會導致確認兩個元件中的型別。
在下列範例中,為AddValidationForTypesInClient專案的.Client建立Blazor Web App方法,以使用.Client專案中定義的類型進行驗證。
ServiceCollectionExtensions.cs (在 .Client 項目中):
namespace BlazorSample.Client.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddValidationForTypesInClient(
this IServiceCollection collection)
{
return collection.AddValidation();
}
}
在伺服器專案的Program檔案中,新增命名空間,並呼叫.Client專案的服務集合擴充方法AddValidationForTypesInClient 和AddValidation。
using BlazorSample.Client.Extensions;
...
builder.Services.AddValidationForTypesInClient();
builder.Services.AddValidation();
套件中 Microsoft.Extensions.Validation 新增的屬性(ValidatableTypeAttribute 和 SkipValidationAttribute)會在 .NET 10 中以 實驗性 形式發佈。 此套件旨在提供跨框架驗證功能的共享新基礎設施,而發佈實驗型態則為公開 API 的最終設計提供更大彈性,以提升框架消費時的支援。
在 Blazor 應用程式中,型別是透過生成的嵌入屬性提供的。 如果使用 Microsoft.NET.Sdk.Web SDK (<Project Sdk="Microsoft.NET.Sdk.Web">) 的網頁應用程式專案或使用 Microsoft.NET.Sdk.Razor SDK (<Project Sdk="Microsoft.NET.Sdk.Razor">) 的 RCL 包含 Razor 元件(.razor),框架會自動在專案內產生內部屬性(Microsoft.Extensions.Validation.Embedded.ValidatableType, Microsoft.Extensions.Validation.Embedded.SkipValidation)。 這些類型與實際屬性可互換,且不標示為實驗性。 在大多數情況下,開發者會直接使用 [ValidatableType]/[SkipValidation] 類別上的屬性,而不在意其來源。
然而,上述方法在使用 Microsoft.NET.Sdk SDK 的純類別函式庫中並不適用。<Project Sdk="Microsoft.NET.Sdk"> 在純類別函式庫中使用這些型別會引發程式碼分析警告:
ASP0029:「Microsoft.Extensions.Validation.ValidatableTypeAttribute」僅供評估用途,未來更新中可能會變更或移除。 隱藏此診斷以繼續進行。
可透過以下任一方法抑制警告:
專案檔案中的一個
<NoWarn>屬性:<PropertyGroup> <NoWarn>$(NoWarn);ASP0029</NoWarn> </PropertyGroup>pragma一個使用屬性的指令:#pragma warning disable ASP0029 [Microsoft.Extensions.Validation.ValidatableType] #pragma warning restore ASP0029EditorConfig 檔案(
.editorconfig) 規則:dotnet_diagnostic.ASP0029.severity = none
如果抑制警告無法接受,請在函式庫中手動建置由 Web 和 Razor SDK 自動生成的嵌入屬性。
ValidatableTypeAttribute.cs:
namespace Microsoft.Extensions.Validation.Embedded
{
[AttributeUsage(AttributeTargets.Class)]
internal sealed class ValidatableTypeAttribute : Attribute
{
}
}
使用精確的命名空間(Microsoft.Extensions.Validation.Embedded)和類別名稱(ValidatableTypeAttribute),讓驗證來源產生器能偵測並使用該類型。 你可以為命名空間宣告一個全域 using 語句,可以使用 global using Microsoft.Extensions.Validation.Embedded; 語句,或在庫的專案檔中使用 <Using Include="Microsoft.Extensions.Validation.Embedded" /> 項目。
無論採用哪種方法,請在程式碼中註明現有的解決方法,以備未來更新之用。 計畫於 .NET 11(2026 年 11 月)推出框架更新,以簡化純類別函式庫中驗證型別的採用。
根據表單驗證啟用提交按鈕
若要根據表單驗證啟用和停用提交按鈕,下列範例:
- 使用
Starfleet Starship Database一文中Starship3小節的舊版 表單 ( 元件) 的縮減版本,其僅接受飛船識別碼的值。建立Starship型別的執行個體時,其他Starship屬性會收到有效的預設值。 - 使用表單的 EditContext,在元件初始化時指派模型。
- 驗證上下文的OnFieldChanged回調函數中的表單,以啟用或停用提交按鈕。
- 實作 IDisposable 並取消訂閱
Dispose方法中的事件處理常式。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置。
注意
指派給 EditForm.EditContext 時,請勿同時將 EditForm.Model 指派給 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;
}
}
}
如果表單未預先載入有效值,而且您想要停用表單載入上的 Submit 按鈕,請將 formInvalid 設定為 true。
上述方法的副作用是驗證摘要 (ValidationSummary 元件) 會在使用者與任何一個欄位互動之後填入無效的欄位。 以下列其中一種方式解決此案例:
- 請勿在表單上使用 ValidationSummary 元件。
- 選取 [提交] 按鈕時,讓 ValidationSummary 元件可見 (例如,在
Submit方法中)。
<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";
}
}
DataAnnotationsValidator 驗證行為
該 DataAnnotationsValidator 元件具有與 相同的 System.ComponentModel.DataAnnotations.Validator驗證順序和短路行為。 驗證型別 T的執行個體時,會套用下列規則:
- 的
T成員屬性會經過驗證,包括遞迴驗證巢狀物件。 - 的
T類型層級屬性已驗證。 -
IValidatableObject.Validate如果實作方法,則
T會執行該方法。
如果上述其中一個步驟產生驗證錯誤,則會略過其餘步驟。