跳至主要内容

FluentValidation 驗證指南

用途:以 Fluent API 定義強型別、可測試的驗證規則

NuGetFluentValidation(核心)/ FluentValidation.DependencyInjectionExtensions(DI 整合)

版本:12.x(最新 12.1.1)

授權:Apache 2.0(.NET Foundation 成員)

支援:.NET 8+


為什麼選 FluentValidation

在工業自動化中,我們需要驗證各種輸入:Recipe 參數、設備設定、通訊設定。驗證邏輯需要:集中管理可測試訊息可自訂

方案優勢劣勢
FluentValidation集中管理、強型別、可測試、規則可組合需學習 Fluent API
DataAnnotations簡單、與 ASP.NET 整合好規則寫在 Model 上、不靈活、難以測試複雜規則
手寫 if-else完全自由散亂在各處、難維護、容易遺漏

對比

// ❌ DataAnnotations:規則和 Model 綁死,跨欄位驗證困難
public class Recipe
{
[Required(ErrorMessage = "名稱不可為空")]
[MaxLength(50)]
public string Name { get; set; }

[Range(0, 500, ErrorMessage = "溫度必須在 0-500 之間")]
public double Temperature { get; set; }
}

// ❌ 手寫 if-else:散落各處
if (string.IsNullOrEmpty(recipe.Name))
errors.Add("名稱不可為空");
if (recipe.Temperature < 0 || recipe.Temperature > 500)
errors.Add("溫度必須在 0-500 之間");

// ✅ FluentValidation:集中、可讀、可測試
public class RecipeValidator : AbstractValidator<Recipe>
{
public RecipeValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("名稱不可為空")
.MaximumLength(50).WithMessage("名稱最多 50 個字元");

RuleFor(x => x.Temperature)
.InclusiveBetween(0, 500)
.WithMessage("溫度必須在 0-500°C 之間");
}
}

安裝

dotnet add package FluentValidation
dotnet add package FluentValidation.DependencyInjectionExtensions # DI 整合
using FluentValidation;
using FluentValidation.Results;

核心概念

AbstractValidator<T>

所有驗證器都繼承 AbstractValidator<T>,在建構子中定義規則:

public class DeviceSettingsValidator : AbstractValidator<DeviceSettings>
{
public DeviceSettingsValidator()
{
RuleFor(x => x.IpAddress)
.NotEmpty().WithMessage("IP 位址不可為空")
.Matches(@"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
.WithMessage("IP 位址格式不正確");

RuleFor(x => x.Port)
.InclusiveBetween(1, 65535)
.WithMessage("Port 必須在 1-65535 之間");

RuleFor(x => x.DeviceName)
.NotEmpty()
.MaximumLength(100);

RuleFor(x => x.PollingIntervalMs)
.GreaterThanOrEqualTo(100)
.WithMessage("輪詢間隔至少 100ms")
.LessThanOrEqualTo(60000)
.WithMessage("輪詢間隔最多 60000ms");
}
}

執行驗證

var validator = new DeviceSettingsValidator();
var settings = new DeviceSettings { IpAddress = "", Port = 0 };

ValidationResult result = validator.Validate(settings);

if (!result.IsValid)
{
foreach (var error in result.Errors)
{
Console.WriteLine($"[{error.PropertyName}] {error.ErrorMessage}");
}
}
// 輸出:
// [IpAddress] IP 位址不可為空
// [Port] Port 必須在 1-65535 之間

拋出例外模式

// ValidateAndThrow:驗證失敗直接拋 ValidationException
validator.ValidateAndThrow(settings);

常用內建驗證器

驗證器用途範例
NotEmpty()不可為空(含空白字串)設備名稱
NotNull()不可為 null必填欄位
MaximumLength(n)最大長度名稱最多 50 字
MinimumLength(n)最小長度密碼至少 8 字
InclusiveBetween(a, b)範圍(含端點)溫度 0-500°C
ExclusiveBetween(a, b)範圍(不含端點)
GreaterThan(n)大於冷卻速率 > 0
LessThan(n)小於
GreaterThanOrEqualTo(n)大於等於輪詢間隔 ≥ 100ms
Equal(value)等於
NotEqual(value)不等於
Matches(regex)正則比對IP 格式
EmailAddress()Email 格式
IsInEnum()是有效的 enum 值設備模式
Must(predicate)自訂條件任意邏輯

WithMessage 自訂訊息

RuleFor(x => x.Temperature)
.InclusiveBetween(0, 500)
.WithMessage("溫度 {PropertyValue} 超出範圍,必須在 {From}-{To}°C 之間");
// 輸出:溫度 600 超出範圍,必須在 0-500°C 之間

可用的佔位符:{PropertyName}{PropertyValue}{ComparisonValue}{From}{To}


CascadeMode — 驗證短路

預設情況下,一個屬性的所有規則都會執行(Continue)。用 Cascade(CascadeMode.Stop) 可以在第一個失敗時停止後續規則。下圖以 RuleFor(x.Name).NotEmpty().MaximumLength(50).Matches(regex) 為例對照兩種模式:

Continue(預設):所有規則都跑,累積錯誤

Stop:第一條失敗即停

何時用哪個:

  • Continue(預設):表單驗證 — 一次顯示所有錯誤給使用者,避免「修一個跳一個」的體驗
  • Stop:規則之間有相依(後一條依賴前一條的結果),或後續規則執行成本高(DB 查詢、API 呼叫)時,先擋掉廉價檢查

對應的 C# 寫法:

RuleFor(x => x.RecipeName)
.Cascade(CascadeMode.Stop) // 第一個失敗就停
.NotEmpty().WithMessage("名稱不可為空")
.MaximumLength(50).WithMessage("名稱過長")
.Must(BeUniqueName).WithMessage("名稱已存在"); // 這條會打 DB,先用 NotEmpty + MaximumLength 擋掉

全域設定:

ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;

Child Validator 組合

複雜物件(如 Recipe 包含多個 Step)可用 SetValidator 把子物件的驗證委派給專屬的 Validator,避免單一 Validator 過大:

組合模式的特性:

  • SetValidator:對單一子屬性套用一個子 Validator
  • RuleForEach + SetValidator:對集合中的每一個元素套用同一個子 Validator
  • 錯誤路徑會帶上巢狀路徑:子 Validator 的 RuleFor(s => s.Duration) 失敗時,PropertyName 會是 Steps[0].Duration,方便 UI 對應到正確欄位

子 Validator 可以獨立測試與重用——StepValidator 可以同時被 RecipeValidator 與獨立的「新增 Step 表單」共用。


本指南結構

頁面內容
概觀與核心用法(本頁)AbstractValidator、內建驗證器、CascadeMode
進階用法與場景條件驗證、集合驗證、自訂驗證、DI、WPF/ReactiveUI 整合、測試