使用FluentValidation的WithMessage方法和命名参数列表

Jas*_*son 17 .net c# linq func fluentvalidation

我正在使用FluentValidation,我想格式化一个对象的属性值的消息.问题是我对C#中的表达式和委托的经验很少.

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

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

我想做这样的事情,以避免在我更改参数的顺序时更改消息字符串.

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

原始方法签名如下所示:

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

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

有人可以帮我这个吗?

Eri*_*ikE 17

使用C#6.0,这大大简化了.现在你可以做到这一点(有点像黑客,但比分支Fluent验证要好得多):

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

遗憾的是他们没有提供一个WithMessage需要lambda接受对象的重载,你可以这样做:

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

我认为他们试图string.Format在目标中复制自己以实现更短的语法是愚蠢的,但最终使其不那么灵活,因此我们不能干净地使用新的C#6.0语法.

  • 请注意,当前支持此建议(从 v8.0.100 开始) (5认同)

Kha*_*meh 11

您不能使用FluentValidation中的WithMessage执行此操作,但您可以高举JackState属性并在那里注入您的消息.这是一个有效的例子; 您的另一个选择是fork 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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ily*_*nov 9

虽然KhalidAbuhakmeh的答案非常好而且很深,但我只想分享一个解决这个问题的简单方法.如果你害怕位置参数,为什么不用连接运算符封装错误创建机制+并利用WithMessage重载Func<T, object>.这个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Console.WriteLine(result.Errors.First().ErrorMessage);
Run Code Online (Sandbox Code Playgroud)

或者,使用内联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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*ard 9

对于现在正在研究此问题的任何人 - 当前的 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}.");
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助某人。