ASP.NET Core Blazor 在靜態 SSR 中的客戶端表單驗證

本文說明如何 Blazor 為使用 靜態伺服器端渲染(static SSR)的表單加入即時客戶端驗證。 瀏覽器會在使用者編輯欄位時驗證個別欄位,並在提交前驗證完整表單。 若客戶端檢查通過,表單會提交並在伺服器上再次驗證。

使用互動式渲染模式的表單並未使用本文所述的靜態 SSR 用戶端驗證功能。 他們的每個欄位驗證與完整表單提交驗證皆以 .NET 格式執行,透過表單的 EditContext。

客戶端驗證的運作方式

當靜態 SSR 表單包含 DataAnnotationsValidator 元件時,會 Blazor 將表單的驗證規則渲染到頁面中,並在表單提交前使用 JavaScript 強制執行。 使用者在不需往返伺服器的情況下會看到驗證錯誤。

當符合以下條件時,客戶端驗證會自動啟動:

對於內建的驗證屬性集合,則不需要 JavaScript 設定、額外套件或服務註冊。 「自訂用戶端驗證規則」章節說明如何新增對自訂驗證屬性的支援。

Important

客戶端驗證是提升使用者體驗,而非權威驗證通過。 可透過停用或修改瀏覽器的 JavaScript 執行來繞過此問題。 伺服器端驗證會在表單發布後執行,且保持權威性。 切勿依賴用戶端驗證來保護資料完整性。

支援的驗證屬性

以下 System.ComponentModel.DataAnnotations 屬性在用戶端強制執行,與伺服器端資料註解行為相符:

未出現在此清單中的驗證屬性,包括衍生自 ValidationAttribute 的自訂屬性,預設不會在用戶端強制執行。 表格提交後,它們仍會繼續在伺服器端運行。 若要提供自訂屬性的客戶端規則,請參閱 自訂客戶端驗證規則 章節。

驗證時序

欄位在其值被提交時即為驗證。 對於文字輸入(<input> 元素),當場域失去焦點時,就會發生這種情況。 勾選框和下拉清單會在選擇後立即驗證。

當欄位出現驗證錯誤或表單至少提交一次後,該欄位會再次按鍵驗證,以確保修正能立即反映。

提交表單即可驗證所有追蹤欄位。 若有任何欄位無效,提交將被阻擋,焦點轉移至第一個無效欄位。

驗證訊息、在地化與無障礙性

用戶端驗證用於 ValidationMessage<TValue> 顯示個別欄位的訊息,以及 ValidationSummary 整個表單的訊息,這與互動驗證向使用者回報訊息的方式相同。

設定驗證在地化後,系統會在伺服器轉譯頁面時將錯誤訊息在地化,因此用戶端驗證會顯示與伺服器端體驗一致的在地化字串。 本地化需要 Microsoft.Extensions.Validation。 欲了解更多資訊,請參閱 ASP.NET Core 中的驗證。

輸入元素與驗證訊息容器的 ARIA 屬性由 Blazor 自動管理,因此輔助技術可在無需額外設定的情況下公告驗證錯誤。

驗證狀態 CSS 類別

用戶端驗證引擎套用與 Blazor互動式驗證相同的 CSS 類別,如下表所示。

Element 班級
Input valid 或 invalid,加上 modified 使用者編輯欄位後
驗證訊息 validation-message
驗證摘要 validation-summary-errors 或 validation-summary-valid

用戶端驗證也會呼叫瀏覽器的 Constraint Validation API,因此標準的 CSS 偽類別 :valid 和 :invalid 會反映每個輸入欄位目前的驗證狀態。

選擇退出用戶端驗證

此功能可在多個層級關閉。 伺服器端驗證不受本節選項影響。

選擇不使用單一表格

將元件參數DisableClientValidation設DataAnnotationsValidator為true:

<DataAnnotationsValidator DisableClientValidation="true" />

選擇退出整個應用程式

當Razor元件服務已在Program檔案中註冊時,於RazorComponentsServiceOptions上設定DisableClientValidation:

