ASP.NET Core MVC 和 Razor Pages 中的模型驗證

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自 模型系結 的錯誤通常是資料轉換錯誤。 例如,在整數位段中輸入 「x」。 模型驗證會在模型系結之後發生,並報告資料不符合商務規則的錯誤。 例如,0 會在預期 1 到 5 的評等欄位中輸入。

模型系結和模型驗證都會在執行控制器動作或 Razor Pages 處理常式方法之前發生。 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。 Web 應用程式通常會以錯誤訊息重新顯示頁面,如下列 Razor Pages 範例所示:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

針對具有控制器和檢視的 ASP.NET Core MVC,下列範例示範如何在控制器動作內檢查 ModelState.IsValid

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Web API 控制器不需要檢查 ModelState.IsValid 它們是否有 [ApiController] 屬性。 在此情況下,當模型狀態無效時,會傳回包含錯誤詳細資料的自動 HTTP 400 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應

重新執行驗證

驗證會自動進行,但建議您以手動方式重複進行。 例如,您可能會計算屬性的值,並在將屬性設定為計算的值之後重新執行驗證。 若要重新執行驗證,請呼叫 ModelStateDictionary.ClearValidationState 以清除所驗證模型的特定驗證,後面接著 TryValidateModel

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

驗證屬性

驗證屬性可讓您指定模型屬性的驗證規則。 下列 範例應用程式 範例會顯示以驗證屬性標注的模型類別。 屬性 [ClassicMovie] 是自訂驗證屬性,而其他屬性則是內建的。 未顯示為 [ClassicMovieWithClientValidator] ,這會顯示實作自訂屬性的替代方式。

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

內建屬性

下列是部分內建驗證屬性:

您可以在 命名空間中找到 System.ComponentModel.DataAnnotations 驗證屬性的完整清單。

錯誤訊息

驗證屬性可讓您指定要顯示的無效輸入錯誤訊息。 例如:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

就內部而言,屬性會使用欄位名稱的預留位置呼叫 String.Format,有時候也會使用其他預留位置。 例如:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

套用至 Name 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

不可為 Null 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或系結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 藉由 啟用 Nullable 內容,MVC 會隱含地在 非泛型型別 或參數上驗證不可為 Null 的屬性,就像它們已使用 [Required(AllowEmptyStrings = true)] 屬性屬性一樣。 請考慮下列程式碼:

public class Person
{
    public string Name { get; set; }
}

如果應用程式是以 建置 <Nullable>enable</Nullable> ,則 ON 或表單貼文中的 JS 遺漏值 Name 會導致驗證錯誤。 使用可為 Null 的參考型別,允許為 Name 屬性指定 Null 或遺漏值:

public class Person
{
    public string? Name { get; set; }
}

在 中 Program.cs 設定 ,即可停用 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 此行為:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

泛型型別和 [必要] 屬性上不可為 Null 的屬性

泛型型別上不可為 Null 的屬性必須在需要類型時包含 [Required] 屬性。 在下列程式碼中, TestRequired 不需要:

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

在下列程式碼中, TestRequired 明確標示為必要:

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,而且 [Required] 永遠不會顯示內容的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 空 <T >實值型別會被視為標準可為 Null 的類型。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote]屬性會實作用戶端驗證,要求在伺服器上呼叫方法,以判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 jQuery 驗證 遠端 方法需要 JS ON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 在模型類別中,使用指向驗證動作方法的 [Remote] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 AdditionalFields

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields 可以明確設定為字串 「FirstName」 和 「LastName」,但使用 nameof 運算子可簡化稍後的重構。 此驗證的動作方法必須接受 firstNamelastName 引數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

當使用者輸入名字或姓氏時,JavaScript 會進行遠端呼叫以查看該組名稱是否已在使用中。

若要驗證兩個或多個其他欄位,請以逗號分隔清單來提供這些欄位。 例如,若要將 MiddleName 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

如同所有屬性引數,AdditionalFields 必須是常數運算式。 因此,請勿使用內插字串或呼叫 Join 來初始化 AdditionalFields

內建屬性的替代項目

如果您需要內建屬性未提供的驗證,您可以:

自訂屬性

在內建驗證屬性無法處理的情況下,您可以建立自訂驗證屬性。 建立繼承自 ValidationAttribute 的類別,並覆寫 IsValid 方法。

