using System.ComponentModel.DataAnnotations;
namespace MyModels {
public class Book {
[Display( Name = "圖書編號" )]
public int Id { get; set; }
[Display( Name = "圖書名稱" )]
[Required( ErrorMessage = "圖書名稱不可為空白" )]
[MaxLength( 50, ErrorMessage = "長度不可超過 {1}" )]
public string? Title { get; set; } = null!;
[Display( Name = "價格" )]
[Range( 1, int.MaxValue, ErrorMessage = "{0} 有效範圍在 {1} 與 {2} 之間" )]
public int Price { get; set; }
[Display( Name = "出版日期" )]
[DataType( DataType.Date )]
public DateTime PublishDate { get; set; }
[Display( Name = "庫存" )]
public bool InStock { get; set; }
[Display( Name = "說明" )]
[MaxLength( 50, ErrorMessage = "長度不可超過 {1}" )]
public string? Description { get; set; }
DataAnnotations技術將資料驗證邏輯與資料模型(Data Model)緊密整合在一起,不符軟體鬆散耦合(loosely coupled)精神,同時也增加模型(Model)類別的複雜度。除了使用ASP.NET Core內建的DataAnnotations功能來進行驗證之外,有一個好用的程式庫──Fluent Validations,可以讓你定義強型別的驗證規則,讓你完全掌控驗證的動作,將驗證程式碼從模型類別中抽離出來,降低了應用程式的複雜度。
同時Fluent Validation程式庫是一個開放源碼的程式庫,可以整合到ASP.NET Core MVC、Razor Page與Blazor類型的專案,以及Web API服務之中使用。
安裝FluentValidation.AspNetCore套件
要在Razor Page網站中使用Fluent Validation需要在專案之中先安裝FluentValidation.AspNetCore套件。從Visual Studio 2022開發工具「Tools」-「NuGet Package Manager」-「Package Manager Console」開啟「Package Manager Console」視窗,然後在命令提示字元中輸入指令安裝「FluentValidation.AspNetCore」套件:
Install-Package FluentValidation.AspNetCore
註冊FluentValidation.AspNetCore套件
接著我們需要在「Program.cs」檔案中加入程式碼,註冊Fluent Validation服務,為了簡單起見,省略掉其它程式碼:
using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
var app = builder.Build();
「AddFluentValidationAutoValidation」方法在應用程式中加入Fluent Validation功能;而「AddFluentValidationClientsideAdapters」方法則啟用用戶端驗證功能。
設計「Book」模型
參考以下的程式碼,定義一個「Book」模型包含以下屬性:
using System.ComponentModel.DataAnnotations;
namespace MyModels {
public class Book {
[Display( Name = "圖書編號" )]
public int Id { get; set; }
[Display( Name = "圖書名稱" )]
public string? Title { get; set; } = null!;
[Display( Name = "價格" )]
public int Price { get; set; }
[Display( Name = "出版日期" )]
[DataType( DataType.Date )]
public DateTime PublishDate { get; set; }
[Display( Name = "庫存" )]
public bool InStock { get; set; }
[Display( Name = "說明" )]
public string? Description { get; set; }
定義模型驗證類別
Fluent Validation提供許多內建的驗證器(Validator)來檢查資料的有效性,��細的清單可以參閱官網: https://docs.fluentvalidation.net/en/latest/built-in-validators.html
Fluent Validation可以使用Lamdba運算式來設定驗證規則,我們可以在Razor Page專案之中加入一個「BookValidator」類別來進行驗證,請參考以下程式碼:
using FluentValidation;
using MyModels;
namespace MyRazorWeb.Validator {
public class BookValidator : AbstractValidator<Book> {
public BookValidator() {
RuleFor(x => x.Id)
.NotNull();
RuleFor(x => x.Title)
.NotNull()
.MaximumLength(50);
RuleFor(x => x.Price)
.NotNull()
.InclusiveBetween(1, int.MaxValue);
RuleFor(x => x.PublishDate)
.NotNull();
RuleFor(x => x.Description)
.Length(0, 50);
「BookValidator」類別需繼承「AbstractValidator」類別,其中的指的就是想要驗證的���型類別。驗證的規則要定義在建構函式之中,只要叫用「RuleFor」方法傳入Lambda運算式指明想要驗證的屬性名稱即可。
註冊Fluent Validation服務
「BookValidator」類別需要在Razor Page網站中Program.cs檔案內註冊之後才能夠使用,請參考以下程式碼:
using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddScoped<IValidator<Book>, BookValidator>();
var app = builder.Build();
除了叫用「AddScoped」方法註冊「BookValidator」之外,你也可以改用「AddValidatorsFromAssemblyContaining」方法來進行註冊,請參考以下程式碼:
using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddValidatorsFromAssemblyContaining<BookValidator>();
var app = builder.Build();
在Razor Page專案中的Create頁面程式如下:
@page
@model MyRazorWeb.Pages.Books.CreateModel
ViewData[ "Title" ] = " Create ";
<h1> Book Create </h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"> </div>
<div class="mb-3">
<label asp-for="Book.Title" class="form-label"> </label>
<input asp-for="Book.Title" class="form-control" />
<span asp-validation-for="Book.Title" class="text-danger"> </span>
<div class="mb-3">
<label asp-for="Book.Price" class="form-label"> </label>
<input asp-for="Book.Price" class="form-control" />
<span asp-validation-for="Book.Price" class="text-danger"> </span>
<div class="mb-3">
<label asp-for="Book.PublishDate" class="form-label"> </label>
<input asp-for="Book.PublishDate" class="form-control" />
<span asp-validation-for="Book.PublishDate" class="text-danger"> </span>
<div class="mb-3 form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Book.InStock" />
@Html.DisplayNameFor( model => model.Book.InStock )
</label>
<div class="mb-3">
<label asp-for="Book.Description" class="form-label"> </label>
<input asp-for="Book.Description" class="form-control" />
<span asp-validation-for="Book.Description" class="text-danger"> </span>
<div class="mb-3">
<input type="submit" value="Save" class="btn btn-primary" />
</form>
<a asp-page="./List"> Back to List </a>
網頁執行時,故意不填資料到文字方塊,再按下「Save」按鈕,請參考下圖所示,錯誤訊息會自動出現在「asp-validation-summary」與「asp-validation-for」標記協助程式(Tag Helper)所在的位置:
圖 1:錯誤驗證測試。
若修改文字方塊中的值,讓「Title(圖書名稱)」文字方塊中的字串超過50個字;並將「Price(價格)」設為負數值,再按下「Save」按鈕,則錯誤訊息會自動出現在「asp-validation-summary」與「asp-validation-for」標記協助程式(Tag Helper)所在的位置,請參考下圖所示:
圖 2:顯示錯誤訊息。
自訂驗證錯誤訊息
若需要自訂驗證錯誤訊息,可叫用「WithMessage」方法,例如以下驗證「Title」屬性的程式碼,在「NotNull」方法之後叫用「WithMessage」方法設定錯誤訊息為「圖書名稱不可為空白」;在叫用「MaximumLength」方法之後,再串接叫用「WithMessage」方法,設定錯誤訊息為「長度不可超過 50」;依此類推:
using FluentValidation;
using MyModels;
namespace MyRazorWeb.Validator {
public class BookValidator : AbstractValidator<Book> {
public BookValidator() {
RuleFor( x => x.Id )
.NotNull( );
RuleFor( x => x.Title )
.NotNull( )
.WithMessage( "圖書名稱不可為空白" )
.MaximumLength( 50 )
.WithMessage( "長度不可超過 50 " );
RuleFor( x => x.Price )
.NotNull( )
.WithMessage( "價格不可為空白" )
.InclusiveBetween( 1, int.MaxValue )
.WithMessage( $"有效範圍在 1 與 {int.MaxValue} 之間" );
RuleFor( x => x.PublishDate )
.NotEmpty( )
.WithMessage( "出版日期不可為空白" );
RuleFor( x => x.Description )
.Length( 0, 50 )
.WithMessage( "長度不可超過 50 " );
驗證測試的執行結果,請參考下圖所示:
圖 3:驗證測試。
錯誤訊息內可包含一些參數取得要驗證的屬性名稱、屬性值、最大值、最小值等等,我們可以將上個範例程式碼變更如下:
using FluentValidation;
using MyModels;
namespace MyRazorWeb.Validator {
public class BookValidator : AbstractValidator<Book> {
public BookValidator() {
RuleFor( x => x.Id )
.NotNull( );
RuleFor( x => x.Title )
.NotNull( )
.WithMessage( "{PropertyName} 不可為空白" )
.MaximumLength( 50 )
.WithMessage( "{PropertyName} 長度不可超過 {MaxLength} " );
RuleFor( x => x.Price )
.NotNull( )
.WithMessage( "{PropertyName} 不可為空白" )
.InclusiveBetween( 1, int.MaxValue )
.WithMessage( "有效範圍在 {From} 與 {To} 之間" );
RuleFor( x => x.PublishDate )
.NotEmpty( )
.WithMessage( "{PropertyName} 不可為空白" );
RuleFor( x => x.Description )
.Length( 0, 50 )
.WithMessage( "長度不可超過 {MaxLength} " );
驗證測試的執行結果,請參考下圖所示:
圖 4:驗證測試。
在Web API進行驗證
要在Web API的控制器使用Fluent Validation進行驗證,並不需要特別寫叫用Fluent Validation套件的程式碼。由於已在Program.cs註冊Fluent Validation服務,驗證的功能會自動生效,只需要在Web API控制器中利用「ModelState」的「IsValid」屬性進行判斷,只要驗證成功,屬性的值就為「true」,若驗證失敗屬性的值就為「false」,例如以下「BooksController」程式碼:
using Microsoft.AspNetCore.Mvc;
using MyModels;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace MyRazorWeb.Controllers {
[Route( "api/[controller]" )]
[ApiController]
public class BooksController : ControllerBase {
public static List<Book> books = new List<Book>( ) {
new Book() {
Id = 1,
Title = " Essential Programming Language ",
Price = 250,
PublishDate = new DateTime(2019, 1, 2),
InStock = true,
Description = "Essential Programming Language ",
new Book() {
Id = 2,
Title = " Telling Arts ",
Price = 245,
PublishDate = new DateTime(2019, 4, 15),
InStock = true,
Description = " Telling Arts ",
new Book() {
Id = 3,
Title = " Marvel ",
Price = 150,
PublishDate = new DateTime(2019, 2, 21),
InStock = true,
Description = " Marvel ",
new Book() {
Id = 4,
Title = " The Beauty of Cook",
Price = 450,
PublishDate = new DateTime(2019, 12, 2),
InStock = true,
Description = " The Beauty of Cook ",
new Book() {
Id = 5,
Title = " Learning how to Cook ",
Price = 450,
PublishDate = new DateTime(2020, 1, 20),
InStock = true,
Description = " Learning how to Cook ",
public BooksController() {
// GET: api/<BooksController>
[HttpGet]
public ActionResult Get() {
try {
return Ok( books );
catch ( Exception ) {
return StatusCode( StatusCodes.Status500InternalServerError,
"Error get books from Server !" );
[HttpPost]
public ActionResult<Book> Post( [FromBody] Book book ) {
try {
if ( book == null ) {
return BadRequest( );
if ( !ModelState.IsValid ) {
return StatusCode( StatusCodes.Status400BadRequest, ModelState );
book.Id = books.MaxBy( b => b.Id )!.Id + 1;
books.Add( book );
return CreatedAtAction( nameof( Post ), new { id = book.Id }, book );
catch ( Exception ) {
return StatusCode( StatusCodes.Status500InternalServerError,
"Error insert book to database !" );
使用Swagger來測試Web API,當送出POST請求要新增一筆圖書資料時,若資料沒有驗證問題,便可自動新增:
"id": 0,
"title": "Programming JavaScript",
"price": 500,
"publishDate": "2023-08-30",
"inStock": true,
"description": "Programming JavaScript"
驗證測試的執行結果,請參考下圖所示:
圖 5:使用Swagger測試驗證。
接著使用Swagger測試,送出GET請求查詢圖書資料,可看到新增的圖書,請參考下圖所示:
圖 6:使用Swagger測試驗證。
執行結果,請參考下圖所示:
圖 7:使用Swagger測試驗證。
若故意輸入有問題的資料如下,讓新增的動作產生例外錯誤:
"id": 0,
"price": -100,
"inStock": true,
"description": "string"
驗證測試的執行結果,請參考下圖所示:
圖 8:驗證測試。
當送出POST請求要新增一筆圖書資料時,便會得到400號錯誤,「Response body」會列出所有有問題的屬性與在Fluent Validation自訂的錯誤訊息:
圖 9:驗證測試。
驗證測試的執行結果,請參考下圖所示:
圖 10:驗證測試。
我們可以明確地透過程式碼,建立「BookValidator」物件,叫用「Validate」方法進行驗證,修改控制器程式如下,從方法傳回的「ValidationResult」物件的「Errors」屬性,可取得包含詳細錯誤資訊的「ValidationFailure」物件。
using Microsoft.AspNetCore.Mvc;
using MyModels;
using MyRazorWeb.Validator;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace MyRazorWeb.Controllers {
[Route( "api/[controller]" )]
[ApiController]
public class BooksController : ControllerBase {
... //略
[HttpPost]
public ActionResult<Book> Post( [FromBody] Book book ) {
try {
if ( book == null ) {
return BadRequest( );
BookValidator validator = new BookValidator( );
var result = validator.Validate( book );
if ( !result.IsValid ) {
return StatusCode( StatusCodes.Status400BadRequest, result.Errors);
book.Id = books.MaxBy( b => b.Id )!.Id + 1;
books.Add( book );
return CreatedAtAction( nameof( Post ), new { id = book.Id }, book );
catch ( Exception ) {
return StatusCode( StatusCodes.Status500InternalServerError,
"Error insert book to database !" );
使用Swagger測試,送出POST請求要新增一筆有問題的圖書資料:
"id": 0,
"price": -100,
"inStock": true,
"description": "string"
執行結果請參考下圖所示:
圖 11:驗證測試。
使用相依性插入
ASP.NET Core MVC與Razor Page類型的網站都支援相依性插入(DI),因此從控制器類別的建構函式之中可以直接取得在Program.cs的「IValidator」來進行驗證,不需要明確在程式中建立Validator實體,要達到這個目的,可修改控制器類別的程式碼如下:
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using MyModels;
using MyRazorWeb.Validator;
using System;
using System.ComponentModel.DataAnnotations;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace MyRazorWeb.Controllers {
[Route( "api/[controller]" )]
[ApiController]
public class BooksController : ControllerBase {
public static List<Book> books = new List<Book>( ) {
new Book() {
Id = 1,
Title = " Essential Programming Language ",
Price = 250,
PublishDate = new DateTime(2019, 1, 2),
InStock = true,
Description = "Essential Programming Language ",
new Book() {
Id = 2,
Title = " Telling Arts ",
Price = 245,
PublishDate = new DateTime(2019, 4, 15),
InStock = true,
Description = " Telling Arts ",
new Book() {
Id = 3,
Title = " Marvel ",
Price = 150,
PublishDate = new DateTime(2019, 2, 21),
InStock = true,
Description = " Marvel ",
new Book() {
Id = 4,
Title = " The Beauty of Cook",
Price = 450,
PublishDate = new DateTime(2019, 12, 2),
InStock = true,
Description = " The Beauty of Cook ",
new Book() {
Id = 5,
Title = " Learning how to Cook ",
Price = 450,
PublishDate = new DateTime(2020, 1, 20),
InStock = true,
Description = " Learning how to Cook ",
private IValidator<Book> _validator;
public BooksController( IValidator<Book> validator ) {
_validator = validator;
// GET: api/<BooksController>
[HttpGet]
public ActionResult Get() {
try {
return Ok( books );
catch ( Exception ) {
return StatusCode( StatusCodes.Status500InternalServerError,
"Error get books from Server !" );
[HttpPost]
public ActionResult<Book> Post( [FromBody] Book book ) {
try {
if ( book == null ) {
return BadRequest( );
var result = _validator.Validate( book );
if ( !result.IsValid ) {
return StatusCode( StatusCodes.Status400BadRequest, result.Errors );
book.Id = books.MaxBy( b => b.Id )!.Id + 1;
books.Add( book );
return CreatedAtAction( nameof( Post ), new { id = book.Id }, book );
catch ( Exception ) {
return StatusCode( StatusCodes.Status500InternalServerError,
"Error insert book to database !" );
自訂驗證規則
若Fluent Validation程式庫內建的驗證規則不敷使用,也允許自訂。例如想要自訂一個驗證規則檢查字串的內容只能包含文字,不可包含數字或特殊符號,參考程式如下,可以在「BookValidator」類別中加入一個「IsAllLetter」方法撰寫驗證邏輯,方法傳回布林值表明驗證結果。接著我們就可以叫用「RuleFor」方法指明驗證屬性之後,再串接叫用「Must」方法,傳入「IsAllLetter」方法名稱來進行檢查:
using FluentValidation;
using MyModels;
namespace MyRazorWeb.Validator {
public class BookValidator : AbstractValidator<Book> {
public BookValidator() {
RuleFor( x => x.Id )
.NotNull( );
RuleFor( x => x.Title )
.NotNull( )
.WithMessage( "{PropertyName} 不可為空白" )
.MaximumLength( 50 )
.WithMessage( "{PropertyName} 長度不可超過 {MaxLength} " );
RuleFor( x => x.Price )
.NotNull( )
.WithMessage( "{PropertyName} 不可為空白" )
.InclusiveBetween( 1, int.MaxValue )
.WithMessage( "有效範圍在 {From} 與 {To} 之間" );
RuleFor( x => x.PublishDate )
.NotEmpty( )
.WithMessage( "{PropertyName} 不可為空白" );
RuleFor( x => x.Description )
.Length( 0, 50 )
.WithMessage( "長度不可超過 {MaxLength} " )
.Must( IsAllLetter )
.WithMessage( "{PropertyName} 只能包含文字,不可包含數字或特殊符號" )
private bool IsAllLetter( string s ) {
return s.All( char.IsLetter );
330f08fa-ab4e-40df-b05c-df8a67271021|12|4.9
Tags:
.NET | .NET Magazine國際中文電子雜誌 | ASP.NET Razor Pages | 許薰尹Vivid Hsu