builder.Services.AddRazorComponents(options =>
{
    options.DisableClientValidation = true;
});

全球選項優先。 當它設為 true時,表單不會發出客戶端驗證規則。

停用單一送出按鈕設定

請使用按鈕上的標準 HTML formnovalidate 屬性 。 表單發布時未進行客戶端檢查,伺服器端驗證仍會在貼文後執行:

<button type="submit" formnovalidate>Save draft</button>

這可以用來實現「儲存草稿」或「返回」按鈕,且不需要完全有效的表單才能成功提交。

自訂客戶端驗證規則

自訂驗證屬性會持續在伺服器上執行,但預設沒有客戶端實作。 在瀏覽器中強制執行自訂規則包含兩個步驟:從 .NET 屬性發出規則,以及註冊一個匹配的 JavaScript 驗證器。 伺服器端的實作仍具權威性。

從 .NET 發出規則

在驗證屬性上實作 IClientValidationRuleProvider 並回傳一個或多個 ClientValidationRule 實例。 規則 Name 會識別客戶端驗證者,並提供 Parameters 驗證者所需的值。

以下 StartsWithAttribute 會在 IsValid 中於伺服器端驗證,並提供一個含有 prefix 參數的 startswith 用戶端規則:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample.Validation;

// A custom validation attribute that also contributes a client-side rule for static SSR
// forms. The rule name ('startswith') must match the validator registered on the client
// with Blazor.formValidation.addValidator.
public sealed class StartsWithAttribute : ValidationAttribute, IClientValidationRuleProvider
{
    private readonly string prefix;

    public StartsWithAttribute(string prefix)
    {
        this.prefix = prefix;
        ErrorMessage = $"The value must start with '{prefix}'.";
    }

    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        // Leave empty values to RequiredAttribute, matching the client-side validator.
        if (value is not string text || text.Length == 0)
        {
            return ValidationResult.Success;
        }

        if (!text.StartsWith(prefix, StringComparison.Ordinal))
        {
            return new ValidationResult(ErrorMessage, [validationContext.MemberName!]);
        }

        return ValidationResult.Success;
    }

    public IEnumerable<ClientValidationRule> GetClientValidationRules()
    {
        yield return new ClientValidationRule(
            "startswith",
            new Dictionary<string, string> { ["prefix"] = prefix });
    }
}

以通常方式將屬性套用到模型上:

using System.ComponentModel.DataAnnotations;

namespace BlazorSample.Validation;

// Model for the static SSR client-side validation sample. The StartsWith attribute
// contributes a client-side rule enforced in the browser before the form is submitted.
public class ShipModel
{
    [Required]
    [StartsWith("NCC-")]
    public string? Registry { get; set; }
}

註冊 JavaScript 驗證器

在 JavaScript 中,呼叫 Blazor.formValidation.addValidator(name, validator) 將規則名稱與驗證器函式關聯。 name必須完全匹配ClientValidationRule.Name,包括外殼。 註冊是整個應用程式的,再次註冊相同名稱會取代先前的驗證者。

Warning

若未註冊 JavaScript 驗證器以處理已發出的規則名稱,則該規則在瀏覽器中會被跳過。 伺服器端驗證在表單發布時仍會執行。

從應用程式啟動程式碼註冊一次自訂驗證器。 根據下表所述,依照 Blazor 的啟動方式選擇註冊位置。

Blazor 新創公司 註冊地點
自動啟動(預設) 緊接在 blazor.web.js 之後的指令碼
手動 Blazor.start() 由 Blazor.start() 傳回的接續
任一種啟動樣式 JavaScript 初始化器的 afterWebStarted 回調

JavaScript 初始化器可使用任一種啟動風格。 在應用程式的 {ASSEMBLY NAME}.lib.module.js 資料夾中名為 wwwroot 的檔案內:

// A JavaScript initializer works with automatic or manual Blazor startup. The validation service
// is available when afterWebStarted runs, even if the current page contains no validated form.
export function afterWebStarted(blazor) {
  blazor.formValidation.addValidator('startswith', (context) => {
    const value = context.value;

    // An empty value is valid. Use [Required] to require a value.
    if (!value) {
      return { success: true };
    }

    return { success: value.startsWith(context.params.prefix) };
  });
}