IsValid 方法會接受名為 value 的物件,這是要驗證的輸入。 多載也會接受 ValidationContext 物件,該物件會提供其他資訊,例如模型繫結所建立的模型執行個體。

下列範例會驗證 Classic 內容類型中電影的發行日期,不晚於指定的年份。 屬性 [ClassicMovie]

  • 只在伺服器上執行。
  • 針對傳統電影,驗證發行日期:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

上述範例中的 movie 變數代表 Movie 物件,該物件包含表單送出中的資料。 驗證失敗時,會傳回 ValidationResult 和錯誤訊息。

IValidatableObject

上述範例僅適用於 Movie 型別。 類別層級驗證的另一個選項,是在模型類別中實作 IValidatableObject,如下列範例所示:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

最上層節點驗證

最上層節點包括:

  • 動作參數
  • 控制器屬性
  • 頁面處理常式參數
  • 頁面模型屬性

除了驗證模型屬性,也會驗證模型所繫結的最上層節點。 在 範例應用程式中的下列範例中 VerifyPhone ,方法會使用 RegularExpressionAttribute 來驗證 phone 動作參數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

最上層節點可以搭配使用 BindRequiredAttribute 和驗證屬性。 在 範例應用程式中的下列範例中 CheckAge ,方法會 age 指定當提交表單時,參數必須從查詢字串系結:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

在 [檢查年齡] 頁面 (CheckAge.cshtml) 中,有兩種形式。 第一個 Age 表單會將 的值 99 提交為查詢字串參數: https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 Age 值,而且驗證會失敗。 由於 age 參數必須來自查詢字串,因此繫結會失敗。

最大錯誤數

達到最大錯誤數 (預設為 200) 時,就會停止驗證。 您可以在 Program.cs 中使用下列程式碼來設定此數目:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

最大遞迴

ValidationVisitor 會周遊所正驗證之模型的物件圖形。 對於深度或無限遞迴的模型,驗證可能會導致堆疊溢位。 MvcOptions.MaxValidationDepth 提供一種方式,可在訪客遞迴超過設定深度時提早停止驗證。 的 MvcOptions.MaxValidationDepth 預設值為 32。

自動最少運算

如果模型圖形不需要驗證,則驗證會自動進行最少運算 (略過)。 執行階段會略過驗證的物件,包含基本集合 (例如 byte[]string[]Dictionary<string, string>) 和沒有任何驗證程式的複雜物件圖形。

用戶端驗證

用戶端驗證可避免送出無效的表單。 [送出] 按鈕會執行 JavaScript 以送出表單或顯示錯誤訊息。

當表單上存在輸入錯誤時,用戶端驗證可避免伺服器上不必要的來回往返。 下列腳本參考 _Layout.cshtml_ValidationScriptsPartial.cshtml 支援用戶端驗證:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

jQuery Unobtrusive Validation腳本是自訂的 Microsoft 前端程式庫,以熱門的 jQuery 驗證外掛程式為基礎。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery Unobtrusive Validation 會剖析屬性, data- 並將邏輯傳遞至 jQuery 驗證,有效地將伺服器端驗證邏輯「複製」至用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

上述標籤協助程式會轉譯下列 HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

請注意,HTML 輸出中的 data- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery Unobtrusive Validation 會將此值傳遞至jQuery Validation required () 方法,然後在隨附< 的 span >元素中顯示該訊息。

資料類型驗證是以屬性的 .NET 類型為基礎,除非該類型是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別,例如 [EmailAddress] 可讓您指定錯誤訊息。

不幹擾驗證

如需不幹擾驗證的資訊,請參閱 此 GitHub 問題

將驗證新增至動態表單

jQuery 非干擾驗證會在頁面第一次載入時,將驗證邏輯和參數傳遞至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 AJAX 新增的表單上設定用戶端驗證。

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 這些屬性的值接著會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

自訂用戶端驗證

自訂用戶端驗證是藉由產生 data- 使用自訂 jQuery 驗證配接器的 HTML 屬性來完成。 下列配接器程式碼範例,是針對本文稍早介紹的 [ClassicMovie][ClassicMovieWithClientValidator] 屬性所撰寫:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

如需如何撰寫配接器的資訊,請參閱 jQuery 驗證檔

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示 data-範例應用程式ClassicMovie 屬性的屬性:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

