You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于父级数据的FluentValidation实现:验证服务配置速度限制

问题描述

背景

假设存在包含以下产品的数据库:

Id名称最大速度
40Lite100
41Basic500
42Premium1000

我们基于这些产品销售服务,服务可配置特定速度,且该速度不得超过关联产品的最大速度(例如订阅Basic服务时,速度配置值需在0-500之间)。

相关代码定义

C#实体

public record Product(int Id, string Name, int MaxSpeed);
public record Service(string Name, int ProductId, ServiceConfiguration Configuration);
public record ServiceConfiguration(int Speed, int Other, bool Options, float Whatever);

仓储接口

public interface IProductRepository
{
    Product GetProductById(int id);
}

现有验证器实现

public class ServiceValidator : AbstractValidator<Service>
{
    public ServiceValidator(IProductRepository productRepository)
    {
        RuleFor(s => s.Name).MaximumLength(50);
        RuleFor(s => s.Configuration)
            .SetValidator(c => new ServiceConfigurationValidator(productRepository));
    }
}

当前ServiceValidator的构造函数接收IProductRepository但仅用于传递给ServiceConfigurationValidator,设计不够合理,需要优化。

验证需求

需要验证如下服务(JSON示例):

{ 
  "Id": 42,
  "Name": "My subscription Foo Bar",
  "Configuration": {
    "Speed": 10,
    "Other": "Foo",
    "Options": "Bar"
  }
}

需编写/改写ServiceConfigurationValidator,实现两个目标:

  1. 访问父级Service对象的ProductId;
  2. 使用该ProductId通过IProductRepository获取对应产品,验证Configuration.Speed不超过产品的MaxSpeed。

解决方案

优化方案一:直接在ServiceValidator中实现完整验证

不需要单独为ServiceConfiguration创建独立验证器,直接在ServiceValidator中编写规则,既可以直接访问父级Service的ProductId,又避免了仓储依赖的冗余传递:

public class ServiceValidator : AbstractValidator<Service>
{
    public ServiceValidator(IProductRepository productRepository)
    {
        // 验证服务名称长度
        RuleFor(s => s.Name).MaximumLength(50);

        // 验证ProductId对应的产品存在
        RuleFor(s => s.ProductId)
            .Must(id => productRepository.GetProductById(id) != null)
            .WithMessage("无效的ProductId:未找到对应产品");

        // 验证配置速度不超过产品最大速度
        RuleFor(s => s.Configuration.Speed)
            .Must((service, speed) => 
            {
                var product = productRepository.GetProductById(service.ProductId);
                return speed >= 0 && speed <= product.MaxSpeed;
            })
            .WithMessage((service, speed) => 
                $"配置速度{speed}超过了产品ID{service.ProductId}的最大允许速度{productRepository.GetProductById(service.ProductId).MaxSpeed}");

        // 可在此添加ServiceConfiguration其他属性的验证规则
        RuleFor(s => s.Configuration.Other).NotEmpty();
        RuleFor(s => s.Configuration.Options).NotNull();
    }
}

优化方案二:保留独立ServiceConfigurationValidator

如果ServiceConfiguration的验证逻辑需要复用,可通过构造函数传入父级的ProductId,明确依赖关系:

// 父级验证器
public class ServiceValidator : AbstractValidator<Service>
{
    public ServiceValidator(IProductRepository productRepository)
    {
        RuleFor(s => s.Name).MaximumLength(50);

        // 验证ProductId有效性
        RuleFor(s => s.ProductId)
            .Must(id => productRepository.GetProductById(id) != null)
            .WithMessage("无效的ProductId:未找到对应产品");

        // 传递ProductId给子验证器
        RuleFor(s => s.Configuration)
            .SetValidator(service => new ServiceConfigurationValidator(productRepository, service.ProductId));
    }
}

// 子验证器
public class ServiceConfigurationValidator : AbstractValidator<ServiceConfiguration>
{
    private readonly IProductRepository _productRepository;
    private readonly int _productId;

    public ServiceConfigurationValidator(IProductRepository productRepository, int productId)
    {
        _productRepository = productRepository;
        _productId = productId;

        // 验证速度规则
        RuleFor(c => c.Speed)
            .Must(speed => 
            {
                var product = _productRepository.GetProductById(_productId);
                return speed >= 0 && speed <= product.MaxSpeed;
            })
            .WithMessage((config, speed) => 
                $"配置速度{speed}超过了产品ID{_productId}的最大允许速度{_productRepository.GetProductById(_productId).MaxSpeed}");

        // 其他配置属性验证
        RuleFor(c => c.Other).NotEmpty();
        RuleFor(c => c.Options).NotNull();
    }
}

方案说明

  • 方案一逻辑更简洁,避免了子验证器的依赖传递问题,适合不需要复用配置验证逻辑的场景。
  • 方案二适合ServiceConfiguration验证逻辑需要在多个地方复用的场景,通过构造函数传入ProductId,明确了验证所需的依赖条件。
  • 两种方案都添加了ProductId有效性验证,避免因无效ID导致的空引用异常。

内容的提问来源于stack exchange,提问作者RobIII

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 14:40:23