编辑
2025-02-05
C# 应用
00
请注意,本文编写于 90 天前,最后修改于 90 天前,其中某些信息可能已经过时。

目录

简介
安装和基本设置
创建验证器
常用验证规则
自定义验证规则
条件验证
集合验证
级联验证
与ASP.NET Core集成
最佳实践
结论

简介

FluentValidation是一个流行的.NET验证库,它提供了一种优雅的方式来定义强类型的验证规则。它的流畅接口使得创建复杂的验证逻辑变得简单直观。本文将深入探讨FluentValidation的各种特性和用法,并提供丰富的示例来说明如何在实际项目中应用这些概念。

安装和基本设置

首先,让我们通过NuGet包管理器安装FluentValidation:

Markdown
dotnet add package FluentValidation

image.png

对于ASP.NET Core项目,你可能还想安装集成包:

Markdown
dotnet add package FluentValidation.AspNetCore

创建验证器

让我们从一个简单的例子开始,创建一个用户模型和相应的验证器:

C#
public class User { public int Id { get; set; } public string Username { get; set; } public string Email { get; set; } public DateTime DateOfBirth { get; set; } } public class UserValidator : AbstractValidator<User> { public UserValidator() { RuleFor(user => user.Username).NotEmpty().Length(3, 20); RuleFor(user => user.Email).NotEmpty().EmailAddress(); RuleFor(user => user.DateOfBirth).NotEmpty().LessThan(DateTime.Today); } }

这个验证器可以确保:

  1. 用户名不为空,且长度适当
  2. 提供了有效的电子邮件地址
  3. 提供了有效的出生日期
C#
static void Main(string[] args) { var user = new User { Username = "john", Email = "john@example.com", DateOfBirth = new DateTime(1990, 1, 1) }; var validator = new UserValidator(); var result = validator.Validate(user); if (result.IsValid) { Console.WriteLine("User is valid"); } else { foreach (var error in result.Errors) { Console.WriteLine(error.ErrorMessage); } } }

在这个例子中,我们为User类创建了一个验证器,定义了用户名、邮箱和出生日期的基本验证规则。

常用验证规则

FluentValidation提供了许多内置的验证规则。以下是一些常用的规则:

C#
public enum ProductCategory { Electronics, Clothing, Food, Books, Toys } public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public int StockQuantity { get; set; } public string Description { get; set; } public ProductCategory Category { get; set; } public List<string> Tags { get; set; } } public class ProductValidator : AbstractValidator<Product> { public ProductValidator() { RuleFor(p => p.Name) .NotEmpty().WithMessage("产品名称不能为空") .MaximumLength(50).WithMessage("产品名称不能超过50个字符"); RuleFor(p => p.Price) .GreaterThan(0).WithMessage("价格必须大于0") .LessThanOrEqualTo(1000000).WithMessage("价格不能超过1,000,000"); RuleFor(p => p.StockQuantity) .InclusiveBetween(0, 10000).WithMessage("库存数量必须在0到10,000之间"); RuleFor(p => p.Description) .Length(10, 500).WithMessage("描述必须在10到500个字符之间"); RuleFor(p => p.Category) .IsInEnum().WithMessage("无效的产品类别"); RuleFor(p => p.Tags) .Must(tags => tags != null && tags.All(tag => tag.Length <= 20)) .WithMessage("每个标签不能超过20个字符"); } }
  • 产品名称不能为空且不超过50个字符
  • 价格必须大于0且不超过1,000,000
  • 库存数量必须在0到10,000之间
  • 描述长度必须在10到500个字符之间
  • 类别必须是有效的枚举值
  • 每个标签不能超过20个字符
C#
static void Main(string[] args) { var product = new Product { Name = "Super Gadget", Price = 199.99m, StockQuantity = 99999, Description = "This is a fantastic gadget that does amazing things.", Category = ProductCategory.Electronics, Tags = new List<string> { "gadget", "electronic", "cool" } }; var validator = new ProductValidator(); var result = validator.Validate(product); if (result.IsValid) { Console.WriteLine("Product is valid"); } else { foreach (var error in result.Errors) { Console.WriteLine(error.ErrorMessage); } } }

image.png

这个例子展示了多种常用的验证规则,包括非空检查、长度限制、数值范围检查、枚举验证等。

自定义验证规则

有时,内置的验证规则可能无法满足特定需求。在这种情况下,我们可以创建自定义验证规则:

