基於 .NET 的 FluentValidation 驗證教學

基於 .NET 的 FluentValidation 驗證教學

FluentValidation 是一個基於 .NET 開發的驗證框架,開源免費,而且優雅,支援鏈式操作,易於理解,功能完善,還可與 MVC5、WebApi2 和 ASP.NET CORE 深度整合,組件內提供十幾種常用驗證器,可擴展性好,支援自訂驗證器,支援本地化多語言。

最後更新 2024/1/19 上午5:41
零度
預計閱讀 26 分鐘
分類
.NET
標籤
.NET C# ASP.NET Core 開源 Web API

FluentValidation 是一個基於 .NET 開發的驗證框架,開放原始碼且免費,而且優雅,支援鏈式操作,易於理解,功能完善,還可與 MVC5、WebApi2 和 ASP.NET CORE 深度整合,元件內提供十幾種常用驗證器,可擴充性好,支援自訂驗證器,支援本地化多語言。

雖然 FluentValidation 是一個非常強大的驗證框架,但針對該框架的中文資料並不完善,零度在學習的過程中,將官方文件進行了翻譯,由此產生本文,可供參閱。

要使用驗證框架,需要在專案中加入對 FluentValidation.dll 的參考,支援 netstandard2.0 程式庫和 .NET4.5 平台,支援 .NET Core 平台,最簡單的方法是使用 NuGet 套件管理器參考元件。

Install-Package FluentValidation

若要在 ASP.NET Core 中使用 FluentValidation 擴充,可參考以下套件:

Install-Package FluentValidation.AspNetCore

若要在 ASP.NET MVC 5 或 WebApi 2 專案整合,可以使用分別使用 FluentValidation.Mvc5 和 FluentValidation.WebApi 套件。

Install-Package FluentValidation.Mvc5
Install-Package FluentValidation.WebApi

建立第一個驗證程式

若要為特定物件定義一組驗證規則,您需要建立一個從 AbstractValidator<T> 繼承的類別,其中泛型 T 參數是要驗證的類別的型別。假設您有一個客戶類別:

public class Customer {
  public int Id { get; set; }
  public string Surname { get; set; }
  public string Forename { get; set; }
  public decimal Discount { get; set; }
  public string Address { get; set; }
}

接下來自訂繼承於 AbstractValidator 泛型類別的驗證器,然後在建構函式中使用 LINQ 運算式編寫 RuleFor 驗證規則。

using FluentValidation;

public class CustomerValidator : AbstractValidator<Customer> {
  public CustomerValidator() {
    RuleFor(customer => customer.Surname).NotNull();
  }
}

若要執行驗證程式,我們透過定義好的 CustomerValidator 驗證器傳入實體類別 Customer 即可。

Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();

ValidationResult result = validator.Validate(customer);

該驗證方法回傳一個 ValidationResult 物件,表示驗證結果,ValidationResult 包含兩個屬性:IsValid 屬性是布林值,它表示驗證是否成功,Errors 屬性包含驗證失敗的詳細資訊。

下面的程式碼示範向主控台輸出驗證失敗的詳細資訊:

Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();

ValidationResult results = validator.Validate(customer);

if(! results.IsValid) {
  foreach(var failure in results.Errors) {
    Console.WriteLine("Property " + failure.PropertyName + " Error was: " + failure.ErrorMessage);
  }
}

鏈結規則寫法

您可以將物件的同一屬性用多個驗證程式鏈在一起,以下程式碼將驗證 Surname 屬性不為 Null 的同時且不等於 foo 字串。

using FluentValidation;

public class CustomerValidator : AbstractValidator<Customer> {
  public CustomerValidator() {
    RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
  }
}

引發例外

如果驗證失敗,不想回傳 ValidationResult 物件,而是想直接擲回例外,可透過呼叫驗證器的 ValidateAndThrow 進行驗證。

Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();

validator.ValidateAndThrow(customer);

如果驗證失敗,將引發一個 ValidationException 型別的例外,這個例外可以被上層程式捕捉,並從例外中找到詳細錯誤資訊。

集合

當針對一個集合進行驗證時,只需要定義集合項目標型的規則即可,以下規則將對集合中的每個元素執行 NotNull 檢查。

public class Person {
  public List<string> AddressLines {get;set;} = new List<string>();
}
public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
    RuleForEach(x => x.AddressLines).NotNull();
  }
}

