簡介
FluentValidation是一個基於“流式API”(Fluent API)的.NET驗證框架,用於在應用層對模型(DTO、ViewModel、Entity等)進行聲明式驗證。-
核心優勢:
- 高可讀性:通過鏈式方法配置驗證規則,邏輯清晰;
- 可複用:將驗證代碼從業務邏輯中分離,易於單元測試;
- 豐富的內置規則:郵箱、長度、正則、多字段聯動、集合驗證等;
- 可擴展:支持自定義驗證器、異步驗證、跨屬性驗證。
-
適用場景:
Web API模型驗證- 複雜業務規則驗證
- 需要高度可定製驗證邏輯的系統
- 多語言驗證消息需求
- 需要測試覆蓋的驗證邏輯
安裝與基礎配置
NuGet包
Install-Package FluentValidation
Install-Package FluentValidation.DependencyInjectionExtensions
- 引用命名空間
using FluentValidation;
核心用法
定義 Model 與 Validator
public class UserDto
{
public string Username { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class UserDtoValidator : AbstractValidator<UserDto>
{
public UserDtoValidator()
{
// NotEmpty / NotNull
RuleFor(x => x.Username)
.NotEmpty().WithMessage("用户名不能為空")
.Length(3, 20).WithMessage("用户名長度須在3到20之間");
// Email 格式
RuleFor(x => x.Email)
.NotEmpty().WithMessage("郵箱不能為空")
.EmailAddress().WithMessage("郵箱格式不正確");
// 數值範圍
RuleFor(x => x.Age)
.InclusiveBetween(18, 120)
.WithMessage("年齡須在18到120之間");
}
}
執行驗證
var user = new UserDto { Username = "", Email = "bad", Age = 10 };
var validator = new UserDtoValidator();
var result = validator.Validate(user);
if (!result.IsValid)
{
foreach (var failure in result.Errors)
{
Console.WriteLine($"{failure.PropertyName}: {failure.ErrorMessage}");
}
}
// ASP.NET Core 自動驗證
[HttpPost]
public IActionResult CreateUser([FromBody] UserDto user)
{
// 模型綁定後自動驗證
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// ...
}
常用驗證規則
| 方法 | 作用 |
|---|---|
NotNull() / NotEmpty() |
非空或非空串 |
Length(min, max) |
字符串長度範圍 |
EmailAddress() |
郵箱格式 |
Matches(regex) |
正則匹配 |
InclusiveBetween(min, max) |
數值範圍 |
GreaterThan(x) / LessThan(x) |
大小比較 |
Must(predicate) |
自定義同步條件 |
MustAsync(asyncPredicate) |
自定義異步條件 |
進階特性
跨屬性驗證
RuleFor(x => x.EndDate)
.GreaterThan(x => x.StartDate)
.WithMessage("結束時間必須晚於開始時間");
條件驗證
RuleFor(x => x.Password)
.NotEmpty().When(x => x.RequirePassword)
.WithMessage("密碼不能為空");
集合與嵌套對象
public class OrderDto { public List<OrderItemDto> Items { get; set; } }
public class OrderItemDto { public int Quantity { get; set; } }
public class OrderDtoValidator : AbstractValidator<OrderDto>
{
public OrderDtoValidator()
{
RuleForEach(x => x.Items)
.ChildRules(items =>
{
items.RuleFor(i => i.Quantity)
.GreaterThan(0).WithMessage("數量須大於0");
});
}
}
異步驗證
RuleFor(x => x.Username)
.MustAsync(async (name, ct) => !await userRepo.ExistsAsync(name))
.WithMessage("用户名已存在");
級聯驗證
使用 CascadeMode.Stop 提高性能
RuleFor(x => x.Name).Cascade(CascadeMode.Stop).NotEmpty().MaximumLength(50);
驗證規則組織
規則集(RuleSets)
public class UserValidator : AbstractValidator<UserDto>
{
public UserValidator()
{
// 公共規則
RuleFor(user => user.Name).NotEmpty();
// 創建規則集
RuleSet("Admin", () =>
{
RuleFor(user => user.IsAdmin).Must(b => b == true)
.WithMessage("管理員用户必須設置管理員標誌");
});
RuleSet("PaymentInfo", () => {
RuleFor(c => c.CreditCardNumber).NotEmpty();
RuleFor(c => c.CreditCardExpiry).NotEmpty();
});
}
}
// 使用指定規則集
var result = validator.Validate(user, options =>
{
options.IncludeRuleSets("Admin", "PaymentInfo");
});
繼承與組合
// 基礎驗證器
public class PersonValidator : AbstractValidator<PersonDto>
{
public PersonValidator()
{
RuleFor(p => p.Name).NotEmpty();
RuleFor(p => p.BirthDate).LessThan(DateTime.Now);
}
}
// 繼承擴展
public class EmployeeValidator : PersonValidator
{
public EmployeeValidator()
{
Include(new PersonValidator()); // 包含基礎規則
RuleFor(e => e.EmployeeId).NotEmpty();
RuleFor(e => e.Department).NotEmpty();
}
}
// 組合驗證
public class AdvancedUserValidator : AbstractValidator<UserDto>
{
public AdvancedUserValidator()
{
Include(new UserValidator());
RuleFor(u => u.SecurityLevel).InclusiveBetween(1, 5);
}
}
自定義擴展
自定義驗證器
public static class CustomValidators
{
public static IRuleBuilderOptions<T, string> ValidIdCard<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must(id => Regex.IsMatch(id, @"^[1-9]\d{16}[0-9X]$"))
.WithMessage("身份證格式不正確");
}
}
// 使用
RuleFor(x => x.IdCard).ValidIdCard();
自定義屬性比較器
public class DateRangeValidator : PropertyValidator
{
public DateRangeValidator() : base("{PropertyName} 時間範圍不合法") { }
protected override bool IsValid(PropertyValidatorContext context)
{
var dto = (MyDto)context.InstanceToValidate;
return dto.End > dto.Start;
}
}
// 在 Validator 中
RuleFor(x => x.Start).SetValidator(new DateRangeValidator());
ASP.NET Core 集成
註冊服務(Program.cs)
builder.Services
.AddControllers()
.AddFluentValidation(cfg =>
{
// 自動註冊當前程序集所有繼承 AbstractValidator 的類型
cfg.RegisterValidatorsFromAssemblyContaining<Startup>();
// 禁用 DataAnnotations 驗證(可選)
cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});
自動觸發
ASP.NET Core在模型綁定後會自動調用對應Validator,並將錯誤添加到ModelState。- 在
Controller中可直接檢查if (!ModelState.IsValid)或依賴[ApiController]的自動返回行為。
自定義錯誤響應
// 配置全局異常處理
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var errors = context.ModelState
.Where(e => e.Value.Errors.Count > 0)
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
return new BadRequestObjectResult(new
{
Code = 400,
Message = "請求驗證失敗",
Errors = errors
});
};
});
最佳實踐
-
分層組織規則:
- 針對同一模型,可拆分多個
Validator(或使用Include()),保持單一職責。
- 針對同一模型,可拆分多個
-
複用規則集:
- 對於常見字段(如郵箱、手機號),可定義公共規則並通過擴展方法重用。
-
錯誤消息國際化:
- 將消息文本放入資源文件,使用
.WithMessage(x => Resources.FieldRequired)。
- 將消息文本放入資源文件,使用
-
性能考慮:
- 異步驗證會序列化執行,若有多個異步規則,可合併或避免不必要的數據庫調用。
-
測試驗證器:
- 為每個
Validator編寫單元測試,覆蓋正常和邊界情況,確保規則生效。
- 為每個
-
日誌與監控:
- 在全局捕獲驗證失敗日誌,統計常見錯誤,優化用户體驗。
資源與擴展
- GitHub:
https://github.com/FluentValidation/FluentValidation - 文檔:
https://docs.fluentvalidation.net -
NuGet包:FluentValidation:核心驗證庫。FluentValidation.DependencyInjectionExtensions:ASP.NET Core集成。
-
擴展:
- 支持
Blazor、MVC和Web API。 - 提供多語言支持和客户端驗證。
- 支持