範例應用程式中data- 屬性會使用 ClassicMovie HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  1. 建立自訂驗證屬性的屬性配接器類別。 從 AttributeAdapterBase<TAttribute>衍生類別。 建立會將 data- 屬性新增至轉譯輸出的 AddValidation 方法,如下列範例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. 建立實作 IValidationAttributeAdapterProvider 的配接器提供者類別。 在 GetAttributeAdapter 方法中將自訂屬性傳遞至配接器的建構函式,如下列範例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Program.cs 中註冊 DI 的配接器提供者:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

用戶端驗證的 IClientModelValidator

範例應用程式中data- 屬性會使用 ClassicMovieWithClientValidator HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  • 在自訂驗證屬性中,實作 IClientModelValidator 介面並建立 AddValidation 方法。 在 AddValidation 方法中,為驗證新增 data- 屬性,如下列範例所示:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

停用用戶端驗證

下列程式碼會在 Pages 中 Razor 停用用戶端驗證:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

停用用戶端驗證的其他選項:

  • 將所有檔案中的 .cshtml 參考 _ValidationScriptsPartial 批註化。
  • 移除 Pages\Shared_ValidationScriptsPartial.cshtml 檔案的內容。

上述方法不會防止用戶端驗證 ASP.NET Core IdentityRazor 類別庫。 如需詳細資訊,請參閱ASP.NET Core 專案中的 Scaffold Identity

其他資源

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自 模型系結 的錯誤通常是資料轉換錯誤。 例如,在整數位段中輸入 「x」。 模型驗證會在模型系結之後發生,並報告資料不符合商務規則的錯誤。 例如,在預期評等介於 1 到 5 的欄位中輸入 0。

模型系結和模型驗證都會在執行控制器動作或 Razor Pages 處理常式方法之前發生。 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。 Web 應用程式通常會以錯誤訊息重新顯示頁面,如下列 Razor Pages 範例所示:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

對於具有控制器和檢視的 ASP.NET Core MVC,下列範例示範如何在控制器動作內檢查 ModelState.IsValid

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Web API 控制器不需要檢查 ModelState.IsValid 它們是否有 [ApiController] 屬性。 在此情況下,當模型狀態無效時,會傳回包含錯誤詳細資料的自動 HTTP 400 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應

重新執行驗證

驗證會自動進行,但建議您以手動方式重複進行。 例如,您可能會計算屬性的值,並在將屬性設定為計算的值之後重新執行驗證。 若要重新執行驗證,請呼叫 ModelStateDictionary.ClearValidationState 以清除所驗證模型的特定驗證, TryValidateModel 後面接著 :

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

驗證屬性

驗證屬性可讓您指定模型屬性的驗證規則。 下列來自 範例應用程式的範例 顯示以驗證屬性標注的模型類別。 屬性 [ClassicMovie] 是自訂驗證屬性,而其他屬性則是內建的。 [未顯示] 是 [ClassicMovieWithClientValidator] ,這會顯示實作自訂屬性的替代方式。

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

內建屬性

下列是部分內建驗證屬性:

您可以在 命名空間中找到 System.ComponentModel.DataAnnotations 驗證屬性的完整清單。

錯誤訊息

驗證屬性可讓您指定要顯示的無效輸入錯誤訊息。 例如:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

就內部而言,屬性會使用欄位名稱的預留位置呼叫 String.Format,有時候也會使用其他預留位置。 例如:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

套用至 Name 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

在驗證錯誤中使用 JS ON 屬性名稱

根據預設,當發生驗證錯誤時,模型驗證會產生 ModelStateDictionary 屬性名稱做為錯誤索引鍵的 。 某些應用程式,例如單頁應用程式,可受益于使用 JS ON 屬性名稱來取得從 Web API 產生的驗證錯誤。 下列程式碼會將驗證設定為 JS 使用 SystemTextJsonValidationMetadataProvider ON 屬性名稱:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

下列程式碼會設定驗證,以在使用Json.NET時使用 NewtonsoftJsonValidationMetadataProviderJS ON 屬性名稱:

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

如需使用 camel 大小寫的原則範例,請參閱Program.cs GitHub 上的