複雜屬性

驗證程式可以用於複雜屬性,假設您有兩個類別:客戶和地址。

public class Customer {
  public string Name { get; set; }
  public Address Address { get; set; }
}

public class Address {
  public string Line1 { get; set; }
  public string Line2 { get; set; }
  public string Town { get; set; }
  public string County { get; set; }
  public string Postcode { get; set; }
}

然後定義一個基於地址的 AddressValidator 驗證器件:

public class AddressValidator : AbstractValidator<Address> {
  public AddressValidator() {
    RuleFor(address => address.Postcode).NotNull();
    //etc
  }
}

然後定義一個基於客戶的 CustomerValidator 驗證器件,對地址驗證時使用地址驗證器。

public class CustomerValidator : AbstractValidator<Customer> {
  public CustomerValidator() {
    RuleFor(customer => customer.Name).NotNull();
    RuleFor(customer => customer.Address).SetValidator(new AddressValidator());
  }
}

另外,還可以在集合屬性上使用驗證程式,假設客戶物件包含訂單集合屬性:

public class Customer {
   public IList<Order> Orders { get; set; }
}

public class Order {
  public string ProductName { get; set; }
  public decimal? Cost { get; set; }
}

var customer = new Customer();
customer.Orders = new List<Order> {
  new Order { ProductName = "Foo" },
  new Order { Cost = 5 }
};

定義了一個 OrderValidator 驗證器件:

public class OrderValidator : AbstractValidator<Order> {
    public OrderValidator() {
        RuleFor(x => x.ProductName).NotNull();
        RuleFor(x => x.Cost).GreaterThan(0);
    }
}

此驗證程式可在 CustomerValidator 中透過 SetCollectionValidator 方法使用:

public class CustomerValidator : AbstractValidator<Customer> {
    public CustomerValidator() {
        RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator());
    }
}

var validator = new CustomerValidator();
var results = validator.Validate(customer);

執行行驗證程式時,透過驗證結果,可以輸出訂單每個屬性驗證錯誤的詳細資訊:

foreach(var result in results.Errors) {
   Console.WriteLine("Property name: " + result.PropertyName);
   Console.WriteLine("Error: " + result.ErrorMessage);
   Console.WriteLine("");
}
Property name: Orders[0].Cost
Error: 'Cost' must be greater than '0'.

Property name: Orders[1].ProductName
Error: 'Product Name' must not be empty.

在編寫驗證規則時,可以透過 Where 關鍵字排除或者篩選不需要驗證的物件。

RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator())
        .Where(x => x.Cost != null);

支援規則集

規則集允許您將驗證規則組合在一起,作為一個組一起執行,同時忽略其他規則,假如:Person 類別有 3 個屬性分別是 Id、Surname 和 Forename,我們將 Id 單獨驗證, Surname 和 Forename 作為一組 Names 規則集進行驗證。

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {
     RuleSet("Names", () => {
        RuleFor(x => x.Surname).NotNull();
        RuleFor(x => x.Forename).NotNull();
     });

     RuleFor(x => x.Id).NotEqual(0);
  }
}

然後,我們可以使用驗證器提供的擴充方法,針對指定的規則集執行驗證,以下程式碼將不驗證 Id 屬性。

var validator = new PersonValidator();
var person = new Person();
var result = validator.Validate(person, ruleSet: "Names");

執行多個規則集驗證,可以使用逗號分隔的字串列表。

validator.Validate(person, ruleSet: "Names,MyRuleSet,SomeOtherRuleSet")

透過*號匹配所有規則,可以強制執行所有規則,不管它們是否在規則集中:

validator.Validate(person, ruleSet: "*")

設定方法

透過在驗證程式上呼叫 WithMessage 方法,可以覆蓋驗證程式的預設驗證錯誤訊息:

RuleFor(customer => customer.Surname).NotNull().WithMessage("姓名不能為空");

錯誤提示中,可以透過 佔位符取代屬性名:

RuleFor(customer => customer.Surname).NotNull().WithMessage("{PropertyName}不能為空");

除了 佔位符,框架還內建了:、、、和 佔位符,關於更多內建佔位符,可以參閱官方文件。

