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

如何在FluentValidation中实现WithMessageAsync异步消息翻译

实现FluentValidation的异步错误消息链式调用方案

要实现WithMessageAsync并复用内置验证规则,核心思路是通过包装内置验证器,重写其异步消息获取逻辑,同时保留链式调用的体验。以下是具体实现步骤:

1. 创建异步消息包装验证器

这个类负责包裹FluentValidation的内置验证器,替换其错误消息的获取逻辑为异步方式:

public class AsyncMessagePropertyValidator<T, TProperty> : PropertyValidator<T, TProperty>
{
    private readonly PropertyValidator<T, TProperty> _innerValidator;
    private readonly Func<ValidationContext<T>, Task<string>> _messageProvider;

    public AsyncMessagePropertyValidator(PropertyValidator<T, TProperty> innerValidator, Func<ValidationContext<T>, Task<string>> messageProvider)
    {
        _innerValidator = innerValidator;
        _messageProvider = messageProvider;
        RuleSets = innerValidator.RuleSets;
    }

    public override async Task<bool> IsValidAsync(ValidationContext<T> context, TProperty value, CancellationToken cancellationToken)
    {
        return await _innerValidator.IsValidAsync(context, value, cancellationToken);
    }

    protected override async Task<string> GetErrorMessageAsync(ValidationContext<T> context, TProperty value)
    {
        return await _messageProvider(context);
    }

    public override bool IsValid(ValidationContext<T> context, TProperty value)
    {
        return _innerValidator.IsValid(context, value);
    }

    protected override string GetErrorMessage(ValidationContext<T> context, TProperty value)
    {
        throw new InvalidOperationException("请使用异步验证方法调用此规则");
    }
}

2. 编写扩展方法实现链式调用

通过扩展IRuleBuilderOptions,添加WithMessageAsync方法,将内置验证器替换为我们的包装器:

public static class FluentValidationAsyncExtensions
{
    public static IRuleBuilderOptions<T, TProperty> WithMessageAsync<T, TProperty>(
        this IRuleBuilderOptions<T, TProperty> rule,
        Func<ValidationContext<T>, Task<string>> messageProvider)
    {
        var innerValidator = rule.CurrentValidator as PropertyValidator<T, TProperty>;
        if (innerValidator == null)
        {
            throw new InvalidOperationException("WithMessageAsync仅适用于属性验证器");
        }

        rule.ClearValidators();
        rule.SetValidator(new AsyncMessagePropertyValidator<T, TProperty>(innerValidator, messageProvider));

        return rule;
    }

    // 重载:结合翻译服务的快捷方式
    public static IRuleBuilderOptions<T, TProperty> WithMessageAsync<T, TProperty>(
        this IRuleBuilderOptions<T, TProperty> rule,
        ITranslationService translationService,
        string translationKey,
        params object[] formatArgs)
    {
        return rule.WithMessageAsync(async context =>
        {
            var translatedText = await translationService.GetTranslationAsync(translationKey, context.CultureInfo);
            return string.Format(translatedText, formatArgs);
        });
    }
}

3. 实际使用示例

假设你有一个从数据库获取译文的ITranslationService,注入到验证器后即可像原生WithMessage一样链式调用:

public class UserValidator : AbstractValidator<User>
{
    private readonly ITranslationService _translationService;

    public UserValidator(ITranslationService translationService)
    {
        _translationService = translationService;

        // 基础用法:复用NotEmpty规则,异步获取翻译消息
        RuleFor(x => x.FirstName)
            .NotEmpty()
            .WithMessageAsync(_translationService, "FirstName.Empty");

        // 自定义异步逻辑:针对邮箱格式验证设置异步消息
        RuleFor(x => x.Email)
            .NotEmpty()
            .WithMessageAsync(_translationService, "Email.Empty")
            .EmailAddress()
            .WithMessageAsync(async ctx => 
                await _translationService.GetTranslationAsync("Email.InvalidFormat", ctx.CultureInfo));
    }
}

关键注意点

  • 必须使用异步验证方法:在Controller或服务中调用await _validator.ValidateAsync(userModel),同步验证会抛出异常(可根据需求修改包装器的同步方法逻辑)。
  • 多规则独立设置:如果一个字段有多个验证规则(如邮箱的NotEmpty和EmailAddress),需为每个规则单独调用WithMessageAsync。
  • 上下文支持:ValidationContext包含当前模型、文化信息等,可用于多语言翻译场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:58:07