深入了解 .NET 8 应用程序的 FluentValidation验证功能

作者:微信公众号:【架构师老卢】
6-5 16:15
69

概述:本文将深入探讨如何使用 FluentValidation 在 .NET 应用程序中实现验证。传统上,.NET 应用程序过去常常使用数据批注进行验证,但这种方法曾经存在一些限制,例如模型或 dto 类看起来臃肿、不可扩展、无法控制消息和行为以及测试不灵活。这就是像 FluentValidation 这样的库帮助解决批次问题的地方。什么是 FluentValidation?FluentValidation 是一个库,它提供了一个流畅的接口,用于定义 .NET 中对象的验证规则。它允许您使用清晰且富有表现力的语法来定义复杂的验证逻辑。目的以声明性和可读性的方式定义类属性的验证规则。集中类的验证逻辑,

本文将深入探讨如何使用 FluentValidation 在 .NET 应用程序中实现验证。传统上,.NET 应用程序过去常常使用数据批注进行验证,但这种方法曾经存在一些限制,例如模型或 dto 类看起来臃肿、不可扩展、无法控制消息和行为以及测试不灵活。这就是像 FluentValidation 这样的库帮助解决批次问题的地方。

什么是 FluentValidation?

FluentValidation 是一个库,它提供了一个流畅的接口,用于定义 .NET 中对象的验证规则。它允许您使用清晰且富有表现力的语法来定义复杂的验证逻辑。

目的

  • 以声明性和可读性的方式定义类属性的验证规则。
  • 集中类的验证逻辑,改进代码组织和可重用性。
  • 支持复杂的验证方案,例如条件验证和跨属性验证。

设计模式

FluentValidation 遵循 Fluent Interface 设计模式,提供流畅且富有表现力的 API 来定义验证规则。它提倡一种清晰易读的语法来定义复杂的验证逻辑。

何时使用

  • 验证类的属性,例如 DTO(数据传输对象)或域模型。
  • 实施涉及多个属性或条件的复杂验证规则。
  • 将验证逻辑集中并封装在专用的验证器类中。

现在,让我们设置一个示例 API,并演示如何在 .NET 中使用 FluentValidation。在本例中,让我们假设我们有一个 Order API,它将创建客户并创建订单,在这种情况下,让我们使用 FluentValidation 构建一些验证器。在此之前,我们需要安装几个 NuGet 包,如下所示。

在业务逻辑项目中,所有验证器和逻辑都保留添加以下包:

dotnet add package FluentValidation

在 API 项目中添加以下包:

dotnet add package FluentValidation.AspNetCore

现在,让我们构建一些示例验证器来验证客户、地址和订单。

如何使用内置验证器

FluentValidation 有几个内置的验证器,这让我们无需编写代码来执行最常见的验证。如下图所示,我们甚至可以像 NotEmpty 和 MaximumLength 这样的链式验证。

using BusinessLogic.Models;  
using FluentValidation;  
  
namespace BusinessLogic.Validators  
{  
    public class OrderRequestValidator : AbstractValidator<OrderRequest>  
    {  
        public OrderRequestValidator()  
        {  
            RuleFor(or=>or.ProductName).NotEmpty().MaximumLength(255).WithMessage("Product Name Required for placing order");  
  
            RuleFor(or => or.Quantity).GreaterThan(0).LessThan(50);  
  
            RuleFor(or => or.PromoCode).NotEmpty().MaximumLength(10);  
        }  
    }  
}

嵌套验证器

我们可以将验证器嵌套在另一个验证器中,如下图所示,我们在客户验证器中调用订单和地址验证器。

using BusinessLogic.Models;  
using FluentValidation;  
  
