2013-01-05 9 views
9

我正在使用FluentValidation,我想用一些對象的屬性值來格式化消息。問題是我對C#中的表達式和委託很少有經驗。使用FluentValidation的WithMessage方法和一個已命名參數列表

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) 

我想提供這種方法與函數功能的列表。

任何人都可以幫助我嗎?

+0

好像問題更多的是用繩子比什麼都格式化。這可能會幫助你:http://stackoverflow.com/questions/159017/named-string-formatting-in-c-sharp –

+0

我認爲它有點不同,因爲提供給FluentValidation的表達式不會立即執行。我認爲這就是爲什麼現有的方法需要一個委託。 – Jason

回答

5

您不能使用FluentValidation中的WithMessage來做到這一點,但您可以高度調整CustomState屬性並在其中注入您的消息。這是一個工作的例子;您的其他選項是分叉FluentValidation併爲WithMethod額外重載。

這與的NuGet和JamesFormater從這個博客帖子FluentValidation引用一個控制檯應用程序:

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); 
      } 
     } 
    } 
} 
+0

謝謝你的例子。我會更喜歡fork代碼或添加一個擴展方法,以便能夠按照我想要的方式使用該方法,但我無法確定如何處理新的Expression(x => new {Name = ...})。任何提示或想法? – Jason

+0

我會在示例中使用上面指定的格式化程序。說實話,我喜歡下面的Ilya的代碼。也許你可以從AbstractValidator繼承並創建一個WitNamedMessage(T target)方法。這樣你可以調整它,你不必等待傑里米斯金納做一個新的推動。 –

+0

這似乎很有希望。你認爲我能夠檢索具有不同名稱的子對象和屬性嗎?例如,如果我想用像objectUnderValidator.ChildArray [0] .Name這樣的東西替換{Test}佔位符,該怎麼辦? – Jason

6

雖然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; 
    } 
} 

打印正確的原始的錯誤消息:

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

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

另外,使用內聯的λ:

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); 
    } 
} 
8

用C#6.0中,這大大簡化了。現在,你可以做到這一點(一個黑客的一點點,但比分叉流利驗證要好很多):

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

可惜他們並沒有提供一個WithMessage超載,需要一個拉姆達接受對象,你可能只是這樣做:

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

我認爲這是愚蠢的,他們試圖複製string.Format自己的目標,以實現更短的語法,但最終使它不太靈活,使我們無法清晰地用新的C#6.0的語法。

+0

同意,我試圖讓它像你顯示的那樣工作(我將如何預期它與6.0)。 +1 – schmoopy

1

基於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}."); 
相關問題