不可為 Null 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或系結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 藉由 啟用 Nullable 內容,MVC 會隱含地開始驗證不可為 Null 的屬性或參數,就像它們已使用 [Required(AllowEmptyStrings = true)] 屬性屬性一樣。 請考慮下列程式碼:

public class Person
{
    public string Name { get; set; }
}

如果應用程式是以 建置 <Nullable>enable</Nullable> ,則 ON 或表單文章中的 JS 遺漏值 Name 會導致驗證錯誤。 使用可為 Null 的參考型別,允許為 Name 屬性指定 Null 或遺漏值:

public class Person
{
    public string? Name { get; set; }
}

在 中 Program.cs 設定 ,即可停用 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 此行為:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,而且 [Required] 永遠不會顯示內容的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 空 <T >實值型別會被視為標準可為 Null 的型別。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote]屬性會實作用戶端驗證,要求在伺服器上呼叫方法,以判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 jQuery 驗證 遠端 方法需要 JS ON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 在模型類別中,使用指向驗證動作方法的 [Remote] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 AdditionalFields

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields 可以明確設定為字串 「FirstName」 和 「LastName」,但使用 nameof 運算子可簡化稍後的重構。 此驗證的動作方法必須接受 firstNamelastName 引數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

當使用者輸入名字或姓氏時,JavaScript 會進行遠端呼叫以查看該組名稱是否已在使用中。

若要驗證兩個或多個其他欄位,請以逗號分隔清單來提供這些欄位。 例如,若要將 MiddleName 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

如同所有屬性引數,AdditionalFields 必須是常數運算式。 因此,請勿使用內插字串或呼叫 Join 來初始化 AdditionalFields

內建屬性的替代項目

如果您需要內建屬性未提供的驗證,您可以:

自訂屬性

在內建驗證屬性無法處理的情況下,您可以建立自訂驗證屬性。 建立繼承自 ValidationAttribute 的類別,並覆寫 IsValid 方法。

IsValid 方法會接受名為 value 的物件,這是要驗證的輸入。 多載也會接受 ValidationContext 物件,該物件會提供其他資訊,例如模型繫結所建立的模型執行個體。

下列範例會驗證 Classic 內容類型中電影的發行日期,不晚於指定的年份。 屬性 [ClassicMovie]

  • 只在伺服器上執行。
  • 針對傳統電影,驗證發行日期:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

上述範例中的 movie 變數代表 Movie 物件,該物件包含表單送出中的資料。 驗證失敗時,會傳回 ValidationResult 和錯誤訊息。

IValidatableObject

上述範例僅適用於 Movie 型別。 類別層級驗證的另一個選項,是在模型類別中實作 IValidatableObject,如下列範例所示:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

最上層節點驗證

最上層節點包括:

  • 動作參數
  • 控制器屬性
  • 頁面處理常式參數
  • 頁面模型屬性

除了驗證模型屬性,也會驗證模型所繫結的最上層節點。 在 範例應用程式的下列範例VerifyPhone ,方法會使用 RegularExpressionAttribute 來驗證 phone 動作參數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

最上層節點可以搭配使用 BindRequiredAttribute 和驗證屬性。 在 範例應用程式中的下列範例中 CheckAge ,方法會 age 指定在提交表單時,參數必須系結自查詢字串:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

在 [檢查年齡] CheckAge.cshtml 頁面中, () 有兩種表單。 第一個 Age 表單會將 的值 99 提交為查詢字串參數: https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 Age 值,而且驗證會失敗。 由於 age 參數必須來自查詢字串,因此繫結會失敗。

最大錯誤數

達到最大錯誤數 (預設為 200) 時,就會停止驗證。 您可以在 Program.cs 中使用下列程式碼來設定此數目:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

最大遞迴

ValidationVisitor 會周遊所正驗證之模型的物件圖形。 對於深層或無限遞迴的模型,驗證可能會導致堆疊溢位。 MvcOptions.MaxValidationDepth 提供一種方式,可在訪客遞迴超過設定的深度時,提早停止驗證。 的預設值 MvcOptions.MaxValidationDepth 為 32。

自動最少運算

如果模型圖形不需要驗證,則驗證會自動進行最少運算 (略過)。 執行階段會略過驗證的物件,包含基本集合 (例如 byte[]string[]Dictionary<string, string>) 和沒有任何驗證程式的複雜物件圖形。

用戶端驗證