透過自動啟動,可以在 blazor.web.js 之後立即載入應用程式專用的驗證指令碼:

<script src="@Assets["_framework/blazor.web.js"]"></script>
<script src="@Assets["js/custom-validation.js"]"></script>

第二個腳本可以直接呼叫 Blazor.formValidation.addValidator 。

手動啟動時,請在應用程式腳本中定義註冊:

function registerCustomValidators(blazor) {
  blazor.formValidation.addValidator('startswith', (context) => {
    const value = context.value;

    if (!value) {
      return { success: true };
    }

    return { success: value.startsWith(context.params.prefix) };
  });
}

// Automatic startup initializes formValidation before this script runs.
// Manual startup calls registerCustomValidators after Blazor.start completes.
if (Blazor.formValidation) {
  registerCustomValidators(Blazor);
}

載入已停用自動啟動的指令碼,並在 Blazor.start() 完成後註冊驗證器:

<script src="@Assets["_framework/blazor.web.js"]" autostart="false"></script>
<script src="@Assets["js/custom-validation.js"]"></script>
<script>
    Blazor.start().then(() => {
        registerCustomValidators(Blazor);
    });
</script>

Important

從應用程式啟動程式碼註冊驗證器,而非從頁面或表單元件註冊。 元件指令碼可以在 Blazor 啟動之前執行,而透過增強型導覽新增的指令碼不會執行。 靜態 SSR 元件也無法使用IJSRuntime,因為它們沒有互動式的 .NET 執行環境。

撰寫 JavaScript 驗證函式

驗證者會收到包含以下成員的上下文物件,如下表所示。

會員 描述
value 欄位目前的值(以字串表示),若沒有值則為 null/undefined。
element 驗證的 input、 select或 textarea 元素。
params 規則就像 Parameters 字典一樣。

驗證者預期在值有效時回傳 { success: true } 。 傳回 { success: false } 以使用該規則的伺服器提供訊息,或者您也可以傳回 { success: false, message: '...' } 以覆寫該次呼叫的訊息。

空值通常應被除 required之外的規則視為有效,允許選填欄位保持空置,同時仍能驗證所提供的值。

在 .NET 和 JavaScript 中實作相同的規則語意,包括大小寫敏感性、正規化和空值處理。 若實作不同,瀏覽器與權威伺服器端驗證可能會產生不同結果。

按需驗證表單

API Blazor.formValidation 也暴露了用於按需驗證的 JavaScript 方法,如下表所示。

方法 描述
validateField(element) 驗證單一欄位元素並更新其錯誤顯示。 有效時會傳回 true。
validateForm(form) 驗證表單中所有追蹤欄位。 當所有欄位都有效時回傳 true 。

Limitations

  • 用戶端規則僅針對伺服器端驗證中包含的欄位會產生。 若無 Microsoft.Extensions.Validation,則僅驗證頂層模型屬性。 驗證巢狀物件與集合需要應用程式呼叫 AddValidation 並發現模型。 欲了解更多資訊,請參閱 ASP.NET Core 中的驗證。 請注意,此限制是刻意設計的,目的是防止因設定錯誤而導致權威伺服器驗證缺失的錯誤。
  • 透過後續 串流渲染 更新新增的輸入,不會獲得客戶端驗證。 單批次交付的表單通常會被覆蓋。
  • 只有 支援的驗證屬性 中列出的屬性具有內建的用戶端實作。 例如,非數值運算元型別的 a RangeAttribute 只會在伺服器端強制執行。 其他屬性則需要自訂的 客戶端驗證規則。
  • 自訂 JavaScript 驗證器是同步式的。 需要網路呼叫或其他非同步運算的規則必須在伺服器上執行,或使用帶有互動式渲染模式的非同步驗證。 欲了解更多資訊,請參閱 ASP.NET Core Blazor 進階表單驗證。