預設情況下,錯誤訊息會輸出屬性名稱,以下規則驗證錯誤時,輸出 Surname 不能為空。

RuleFor(customer => customer.Surname).NotNull();

驗證程式支援透過 WithName 方法來指定屬性別名,以下程式碼輸出姓名不能為空。

RuleFor(customer => customer.Surname).NotNull().WithName("姓名");

FluentValidation 還支援透過 DisplayNameResolver 自訂別名取得邏輯:

ValidatorOptions.DisplayNameResolver = (type, member) => {
  if(member != null) {
     return member.Name + "Foo";
  }
  return null;
};

也可以使用 DisplayName 特性指定屬性別名。

public class Person {
  [Display(Name="Last name")]
  public string Surname { get; set; }
}

某些條件下,可透過 When 方法按條件設定驗證規則,如下程式碼:GreaterThan 規則僅在 IsPreferredCustomer 為 true 時才執行。

RuleFor(customer => customer.CustomerDiscount).GreaterThan(0).When(customer => customer.IsPreferredCustomer);

如果需要為多個規則指定相同的條件,則可以在方法頂層呼叫,而不是在規則結束時將呼叫:

When(customer => customer.IsPreferred, () => {
   RuleFor(customer => customer.CustomerDiscount).GreaterThan(0);
   RuleFor(customer => customer.CreditCardNumber).NotNull();
});

預設情況,編寫多個鏈式驗證規則時,下無論前一個規則失敗與否,後一個規則都將執行,以下程式碼檢查 Surname 是否為空,然後檢查 Surname 不等於零度,如多 NotNull 驗證失敗,則仍將呼叫 NotEqual 驗證。

RuleFor(x => x.Surname).NotNull().NotEqual("零度");

我們可以透過 StopOnFirstFailure 方法制定級聯模式。

RuleFor(x => x.Surname).Cascade(CascadeMode.StopOnFirstFailure).NotNull().NotEqual("foo");

現在,如果 NotNull 驗證程式失敗,NotEqual 驗證程式將不會執行。

CascadeMode 是一個列舉,Continue 表示始終呼叫規則定義中的所有驗證程式,StopOnFirstFailure 表示一旦驗證程式失敗,就停止執行後邊的規則。

若要全域設定級聯模式,可以在應用程式啟動時修改 ValidatorOptions 型別的 CascadeMode 屬性:

ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;

若要為單個驗證程式類別設定級聯模式,可以這樣寫:

public class PersonValidator : AbstractValidator<Person> {
  public PersonValidator() {

    // First set the cascade mode
    CascadeMode = CascadeMode.StopOnFirstFailure;

    // Rule definitions follow
    RuleFor(...)
    RuleFor(...)

   }
}

預設情況下,FluentValidation 中的所有規則都是獨立執行的,無相互影響,有助於非同步驗證提高效能,但是,有些情況下,您希望某些規則僅在另一個規則驗證完成後執行,透過 DependentRules 指定規則依賴,當依賴規則驗證完成時,當前規則則會執行。

RuleFor(x => x.Surname).NotNull().DependentRules(() => {
  RuleFor(x => x.Forename).NotNull();
});

我們可以透過 ValidationContext 上下文的 RootContextData 字典向驗證程式傳遞資料。

var instanceToValidate = new Person();
var context = new ValidationContext(person);
context.RootContextData["MyCustomData"] = "Test";
var validator = new PersonValidator();
validator.Validate(context);

然後,可以透過呼叫 Custom 方法在任何自訂屬性驗證程式內存取 RootContextData 字典。

RuleFor(x => x.Surname).Custom((x, context) => {
  if(context.ParentContext.RootContextData.ContainsKey("MyCustomData")) {
    context.AddFailure("My error message");
  }
});

如果每次呼叫驗證程式前,都需要執行特定的程式碼,可以透過覆寫 PreValidate 方法來攔截驗證程式,該方法回傳 true 驗證程式將繼續,回傳 false 將終止繼續驗證。

public class MyValidator : AbstractValidator<Person> {
  public MyValidator() {
    RuleFor(x => x.Name).NotNull();
  }

  protected override bool PreValidate(ValidationContext<Person> context, ValidationResult result) {
    if (context.InstanceToValidate == null) {
      result.Errors.Add(new ValidationFailure("", "Please ensure a model was supplied."));
      return false;
    }
    return true;
  }
}

