FluentValidation是一个流行的.NET验证库,它提供了一种优雅的方式来定义强类型的验证规则。它的流畅接口使得创建复杂的验证逻辑变得简单直观。本文将深入探讨FluentValidation的各种特性和用法,并提供丰富的示例来说明如何在实际项目中应用这些概念。
首先,让我们通过NuGet包管理器安装FluentValidation:
Markdowndotnet add package FluentValidation
对于ASP.NET Core项目,你可能还想安装集成包:
Markdowndotnet 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);
}
}
这个验证器可以确保:
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个字符");
}
}
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);
}
}
}
这个例子展示了多种常用的验证规则,包括非空检查、长度限制、数值范围检查、枚举验证等。
有时,内置的验证规则可能无法满足特定需求。在这种情况下,我们可以创建自定义验证规则:
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}$");
}
}
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);
}
}
}
在这个例子中,我们创建了两个自定义验证规则:一个检查日期是否为工作日,另一个验证客户代码的格式。
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之间");
}
}
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}");
}
}
}
这个例子展示了如何验证客户对象,包括其嵌套的地址对象。
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"
}
对于有效请求,您应该收到一个成功的响应。对于无效请求,您应该收到一个 400 Bad Request 响应,其中包含详细的验证错误信息。
FluentValidation是一个强大而灵活的验证库,能够极大地简化.NET应用程序中的数据验证过程。通过本文介绍的各种技术,你应该能够处理大多数验证场景。记住,好的验证不仅能确保数据的完整性和正确性,还能提供良好的用户体验。持续练习和应用这些概念,将帮助你构建更健壮、更可靠的应用程序。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!