【问题标题】:Using FluentValidation's WithMessage method with a list of named parameters使用带有命名参数列表的 FluentValidation 的 WithMessage 方法
【发布时间】:2012-12-19 11:44:04
【问题描述】:

我正在使用 FluentValidation,我想用一些对象的属性值来格式化消息。问题是我对 C# 中的表达式和委托几乎没有经验。

FluentValidation 已经提供了一种使用格式参数执行此操作的方法。

RuleFor(x => x.Name).NotEmpty()
    .WithMessage("The name {1} is not valid for Id {0}", x => x.Id, x => x.Name);

如果我更改参数的顺序,我想做这样的事情以避免必须更改消息字符串。

RuleFor(x => x.Name).NotEmpty()
    .WithMessage("The name {Name} is not valid for Id {Id}", 
    x => new
        {
            Id = x.Id,
            Name = x.Name
        });

原来的方法签名是这样的:

public static IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(
    this IRuleBuilderOptions<T, TProperty> rule, string errorMessage, 
    params Func<T, object>[] funcs)

我正在考虑为这个方法提供一个 Func 列表。

有人可以帮我吗?

【问题讨论】:

  • 似乎这个问题更多地与字符串格式有关。这可能会对您有所帮助:stackoverflow.com/questions/159017/…
  • 我认为这有点不同,因为提供给 FluentValidation 的表达式不会立即执行。我认为这就是现有方法需要委托的原因。

标签: c# .net linq fluentvalidation func


【解决方案1】:

如果您使用的是 C# 6.0 或更高版本,这里有一个改进的语法。

使用 8.0.100 或更高版本的 Fluent Validation,有一个 WithMessage 重载,它接受一个接受对象的 lambda,您可以这样做:

RuleFor(x => x.Name)
   .NotEmpty()
   .WithMessage(x => $"The name {x.Name} is not valid for Id {x.Id}.");

但是,对于 Fluent Validation 的早期版本,这种有点老套的方法仍然非常干净,并且比 fork 旧版本要好得多:

RuleFor(x => x.Name)
   .NotEmpty()
   .WithMessage("{0}", x => $"The name {x.Name} is not valid for Id {x.Id}.");

【讨论】:

  • 同意,我正试图让它像你展示的那样工作(我希望它与 6.0 一起使用)。 +1
  • 请注意,目前支持此建议(从 v8.0.100 开始)
【解决方案2】:

您无法使用 FluentValidation 中的 WithMessage 执行此操作,但您可以劫持 CustomState 属性并在那里注入您的消息。这是一个工作示例;您的另一个选择是分叉 FluentValidation 并为 WithMethod 进行额外的重载。

这是一个控制台应用程序,引用了来自 Nuget 的 FluentValidation 和来自这篇博文的 JamesFormater:

http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

最佳答案。从 Ilya 中获得灵感,并意识到您可以捎带流利验证的扩展方法性质。因此,以下内容无需修改库中的任何内容即可工作。

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using FluentValidation;

namespace stackoverflow.fv
{
    class Program
    {
        static void Main(string[] args)
        {
            var target = new My() { Id = "1", Name = "" };
            var validator = new MyValidator();
            var result = validator.Validate(target);

            foreach (var error in result.Errors)
                Console.WriteLine(error.ErrorMessage);

            Console.ReadLine();
        }
    }

    public class MyValidator : AbstractValidator<My>
    {
        public MyValidator()
        {
            RuleFor(x => x.Name).NotEmpty().WithNamedMessage("The name {Name} is not valid for Id {Id}");
        }
    }

    public static class NamedMessageExtensions
    {
        public static IRuleBuilderOptions<T, TProperty> WithNamedMessage<T, TProperty>(
            this IRuleBuilderOptions<T, TProperty> rule, string format)
        {
            return rule.WithMessage("{0}", x => format.JamesFormat(x));
        }
    }