用戶端驗證可避免送出無效的表單。 [送出] 按鈕會執行 JavaScript 以送出表單或顯示錯誤訊息。

當表單上存在輸入錯誤時,用戶端驗證可避免伺服器上不必要的來回往返。 中的 _Layout.cshtml 下列腳本參考並支援 _ValidationScriptsPartial.cshtml 用戶端驗證:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

jQuery Unobtrusive Validation腳本是自訂的 Microsoft 前端程式庫,建置在熱門的 jQuery 驗證外掛程式上。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery Unobtrusive Validation 會 data- 剖析屬性,並將邏輯傳遞至 jQuery 驗證,有效地將伺服器端驗證邏輯「複製」至用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

上述標籤協助程式會轉譯下列 HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

請注意,HTML 輸出中的 data- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery Unobtrusive Validation 會將此值傳遞至 jQuery Validation required () 方法,然後在隨附< 的 span >元素中顯示該訊息。

資料類型驗證是以屬性的 .NET 類型為基礎,除非該類型是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別,例如 [EmailAddress] 可讓您指定錯誤訊息。

不具干擾性驗證

如需有關不具干擾性驗證的資訊,請參閱 此 GitHub 問題

將驗證新增至動態表單

jQuery Unobtrusive Validation 會在第一次載入頁面時,將驗證邏輯和參數傳遞至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 AJAX 新增的表單上設定用戶端驗證。

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 這些屬性的值接著會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

自訂用戶端驗證

自訂用戶端驗證是藉由產生 data- 使用自訂 jQuery 驗證配接器的 HTML 屬性來完成。 下列配接器程式碼範例,是針對本文稍早介紹的 [ClassicMovie][ClassicMovieWithClientValidator] 屬性所撰寫:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

如需如何撰寫配接器的資訊,請參閱 jQuery 驗證檔

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示 data-範例應用程式ClassicMovie 屬性的屬性:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

範例應用程式中data- 屬性會使用 ClassicMovie HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  1. 建立自訂驗證屬性的屬性配接器類別。 從 AttributeAdapterBase<TAttribute>衍生類別。 建立會將 data- 屬性新增至轉譯輸出的 AddValidation 方法,如下列範例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. 建立實作 IValidationAttributeAdapterProvider 的配接器提供者類別。 在 GetAttributeAdapter 方法中將自訂屬性傳遞至配接器的建構函式,如下列範例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Program.cs 中註冊 DI 的配接器提供者:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

用戶端驗證的 IClientModelValidator

範例應用程式中data- 屬性會使用 ClassicMovieWithClientValidator HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  • 在自訂驗證屬性中,實作 IClientModelValidator 介面並建立 AddValidation 方法。 在 AddValidation 方法中,為驗證新增 data- 屬性,如下列範例所示:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

停用用戶端驗證

下列程式碼會在 Pages 中 Razor 停用用戶端驗證:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

停用用戶端驗證的其他選項:

  • 將所有檔案中的 .cshtml 參考 _ValidationScriptsPartial 批註化。
  • 移除 Pages\Shared_ValidationScriptsPartial.cshtml 檔案的內容。

上述方法不會防止用戶端驗證 ASP.NET Core IdentityRazor 類別庫。 如需詳細資訊,請參閱ASP.NET Core 專案中的 Scaffold Identity

其他資源

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自 模型系結 的錯誤通常是資料轉換錯誤。 例如,在整數位段中輸入 「x」。 模型驗證會在模型系結之後發生,並報告資料不符合商務規則的錯誤。 例如,在預期評等介於 1 到 5 的欄位中輸入 0。

模型系結和模型驗證都會在執行控制器動作或 Razor Pages 處理常式方法之前發生。 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。 Web 應用程式通常會重新顯示頁面,並出現錯誤訊息:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Web API 控制器不需要檢查 ModelState.IsValid 它們是否有 [ApiController] 屬性。 在此情況下,當模型狀態無效時,會傳回包含錯誤詳細資料的自動 HTTP 400 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應

重新執行驗證

驗證會自動進行,但建議您以手動方式重複進行。 例如,您可能會計算屬性的值,並在將屬性設定為計算的值之後重新執行驗證。 若要重新執行驗證,請呼叫 ModelStateDictionary.ClearValidationState 以清除所驗證模型的特定驗證, TryValidateModel 後面接著 :

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

驗證屬性