C#
public class Order { public int Id { get; set; } public DateTime DeliveryDate { get; set; } public string CustomerCode { get; set; } // 可以根据需要添加更多属性 } public class OrderValidator : AbstractValidator<Order> { public OrderValidator() { RuleFor(order => order.DeliveryDate) .Must(BeAWorkingDay) .WithMessage("送货日期必须是工作日"); RuleFor(order => order.CustomerCode) .Must(BeAValidCustomerCode) .WithMessage("无效的客户代码"); } private bool BeAWorkingDay(DateTime date) { return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday; } private bool BeAValidCustomerCode(string code) { // 假设有效的客户代码是以"CUST-"开头,后跟5个数字 return Regex.IsMatch(code, @"^CUST-\d{5}$"); } }
  • 送货日期必须是工作日(非周六或周日)
  • 客户代码必须符合特定格式("CUST-" 后跟5个数字)
C#
static void Main(string[] args) { var order = new Order { Id = 1, DeliveryDate = DateTime.Now.AddDays(1), // 假设明天是工作日 CustomerCode = "CUST-12345" }; var validator = new OrderValidator(); var result = validator.Validate(order); if (result.IsValid) { Console.WriteLine("Order is valid"); } else { foreach (var error in result.Errors) { Console.WriteLine(error.ErrorMessage); } } // 测试无效的订单 var invalidOrder = new Order { Id = 2, DeliveryDate = new DateTime(2023, 7, 1), // 假设这是一个周六 CustomerCode = "INVALID-CODE" }; var invalidResult = validator.Validate(invalidOrder); if (!invalidResult.IsValid) { Console.WriteLine("\nInvalid order errors:"); foreach (var error in invalidResult.Errors) { Console.WriteLine(error.ErrorMessage); } } }

image.png

在这个例子中,我们创建了两个自定义验证规则:一个检查日期是否为工作日,另一个验证客户代码的格式。

条件验证

FluentValidation允许我们基于某些条件应用验证规则:

C#
public class RegistrationValidator : AbstractValidator<Registration> { public RegistrationValidator() { RuleFor(r => r.Email).NotEmpty().EmailAddress(); RuleFor(r => r.Age).NotEmpty(); When(r => r.Age < 18, () => { RuleFor(r => r.ParentConsent) .NotEmpty() .Equal(true).WithMessage("未成年人需要父母同意才能注册"); }); RuleFor(r => r.Password).NotEmpty().Length(8, 20); RuleFor(r => r.ConfirmPassword) .Equal(r => r.Password).WithMessage("确认密码必须与密码相同") .When(r => !string.IsNullOrWhiteSpace(r.Password)); } }

这个例子展示了如何使用When方法来应用条件验证,例如只有当年龄小于18岁时才要求父母同意,以及只有在密码不为空时才验证确认密码。

集合验证

FluentValidation可以验证集合中的每个元素:

C#
public class Order { public int Id { get; set; } public List<OrderItem> Items { get; set; } } public class OrderItem { public int ProductId { get; set; } public int Quantity { get; set; } } public class OrderValidator : AbstractValidator<Order> { public OrderValidator() { RuleFor(o => o.Items).NotEmpty().WithMessage("订单必须包含至少一个商品"); RuleForEach(o => o.Items).SetValidator(new OrderItemValidator()); } } public class OrderItemValidator : AbstractValidator<OrderItem> { public OrderItemValidator() { RuleFor(item => item.ProductId).GreaterThan(0).WithMessage("产品ID必须大于0"); RuleFor(item => item.Quantity).InclusiveBetween(1, 100).WithMessage("数量必须在1到100之间"); } }
  • 订单包含至少一个商品。
  • 每个商品都有有效的产品 ID(大于 0)。
  • 每个商品的数量都在合理范围内(1 到 100)。
C#
static void Main(string[] args) { // 创建一个有效的订单 var validOrder = new Order { Id = 1, Items = new List<OrderItem> { new OrderItem { ProductId = 1, Quantity = 5 }, new OrderItem { ProductId = 2, Quantity = 3 } } }; // 创建一个无效的订单 var invalidOrder = new Order { Id = 2, Items = new List<OrderItem> { new OrderItem { ProductId = 0, Quantity = 0 }, new OrderItem { ProductId = 3, Quantity = 101 } } }; var validator = new OrderValidator(); // 验证有效订单 var validResult = validator.Validate(validOrder); Console.WriteLine("Valid Order Validation Result:"); PrintValidationResult(validResult); // 验证无效订单 var invalidResult = validator.Validate(invalidOrder); Console.WriteLine("\nInvalid Order Validation Result:"); PrintValidationResult(invalidResult); } static void PrintValidationResult(FluentValidation.Results.ValidationResult result) { if (result.IsValid) { Console.WriteLine("Order is valid"); } else { foreach (var error in result.Errors) { Console.WriteLine($"- {error.ErrorMessage}"); } } }

这个例子展示了如何验证订单中的每个订单项。

级联验证

FluentValidation支持级联验证,允许我们验证复杂对象的属性:

C#
public class Customer { public string Name { get; set; } public Address BillingAddress { get; set; } public Address ShippingAddress { get; set; } } public class Address { public string Street { get; set; } public string City { get; set; } public string ZipCode { get; set; } } public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(c => c.Name).NotEmpty().WithMessage("客户名称不能为空"); RuleFor(c => c.BillingAddress).SetValidator(new AddressValidator()).WithMessage("账单地址无效"); RuleFor(c => c.ShippingAddress).SetValidator(new AddressValidator()).WithMessage("送货地址无效"); } } public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(a => a.Street).NotEmpty().WithMessage("街道不能为空"); RuleFor(a => a.City).NotEmpty().WithMessage("城市不能为空"); RuleFor(a => a.ZipCode).NotEmpty().Matches(@"^\d{5}(-\d{4})?$").WithMessage("邮政编码格式无效"); } }
C#
static void Main(string[] args) { // 创建一个有效的客户 var validCustomer = new Customer { Name = "John Doe", BillingAddress = new Address { Street = "123 Billing St", City = "Billing City", ZipCode = "12345" }, ShippingAddress = new Address { Street = "456 Shipping St", City = "Shipping City", ZipCode = "67890-1234" } }; // 创建一个无效的客户 var invalidCustomer = new Customer { Name = "", BillingAddress = new Address { Street = "", City = "Billing City", ZipCode = "INVALID" }, ShippingAddress = new Address { Street = "789 Shipping St", City = "", ZipCode = "54321" } }; var validator = new CustomerValidator(); // 验证有效客户 var validResult = validator.Validate(validCustomer); Console.WriteLine("Valid Customer Validation Result:"); PrintValidationResult(validResult); // 验证无效客户 var invalidResult = validator.Validate(invalidCustomer); Console.WriteLine("\nInvalid Customer Validation Result:"); PrintValidationResult(invalidResult); } static void PrintValidationResult(FluentValidation.Results.ValidationResult result) { if (result.IsValid) { Console.WriteLine("Customer is valid"); } else { foreach (var error in result.Errors) { Console.WriteLine($"- {error.PropertyName}: {error.ErrorMessage}"); } } }

image.png

这个例子展示了如何验证客户对象,包括其嵌套的地址对象。

与ASP.NET Core集成

image.png

FluentValidation可以很容易地集成到ASP.NET Core应用中:

C#
builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddValidatorsFromAssemblyContaining<Program>();
C#
public class UserRegistrationRequest { public string Username { get; set; } public string Email { get; set; } public string Password { get; set; } public DateTime DateOfBirth { get; set; } }
C#
public class UserRegistrationValidator : AbstractValidator<UserRegistrationRequest> { public UserRegistrationValidator() { RuleFor(x => x.Username) .NotEmpty().WithMessage("用户名是必填的。") .Length(3, 20).WithMessage("用户名必须在3到20个字符之间。"); RuleFor(x => x.Email) .NotEmpty().WithMessage("邮箱是必填的。") .EmailAddress().WithMessage("请输入有效的邮箱地址。"); RuleFor(x => x.Password) .NotEmpty().WithMessage("密码是必填的。") .MinimumLength(6).WithMessage("密码至少需要6个字符。") .Matches(@"[A-Z]+").WithMessage("密码必须包含至少一个大写字母。") .Matches(@"[a-z]+").WithMessage("密码必须包含至少一个小写字母。") .Matches(@"[0-9]+").WithMessage("密码必须包含至少一个数字。"); RuleFor(x => x.DateOfBirth) .NotEmpty().WithMessage("出生日期是必填的。") .LessThan(DateTime.Now.AddYears(-18)).WithMessage("您必须年满18岁才能注册。"); } }
C#
[Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { [HttpPost("register")] public IActionResult Register(UserRegistrationRequest request) { // 如果验证失败,ASP.NET Core 会自动返回 400 Bad Request // 带有验证错误详情 // 在这里处理有效的注册请求 return Ok(new { Message = "用户注册成功" }); } }

有效请求:

JSON
{ "username": "johndoe", "email": "john@example.com", "password": "Password123", "dateOfBirth": "1990-01-01" }

无效请求:

JSON
{ "username": "j", "email": "not-an-email", "password": "weak", "dateOfBirth": "2010-01-01" }

image.png

对于有效请求,您应该收到一个成功的响应。对于无效请求,您应该收到一个 400 Bad Request 响应,其中包含详细的验证错误信息。

最佳实践

  1. 保持验证器的单一职责: 每个验证器应该只负责一个模型的验证。
  2. 重用验证逻辑: 使用自定义方法或扩展方法来重用常见的验证逻辑。
  3. 适当使用条件验证: 不要过度使用条件验证,这可能会使规则变得难以理解和维护。
  4. 提供有意义的错误消息: 错误消息应该清晰地指出问题所在,并可能提供如何修正的建议。
  5. 考虑性能: 对于大型集合或复杂对象,考虑使用异步验证或优化验证逻辑。
  6. 定期审查和更新验证规则: 随着业务规则的变化,确保验证规则保持最新。

结论

FluentValidation是一个强大而灵活的验证库,能够极大地简化.NET应用程序中的数据验证过程。通过本文介绍的各种技术,你应该能够处理大多数验证场景。记住,好的验证不仅能确保数据的完整性和正确性,还能提供良好的用户体验。持续练习和应用这些概念,将帮助你构建更健壮、更可靠的应用程序。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!