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

我正在使用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 WithMessage( this IRuleBuilderOptions rule, string errorMessage, params Func[] funcs) 

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

有人可以帮我这个吗?

您不能使用FluentValidation中的WithMessage执行此操作,但您可以高举JackState属性并在那里注入您的消息。 这是一个有效的例子; 您的另一个选择是分叉FluentValidation并为WithMethod进行额外的重载。

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

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

最好的答案。 从Ilya那里获得灵感,并意识到你可以捎带流畅validation的扩展方法本质。 所以下面的工作无需修改库中的任何内容。

 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 { public MyValidator() { RuleFor(x => x.Name).NotEmpty().WithNamedMessage("The name {Name} is not valid for Id {Id}"); } } public static class NamedMessageExtensions { public static IRuleBuilderOptions WithNamedMessage( this IRuleBuilderOptions 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 values = new List(); string rewrittenFormat = Regex.Replace(format, @"(?\{)+(?[\w\.\[\]]+)(?:[^}]+)?(?\})+", 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); } } } } 

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

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

可惜他们没有提供一个WithMessage重载,它接受一个lambda接受该对象,你可以这样做:

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

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

虽然KhalidAbuhakmeh的答案非常好而且很深,但我只想分享一个解决这个问题的简单方法。 如果你害怕位置参数,为什么不用连接运算符+封装错误创建机制并利用WithMessage重载,这需要Func 。 这个CustomerValudator

 public class CustomerValidator : AbstractValidator { 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; } } 

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

 var customer = new Customer() {Id = 1, Name = ""}; var result = new CustomerValidator().Validate(customer); Console.WriteLine(result.Errors.First().ErrorMessage); 

或者,使用内联lambda:

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

基于ErikE 答案的扩展方法。

 public static class RuleBuilderOptionsExtensions { public static IRuleBuilderOptions WithMessage(this IRuleBuilderOptions rule, Func func) => DefaultValidatorOptions.WithMessage(rule, "{0}", func); public static IRuleBuilderOptions WithMessage(this IRuleBuilderOptions rule, Func 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}.");