驗證屬性可讓您指定模型屬性的驗證規則。 下列來自 範例應用程式的範例 顯示以驗證屬性標注的模型類別。 屬性 [ClassicMovie] 是自訂驗證屬性,而其他屬性則是內建的。 [未顯示] 是 [ClassicMovieWithClientValidator] ,這會顯示實作自訂屬性的替代方式。

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

內建屬性

下列是部分內建驗證屬性:

您可以在 命名空間中找到 System.ComponentModel.DataAnnotations 驗證屬性的完整清單。

錯誤訊息

驗證屬性可讓您指定要顯示的無效輸入錯誤訊息。 例如:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

就內部而言,屬性會使用欄位名稱的預留位置呼叫 String.Format,有時候也會使用其他預留位置。 例如:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

套用至 Name 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

不可為 Null 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或系結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 藉由 啟用 Nullable 內容,MVC 會隱含地開始驗證不可為 Null 的屬性或參數,就像它們已使用 [Required(AllowEmptyStrings = true)] 屬性屬性一樣。 請考慮下列程式碼:

public class Person
{
    public string Name { get; set; }
}

如果應用程式是以 建置 <Nullable>enable</Nullable> ,則 ON 或表單文章中的 JS 遺漏值 Name 會導致驗證錯誤。 使用可為 Null 的參考型別,允許為 Name 屬性指定 Null 或遺漏值:

public class Person
{
    public string? Name { get; set; }
}

在 中 Startup.ConfigureServices 設定 ,即可停用 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 此行為:

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,而且 [Required] 永遠不會顯示內容的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 空 <T >實值型別會被視為標準可為 Null 的型別。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote]屬性會實作用戶端驗證,要求在伺服器上呼叫方法,以判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 jQuery 驗證 遠端 方法需要 JS ON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 在模型類別中,使用指向驗證動作方法的 [Remote] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 AdditionalFields

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields 可以明確設定為字串 「FirstName」 和 「LastName」,但使用 nameof 運算子可簡化稍後的重構。 此驗證的動作方法必須接受 firstNamelastName 引數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

當使用者輸入名字或姓氏時,JavaScript 會進行遠端呼叫以查看該組名稱是否已在使用中。

若要驗證兩個或多個其他欄位,請以逗號分隔清單來提供這些欄位。 例如,若要將 MiddleName 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

如同所有屬性引數,AdditionalFields 必須是常數運算式。 因此,請勿使用內插字串或呼叫 Join 來初始化 AdditionalFields

內建屬性的替代項目

如果您需要內建屬性未提供的驗證,您可以:

自訂屬性

在內建驗證屬性無法處理的情況下,您可以建立自訂驗證屬性。 建立繼承自 ValidationAttribute 的類別,並覆寫 IsValid 方法。

IsValid 方法會接受名為 value 的物件,這是要驗證的輸入。 多載也會接受 ValidationContext 物件,該物件會提供其他資訊,例如模型繫結所建立的模型執行個體。

下列範例會驗證 Classic 內容類型中電影的發行日期,不晚於指定的年份。 屬性 [ClassicMovie]

  • 只在伺服器上執行。
  • 針對傳統電影,驗證發行日期:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

上述範例中的 movie 變數代表 Movie 物件,該物件包含表單送出中的資料。 驗證失敗時,會傳回 ValidationResult 和錯誤訊息。

IValidatableObject

上述範例僅適用於 Movie 型別。 類別層級驗證的另一個選項,是在模型類別中實作 IValidatableObject,如下列範例所示:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

最上層節點驗證

最上層節點包括:

  • 動作參數
  • 控制器屬性
  • 頁面處理常式參數
  • 頁面模型屬性

除了驗證模型屬性,也會驗證模型所繫結的最上層節點。 在 範例應用程式中的下列範例中 VerifyPhone ,方法會使用 RegularExpressionAttribute 來驗證 phone 動作參數:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

最上層節點可以搭配使用 BindRequiredAttribute 和驗證屬性。 在 範例應用程式中的下列範例中 CheckAge ,方法會 age 指定當提交表單時,參數必須從查詢字串系結:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

在 [檢查年齡] 頁面 (CheckAge.cshtml) 中,有兩種形式。 第一個 Age 表單會將 的值 99 提交為查詢字串參數: https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 Age 值,而且驗證會失敗。 由於 age 參數必須來自查詢字串,因此繫結會失敗。