內建驗證程式

FluentValidation 有幾個內建的驗證器,這些驗證器的錯誤訊息都可以使用特定佔位符。

NotNull 驗證程式

說明:確保指定的屬性不是 null。

RuleFor(customer => customer.Surname).NotNull();

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性的當前值

NotEmpty 驗證程式

說明:確保指定的屬性不是 null、空字串或空格 (或值型別的預設值,例如 int 0)。

RuleFor(customer => customer.Surname).NotEmpty();

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性的當前值

NotEqual 驗證程式

說明:確保指定屬性的值不等於特定值 (或不等於其他屬性的值)

//Not equal to a particular value
RuleFor(customer => customer.Surname).NotEqual("Foo");

//Not equal to another property
RuleFor(customer => customer.Surname).NotEqual(customer => customer.Forename);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性不應等於的值

Equal 相等驗證程式

說明:確保指定屬性的值等於特定值 (或等於另一個屬性的值)

//Equal to a particular value
RuleFor(customer => customer.Surname).Equal("Foo");

//Equal to another property
RuleFor(customer => customer.Password).Equal(customer => customer.PasswordConfirmation);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性應相等的值 = 屬性的當前值

Length 長度驗證程式

確保特定字串屬性的長度位於指定範圍內。但是,它不能確保字串屬性是否為 null。

RuleFor(customer => customer.Surname).Length(1, 250); //must be between 1 and 250 chars (inclusive)

可用的格式參數佔位符:

= 正在驗證的屬性的名稱 = 最小長度 = 最大長度 = 輸入的字元數 = 屬性的當前值

MaxLength 最大長度驗證程式

說明:確保特定字串屬性的長度不超過指定的值。

RuleFor(customer => customer.Surname).MaximumLength(250); //must be 250 chars or fewer

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 最大長度 = 輸入的字元數 = 屬性的當前值

MinLength 最小長度驗證程式

說明:確保特定字串屬性的長度不能小於指定的值。

RuleFor(customer => customer.Surname).MinimumLength(10); //must be 10 chars or more

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 最小長度 = 輸入的字元數 = 屬性的當前值

LessThan 小於驗證程式

說明:確保指定屬性的值小於特定值 (或小於另一個屬性的值)

//Less than a particular value
RuleFor(customer => customer.CreditLimit).LessThan(100);

//Less than another property
RuleFor(customer => customer.CreditLimit).LessThan(customer => customer.MaxCreditLimit);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 -屬性比較的值 = 屬性的當前值

LessThanOrEqualTo 小於等於驗證程式

說明:確保指定屬性的值小於等於特定值 (或小於等於另一個屬性的值)

//Less than a particular value
RuleFor(customer => customer.CreditLimit).LessThanOrEqualTo(100);

//Less than another property
RuleFor(customer => customer.CreditLimit).LessThanOrEqualTo(customer => customer.MaxCreditLimit);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 -屬性比較的值 = 屬性的當前值

GreaterThan 大於驗證程式

說明:確保指定屬性的值大於特定值 (或大於另一個屬性的值)

//Greater than a particular value
RuleFor(customer => customer.CreditLimit).GreaterThan(0);

//Greater than another property
RuleFor(customer => customer.CreditLimit).GreaterThan(customer => customer.MinimumCreditLimit);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 -屬性比較的值 = 屬性的當前值

GreaterThanOrEqualTo 大於等於驗證程式

說明:確保指定屬性的值大於等於特定值 (或大於等於另一個屬性的值)

//Greater than a particular value
RuleFor(customer => customer.CreditLimit).GreaterThanOrEqualTo(1);

//Greater than another property
RuleFor(customer => customer.CreditLimit).GreaterThanOrEqualTo(customer => customer.MinimumCreditLimit);

可用的格式參數佔位符: = 正在驗證的屬性的名稱 -屬性比較的值 = 屬性的當前值

Must 驗證程式

描述:將指定屬性的值傳遞到一個委派中,可以對該值執行自訂驗證邏輯

RuleFor(customer => customer.Surname).Must(surname => surname == "Foo");

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性的當前值

請注意,委派參數不僅傳遞參數,還支援直接傳遞驗證物件參數:

RuleFor(customer => customer.Surname).Must((customer, surname) => surname != customer.Forename)