namespace BusinessLogic.Validators  
{  
    public class CreateCustomerRequestValidator : AbstractValidator<CreateCustomerRequest>  
    {  
        public CreateCustomerRequestValidator()  
        {  
            RuleFor(cus => cus.FirstName).NotEmpty().MaximumLength(100);  
  
            RuleFor(cus => cus.LastName).NotEmpty().MaximumLength(100);  
  
            RuleFor(cus => cus.Email).NotEmpty().EmailAddress().WithMessage("Email not in expected format");  
  
            RuleFor(cus => cus.Age).GreaterThan(20);  
  
            RuleFor(cus => cus.PhysicalAddress).NotNull().SetValidator(new AddressRequestValidator());  
  
            RuleFor(cus => cus.MailingAddress).NotNull().SetValidator(new AddressRequestValidator());  
  
            RuleFor(cus => cus.Order).NotNull().SetValidator(new OrderRequestValidator());  
        }  
    }  
}

自定义验证器

假设我们想要构建可扩展的自定义验证器,在我们的例子中,验证邮政编码是所有数字。没有像验证电子邮件等开箱即用的验证器,因此我们必须实现自己的自定义验证器扩展 IRuleBuilder,如下所示。

using FluentValidation;

namespace BusinessLogic.Validators
{
    public static class FluentValidationExtensions
    {
        public static IRuleBuilderOptions<T, string> ZipCode<T>(this IRuleBuilder<T, string> ruleBuilder)
        {
            //"^[0-9]{5}(?:-[0-9]{4})?$"
            return ruleBuilder
                       .NotEmpty()
                       .Matches(@"\d{5}$");
        }
    }
}

连接一切

为了在我们的 Web API 项目中使用所有这些验证器,我们需要注册它们并使用它们,如下所示。

using BusinessLogic.Validators;
using FluentValidation;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Add each and every validator one after another
//builder.Services.AddScoped<IValidator<CreateCustomerRequestValidator>, CreateCustomerRequestValidator>();
//Register all the validators in one go
builder.Services.AddValidatorsFromAssemblyContaining<CreateCustomerRequestValidator>();


builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();
using BusinessLogic.Models;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;

namespace OrderApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        private IValidator<CreateCustomerRequest> _validator;


        public OrderController(IValidator<CreateCustomerRequest> validator)
        {
            _validator = validator;
        }

        [HttpPost]
        public async Task<ActionResult> PostCustomer([FromBody] CreateCustomerRequest createCustomerRequest)
        {
            ValidationResult result = await _validator.ValidateAsync(createCustomerRequest);

            if (!result.IsValid)
            {
                return BadRequest(result);
            }

            //rest of the logic
            return Created();
        }
    }
}

完成所有操作后,我们可以使用swagger测试验证器,我们可以在下面看到我们如何获得验证错误。

自定义验证器输出

单元测试

我们可以轻松地为验证者编写单元测试,如下所示:

using BusinessLogic.Validators;
using FluentValidation.TestHelper;

namespace BusinessLogic.Tests
{
    [TestFixture]
    public class AddressRequestValidatorTests
    {
        private readonly AddressRequestValidator _validator = new AddressRequestValidator();

        [Test]
        public void GivenValidLine1_Should_Not_Have_Error()
        {
            var result = _validator.TestValidate(new Models.AddressRequest() 
            {
                Line1 = "123 Main Street"
            });

            result.ShouldNotHaveValidationErrorFor(add => add.Line1);
        }

        [Test]
        public void GivenInvalidZipCode_Should_Have_Error()
        {
            var result = _validator.TestValidate(new Models.AddressRequest()
            {
                PostalCode = "ABCDE"
            });

            result.ShouldHaveValidationErrorFor(add => add.PostalCode);
        }

        [Test]
        public void GivenvalidZipCode_Should_Not_Have_Error()
        {
            var result = _validator.TestValidate(new Models.AddressRequest()
            {
                PostalCode = "78613"
            });

            result.ShouldNotHaveValidationErrorFor(add => add.PostalCode);
        }
    }
}

源代码获取:公众号回复消息【code:23084

相关代码下载地址
重要提示!:取消关注公众号后将无法再启用回复功能,不支持解封!
第一步:微信扫码关键公众号“架构师老卢”
第二步:在公众号聊天框发送code:23084,如:code:23084 获取下载地址
第三步:恭喜你,快去下载你想要的资源吧
阅读排行