最大錯誤數

達到最大錯誤數 (預設為 200) 時,就會停止驗證。 您可以在 Startup.ConfigureServices 中使用下列程式碼來設定此數目:

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

最大遞迴

ValidationVisitor 會周遊所正驗證之模型的物件圖形。 對於深度或無限遞迴的模型,驗證可能會導致堆疊溢位。 MvcOptions.MaxValidationDepth 提供一種方式,可在訪客遞迴超過設定深度時提早停止驗證。 的 MvcOptions.MaxValidationDepth 預設值為 32。

自動最少運算

如果模型圖形不需要驗證,則驗證會自動進行最少運算 (略過)。 執行階段會略過驗證的物件,包含基本集合 (例如 byte[]string[]Dictionary<string, string>) 和沒有任何驗證程式的複雜物件圖形。

用戶端驗證

用戶端驗證可避免送出無效的表單。 [送出] 按鈕會執行 JavaScript 以送出表單或顯示錯誤訊息。

當表單上存在輸入錯誤時,用戶端驗證可避免伺服器上不必要的來回往返。 下列腳本參考 _Layout.cshtml_ValidationScriptsPartial.cshtml 支援用戶端驗證:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>

jQuery Unobtrusive Validation腳本是自訂的 Microsoft 前端程式庫,以熱門的 jQuery 驗證外掛程式為基礎。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery Unobtrusive Validation 會剖析屬性, data- 並將邏輯傳遞至 jQuery 驗證,有效地將伺服器端驗證邏輯「複製」至用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

上述標籤協助程式會轉譯下列 HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

請注意,HTML 輸出中的 data- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery Unobtrusive Validation 會將此值傳遞至jQuery Validation required () 方法,然後在隨附< 的 span >元素中顯示該訊息。

資料類型驗證是以屬性的 .NET 類型為基礎,除非該類型是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別,例如 [EmailAddress] 可讓您指定錯誤訊息。

不幹擾驗證

如需不幹擾驗證的資訊,請參閱 此 GitHub 問題

將驗證新增至動態表單

jQuery 非干擾驗證會在頁面第一次載入時,將驗證邏輯和參數傳遞至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 AJAX 新增的表單上設定用戶端驗證。

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 這些屬性的值接著會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

自訂用戶端驗證

自訂用戶端驗證是藉由產生 data- 使用自訂 jQuery 驗證配接器的 HTML 屬性來完成。 下列配接器程式碼範例,是針對本文稍早介紹的 [ClassicMovie][ClassicMovieWithClientValidator] 屬性所撰寫:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

如需如何撰寫配接器的資訊,請參閱 jQuery 驗證檔

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示 data-範例應用程式ClassicMovie 屬性的屬性:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

範例應用程式中data- 屬性會使用 ClassicMovie HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  1. 建立自訂驗證屬性的屬性配接器類別。 從 AttributeAdapterBase<TAttribute>衍生類別。 建立會將 data- 屬性新增至轉譯輸出的 AddValidation 方法,如下列範例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. 建立實作 IValidationAttributeAdapterProvider 的配接器提供者類別。 在 GetAttributeAdapter 方法中將自訂屬性傳遞至配接器的建構函式,如下列範例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Startup.ConfigureServices 中註冊 DI 的配接器提供者:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

用戶端驗證的 IClientModelValidator

範例應用程式中data- 屬性會使用 ClassicMovieWithClientValidator HTML 中的這個轉譯屬性方法。 使用此方法來新增用戶端驗證:

  • 在自訂驗證屬性中,實作 IClientModelValidator 介面並建立 AddValidation 方法。 在 AddValidation 方法中,為驗證新增 data- 屬性,如下列範例所示:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

停用用戶端驗證

下列程式碼會在 Pages 中 Razor 停用用戶端驗證:

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

停用用戶端驗證的其他選項:

  • 將所有檔案中的 .cshtml 參考 _ValidationScriptsPartial 批註化。
  • 移除 Pages\Shared_ValidationScriptsPartial.cshtml 檔案的內容。

上述方法不會防止用戶端驗證 ASP.NET Core IdentityRazor 類別庫。 如需詳細資訊,請參閱ASP.NET Core 專案中的 Scaffold Identity

其他資源