正則表達式驗證程式

說明:確保指定屬性的值與給定的正則表達式匹配,正則表達式可參閱正則表達式教學(站長註:連結已失效)這篇文章

RuleFor(customer => customer.Surname).Matches("some regex here");

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性的當前值

Email 電子郵件驗證程式

說明:確保指定屬性的值是有效的電子郵件地址格式。

RuleFor(customer => customer.Email).EmailAddress();

可用的格式參數佔位符: = 正在驗證的屬性的名稱 = 屬性的當前值

自訂驗證程式

Must 驗證程式

實作自訂驗證程式的最簡單方法是使用方法 Must 方法,假設我們有以下類別:

public class Person {
  public IList<Person> Pets {get;set;} = new List<Person>();
}

為了確保列表中至少包含 10 個元素,我們可以這樣做:

public class PersonValidator:AbstractValidator<Person> {
  public PersonValidator() {
   RuleFor(x => x.Pets).Must(list => list.Count <= 10).WithMessage("The list must contain fewer than 10 items");
  }
}

為了使這種邏輯可重複使用,我們可以將其封裝為擴充方法。

public static class MyCustomValidators {
  public static IRuleBuilderOptions<T, IList<TElement>> ListMustContainFewerThan<T, TElement>(this IRuleBuilder<T, IList<TElement>> ruleBuilder, int num) {
	return ruleBuilder.Must(list => list.Count < num).WithMessage("The list contains too many items");
  }
}

在這裡,我們透過為 IRuleBuilder 建立擴充方法實現可重複使用邏輯,使用方法很簡單。

RuleFor(x => x.Pets).ListMustContainFewerThan(10);

編寫自訂驗證程式

如果您想靈活控制可重複使用的驗證器,則可以使用 Must 方法編寫自訂規則,此方法允許您手動建立與驗證錯誤關聯的實例。

public class PersonValidator:AbstractValidator<Person> {
  public PersonValidator() {
   RuleFor(x => x.Pets).Custom((list, context) => {
     if(list.Count > 10) {
       context.AddFailure("The list must contain 10 items or fewer");
     }
   });
  }
}

此方法的優點是它允許您為同一規則回傳多個錯誤。

context.AddFailure("SomeOtherProperty", "The list must contain 10 items or fewer");
// Or you can instantiate the ValidationFailure directly:
context.AddFailure(new ValidationFailure("SomeOtherProperty", "The list must contain 10 items or fewer");

自訂屬性驗證程式

在某些情況下,針對某些屬性的驗證邏輯非常複雜,我們希望將基於屬性的自訂邏輯移動到單獨的類別中,可透過覆寫 PropertyValidator 類別來完成。

using System.Collections.Generic;
using FluentValidation.Validators;
public class ListCountValidator<T> : PropertyValidator {
        private int _max;

	public ListCountValidator(int max)
		: base("{PropertyName} must contain fewer than {MaxElements} items.") {
		_max = max;
	}

	protected override bool IsValid(PropertyValidatorContext context) {
		var list = context.PropertyValue as IList<T>;

		if(list != null && list.Count >= _max) {
			context.MessageFormatter.AppendArgument("MaxElements", _max);
			return false;
		}

		return true;
	}
}

繼承 PropertyValidator 時,必須覆寫 IsValid 方法,此方法接受一個物件,並回傳一個布林值,指示驗證是否成功,可透過 PropertyValidatorContext 屬性存取: Instance-正在驗證的物件 PropertyDescription-屬性的名稱 (或者是由呼叫 WithName 的自訂的別名) PropertyValue-正在驗證的屬性值 Member-描述正在驗證的屬性的 MemberInfo 資訊

若要使用自訂的屬性驗證程式,可以在定義驗證規則時呼叫:

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
       RuleFor(person => person.Pets).SetValidator(new ListCountValidator<Pet>(10));
    }
}

本地化與多語言

FluentValidation 為預設驗證訊息提供幾種語言的翻譯,預設情況下,會根據目前執行緒的 CurrentUICulture 語言文化來選擇語言,你也可以使用 WithMessage 和 WithLocalizedMessage 來指定錯誤提示。

WithMessage

如果使用 Visual Studio 的內建的 resx 格式資源檔,則可以透過呼叫 WithMessage 本地化錯誤訊息。