    public class My
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }

    public static class JamesFormatter
    {
        public static string JamesFormat(this string format, object source)
        {
            return FormatWith(format, null, source);
        }

        public static string FormatWith(this string format
            , IFormatProvider provider, object source)
        {
            if (format == null)
                throw new ArgumentNullException("format");

            List<object> values = new List<object>();
            string rewrittenFormat = Regex.Replace(format,
              @"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
              delegate(Match m)
              {
                  Group startGroup = m.Groups["start"];
                  Group propertyGroup = m.Groups["property"];
                  Group formatGroup = m.Groups["format"];
                  Group endGroup = m.Groups["end"];

                  values.Add((propertyGroup.Value == "0")
                    ? source
                    : Eval(source, propertyGroup.Value));

                  int openings = startGroup.Captures.Count;
                  int closings = endGroup.Captures.Count;

                  return openings > closings || openings % 2 == 0
                     ? m.Value
                     : new string('{', openings) + (values.Count - 1)
                       + formatGroup.Value
                       + new string('}', closings);
              },
              RegexOptions.Compiled
              | RegexOptions.CultureInvariant
              | RegexOptions.IgnoreCase);

            return string.Format(provider, rewrittenFormat, values.ToArray());
        }

        private static object Eval(object source, string expression)
        {
            try
            {
                return DataBinder.Eval(source, expression);
            }
            catch (HttpException e)
            {
                throw new FormatException(null, e);
            }
        }
    }
}

【讨论】:

  • 谢谢你的例子。我必须更喜欢 fork 代码或添加扩展方法才能以我想要的方式使用该方法,但我不知道如何处理新的 Expression (x => new { Name = ...})。有什么建议或想法吗?
  • 我将使用上面示例中的命名格式化程序。不过老实说,我喜欢 Ilya 下面的代码。也许您可以从 AbstractValidator 继承并创建一个 WitNamedMessage(T target) 方法。这样您就可以对其进行调整,而不必等待 Jeremy Skinner 进行新的推动。
  • 这看起来很有希望。您认为我可以检索具有不同名称的子对象和属性吗?例如,如果我想用 objectUnderValidator.ChildArray[0].Name 之类的东西替换 {Test} 占位符怎么办?
  • 我不确定你能做到这一点。我知道在被黑客攻击的帖子中,一些命名的格式化程序可以让你做像 {Order.Name} 这样的事情作为格式参数,但你必须看看它。您也许还可以将其解析出来并将其编译成一个动作并获得您想要的行为。但我不会对此感到太疯狂,因为维护它可能会让人头疼。
【解决方案3】:

虽然 KhalidAbuhakmeh 的回答非常好和深刻,但我只想分享一个简单的解决方案来解决这个问题。如果您害怕位置参数,为什么不使用连接运算符+ 封装错误创建机制并利用WithMessage 重载,这需要Func&lt;T, object&gt;。这个CustomerValudator

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(customer => customer.Name).NotEmpty().WithMessage("{0}", CreateErrorMessage);
    }

    private string CreateErrorMessage(Customer c)
    {
        return "The name " + c.Name + " is not valid for Id " + c.Id;
    }
}

在下一个代码 sn-p 中打印正确的原始错误消息:

var customer = new Customer() {Id = 1, Name = ""};
var result = new CustomerValidator().Validate(customer);

Console.WriteLine(result.Errors.First().ErrorMessage);

或者,使用内联 lambda:

public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor(customer => customer.Name)
            .NotEmpty()
            .WithMessage("{0}", c => "The name " + c.Name + " is not valid for Id " + c.Id);
    }
}

【讨论】:

    【解决方案4】:

    对于现在正在研究此问题的任何人 - 当前的 FluentValidation (v8.0.100) 允许您在 WithMessage 中使用 lamda(正如 ErikE 上面建议的那样),因此您可以使用:

    RuleFor(x => x.Name).NotEmpty()
       .WithMessage(x => $"The name {x.Name} is not valid for Id {x.Id}.");
    

    希望这对某人有所帮助。

    【讨论】:

      【解决方案5】:

      基于 ErikE 的 answer 的扩展方法。

      public static class RuleBuilderOptionsExtensions
      {
          public static IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<T, object> func)
              => DefaultValidatorOptions.WithMessage(rule, "{0}", func);
          public static IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<T, TProperty, object> func)
              => DefaultValidatorOptions.WithMessage(rule, "{0}", func);
      }
      

      用法示例:

      RuleFor(_ => _.Name).NotEmpty()
      .WithMessage(_ => $"The name {_.Name} is not valid for Id {_.Id}.");
      
      RuleFor(_ => _.Value).GreaterThan(0)
      .WithMessage((_, p) => $"The value {p} is not valid for Id {_.Id}.");
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-12
        • 2013-04-07
        相关资源
        最近更新 更多