RuleFor(x => x.Surname).NotNull().WithMessage(x => MyLocalizedMessages.SurnameRequired);

當然,您可以將多種語言儲存在資料庫中,透過 lambda 運算式來讀取多語言訊息。

WithLocalizedMessage

您可以透過呼叫 WithLocalizedMessage 方法,傳遞資源類型和資源名稱,使錯誤訊息支援本地多語言。

RuleFor(x => x.Surname).NotNull().WithLocalizedMessage(typeof(MyLocalizedMessages), "SurnameRequired");

預設錯誤訊息

如果您要取代 FluentValidation 的預設錯誤提示,可以實作 ILanguageManager 介面,該介面支援本地化多語言。

public class CustomLanguageManager : FluentValidation.Resources.LanguageManager {
  public CustomLanguageManager() {
    AddTranslation("en", "NotNullValidator", "'{PropertyName}' is required.");
  }
}

以上程式碼為 NotNullValidator 驗證器自訂英文錯誤提示訊息,當然也可以實現更多的語言,定義好 LanguageManager 後,需要在啟動時進行設定。

ValidatorOptions.LanguageManager = new CustomLanguageManager();

停用本地化多語言

您可以完全停用 FluentValidation 對本地化的支援,這將強制使用預設的英文語言,而不考慮執行緒的 CurrentUICulture。這可以透過靜態類別 ValidatorOptions 在應用程式的啟動常式中完成。

ValidatorOptions.LanguageManager.Enabled = false;

還可以強制指定預設語言,始終以特定語言顯示:

ValidatorOptions.LanguageManager.Culture = new CultureInfo("fr");

ASP.NET Core 整合

FluentValidation 可以與 ASP.NET Core 整合,要啟用 MVC 整合,您需要透過 NuGet 套件來加入對組件 FluentValidation.AspNetCore 的參考:

Install-Package FluentValidation.AspNetCore

安裝後,您需要參考命名空間 using FluentValidation.AspNetCore,然後,在應用程式的啟動類別 Startup 中透過呼叫 services 的擴充方法 AddFluentValidation 進行設定。

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc(setup => {
      //...mvc setup...
    }).AddFluentValidation();
}

為了讓 ASP.NET Core 發現您的驗證程式,必須在服務集容器中註冊它們。

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc(setup => {
      //...mvc setup...
    }).AddFluentValidation();

    services.AddTransient<IValidator<Person>, PersonValidator>();
    //etc
}

或使用 AddFromAssemblyContaining 方法自動註冊特定組件中的所有驗證器。

services.AddMvc()
  .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonValidator>());

本範例定義 PersonValidator 驗證器,用於驗證 Person 型別。

public class Person {
	public int Id { get; set; }
	public string Name { get; set; }
	public string Email { get; set; }
	public int Age { get; set; }
}

public class PersonValidator : AbstractValidator<Person> {
	public PersonValidator() {
		RuleFor(x => x.Id).NotNull();
		RuleFor(x => x.Name).Length(0, 10);
		RuleFor(x => x.Email).EmailAddress();
		RuleFor(x => x.Age).InclusiveBetween(18, 60);
	}
}

定義控制器 PeopleController 用於建立並儲存 Person 實例,使用 ModelState.IsValid 判斷使用者的輸入是否驗證通過。

public class PeopleController : Controller {
	public ActionResult Create() {
		return View();
	}

	[HttpPost]
	public IActionResult Create(Person person) {

		if(! ModelState.IsValid) { // re-render the view when validation failed.
			return View("Create", person);
		}

		Save(person); //Save the person to the database, or some other logic

		TempData["notice"] = "Person successfully created";
		return RedirectToAction("Index");

	}
}

這是我們建立 Person 的檢視程式碼:

@model Person

<div asp-validation-summary="ModelOnly"></div>

<form asp-action="Create">
  Id: <input asp-for="Id" /> <span asp-validation-for="Id"></span>
  <br />
  Name: <input asp-for="Name" /> <span asp-validation-for="Name"></span>
  <br />
  Email: <input asp-for="Email" /> <span asp-validation-for="Email"></span>
  <br />
  Age: <input asp-for="Age" /> <span asp-validation-for="Age"></span>

  <br /><br />
  <input type="submit" value="submtit" />
</form>

現在,當您提交表單時,MVC 的模型繫結系統將使用 PersonPersonValidator 驗證輸入,並將驗證結果儲存至 ModelState 用於判斷。

與 ASP.NET Core 內建驗證的相容性

預設情況下,在執行 FluentValidation 驗證之後,MVC 原生內建的 DataAnnotations 驗證方式也可能執行,這意味著您可以將 FluentValidation 與 DataAnnotations 驗證混合在一起使用。如果要停用此行為,將 FluentValidation 設為唯一的驗證程式庫,可以將 RunDefaultMvcValidationAfterFluentValidationExecutes 屬性設為 false 值:

services.AddMvc().AddFluentValidation(fv => {
 fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});

隱含與顯式子屬性驗證

在驗證複雜物件時,預設情況下,您必須手動透過 SetValidator 方法指定複雜屬性的子驗證器。執行 ASP.NET MVC 應用程式時,還可以選擇為子屬性啟用隱含驗證,啟用此功能時,MVC 的驗證基礎結構將會自動尋找每個屬性的驗證程式,而不必指定的子驗證程式,這可以透過將 SetValidatorImplicitlyValidateChildProperties 設定為 true 來完成:

services.AddMvc().AddFluentValidation(fv => {
 fv.ImplicitlyValidateChildProperties = true;
});

請注意,如果啟用此行為,則不應手動透過 SetValidator 指定子屬性驗證程式,否則,驗證程式將執行兩次。

用戶端驗證

FluentValidation 是伺服器端驗證框架,不直接提供任何用戶端驗證。但是,它可以產生 jQuery 驗證框架支援的 HTML 元素元資料,用於支援 jquery.validate 框架的自動驗證。

手動驗證

有時您可能需要手動驗證 MVC 專案中的物件,在這種情況下,我們可以將驗證結果複製到 MVC 的 ModelState 字典中,便可用於前端錯誤提示。

public ActionResult DoSomething() {
  var customer = new Customer();
  var validator = new CustomerValidator();
  var results = validator.Validate(customer);

  results.AddToModelState(ModelState, null);
  return View();
}

AddToModelState 方法是作為擴充方法實現的,需要參考 FluentValidation 命名空間,請注意,第二個參數是可選的模型名稱前置詞,該參數可設定物件屬性在 ModelState 字典中的前置詞。

驗證程式自訂

您可以使用 CustomizeValidatorAttribute 為模型指定驗證程式,也支援為驗證器指定規則集。

public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
  // ...
}

這相當於為驗證指定規則集,等同於將規則集傳遞給驗證程式:

var validator = new CustomerValidator();
var customer = new Customer();
var result = validator.Validate(customer, ruleSet: "MyRuleset");

該屬性還可用於呼叫單個屬性的驗證:

public ActionResult Save([CustomizeValidator(Properties="Surname,Forename")] Customer cust) {
  // ...
}

這相當於對驗證程式指定特定屬性,其他屬性將不被驗證:

var validator = new CustomerValidator();
var customer = new Customer();
var result = validator.Validate(customer, properties: new[] { "Surname", "Forename" });

也可以使用 CustomizeValidatorAttribute 特性跳過某些型別的驗證。

public ActionResult Save([CustomizeValidator(Skip=true)] Customer cust) {
  // ...
}

驗證器攔截器

您可以使用攔截器進一步自訂驗證過程,攔截器必須實作 FluentValidation.Mvc 命名空間中的 IValidatorInterceptor 介面:

public interface IValidatorInterceptor	{
  ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext);
  ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result);
}

此介面有兩個方法:BeforeMvcValidation 和 AfterMvcValidation,分別可攔截驗證前和驗證後的過程。除了在驗證程式類別中直接實作此介面外,我們還可以在外部實作該介面,透過 CustomizeValidatorAttribute 特性指定攔截器:

public ActionResult Save([CustomizeValidator(Interceptor=typeof(MyCustomerInterceptor))] Customer cust) {
 //...
}

在這種情況下,攔截器必須是一個實作 IValidatorInterceptor 介面,並具有公開無參數建構函式的類別。請注意,攔截器是進階方案,大多數情況下,您可能不需要使用攔截器,但如果需要,可以選擇它。

為用戶端指定規則集

預設情況下 FluentValidation 不會為用戶端產生基於規則集的驗證程式碼,但您可以透過 RuleSetForClientSideMessagesAttribute 為用戶端指定規則集。

[RuleSetForClientSideMessages("MyRuleset")]
public ActionResult Index() {
   return View(new PersonViewModel());
}

也可以在控制器中使用 SetRulesetForClientsideMessages 擴充方法 (需要參考 FluentValidation 命名空間)為用戶端指定規則集。

public ActionResult Index() {
   ControllerContext.SetRulesetForClientsideMessages("MyRuleset");
   return View(new PersonViewModel());
}

ASP.NET MVC 5 整合

FluentValidation 還可以與舊的 ASP.NET MVC 5 專案整合,需要透過 NuGet 加入對組件 FluentValidation.Mvc5 的參考,然後參考 FluentValidation.Mvc 命名空間:

Install-Package FluentValidation.Mvc5

安裝後,需要在應用程式全域檔案 (Global.asax) 中設定 Application_Start 事件,以便於 FluentValidation 執行初始化。

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    FluentValidationModelValidatorProvider.Configure();
}

在 MVC 內部,FluentValidation 利用驗證器工廠為特定型別建立驗證程式,預設情況下使用 AttributedValidatorFactory 工廠,透過特性的方式,允許您為指定的模型設定驗證程式:

[Validator(typeof(PersonValidator))]
public class Person {
	public int Id { get; set; }
	public string Name { get; set; }
	public string Email { get; set; }
	public int Age { get; set; }
}

public class PersonValidator : AbstractValidator<Person> {
	public PersonValidator() {
		RuleFor(x => x.Id).NotNull();
		RuleFor(x => x.Name).Length(0, 10);
		RuleFor(x => x.Email).EmailAddress();
		RuleFor(x => x.Age).InclusiveBetween(18, 60);
	}
}

您還可以使用 MVC 自帶的 IoC 相依性插入容器實現自訂的驗證器工廠,而不是使用上述特性標記的方法。

FluentValidationModelValidatorProvider.Configure(provider => {
  provider.ValidatorFactory = new MyCustomValidatorFactory();
});

最後,我們可以建立控制器和與之關聯的檢視:

public class PeopleController : Controller {
	public ActionResult Create() {
		return View();
	}

	[HttpPost]
	public ActionResult Create(Person person) {

		if(! ModelState.IsValid) { // re-render the view when validation failed.
			return View("Create", person);
		}

		TempData["notice"] = "Person successfully created";
		return RedirectToAction("Index");

	}
}

這是相應的檢視程式碼:

@Html.ValidationSummary() @using (Html.BeginForm()) { Id: @Html.TextBoxFor(x =>
x.Id) @Html.ValidationMessageFor(x => x.Id)
<br />
Name: @Html.TextBoxFor(x => x.Name) @Html.ValidationMessageFor(x => x.Name)
<br />
Email: @Html.TextBoxFor(x => x.Email) @Html.ValidationMessageFor(x => x.Email)
<br />
Age: @Html.TextBoxFor(x => x.Age) @Html.ValidationMessageFor(x => x.Age)

<br /><br />

<input type="submit" value="submit" />
}

現在,當您提交表單時,MVC 將使用 FluentValidation 框架驗證模型。

特別說明

在 ASP.NET MVC 5 中使用 FluentValidation 框架,和 ASP.NET Core 基本相同,所以,用戶端驗證、手動驗證、驗證程式自訂、驗證器攔截器和用戶端規則集都類似於上文,由於文章篇幅,這裡將不再贅述。

ASP.NET WebApi 2 整合

FluentValidation 的 WebApi 的整合與 MVC 5 整合 (上面) 相同,但您需要透過 NuGet 參考 FluentValidation.WebApi 套件。

出處:https://www.xcode.me/post/5849(站長註:該連結已失敗,可在部落格園瀏覽轉載文章:https://www.cnblogs.com/mq0036/p/14548370.html)

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2024/6/20

CodeWF.EventBus:輕量級事件匯流排,讓通訊更流暢

CodeWF.EventBus,一款靈活的事件匯流排庫,實現模組間解耦通訊。支援多種.NET專案類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現命令的發布與訂閱、請求與回應。透過有序的事件處理,確保事件得到妥善處理。簡化您的程式碼,提升系統可維護性。

繼續閱讀