2013-01-05 29 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) 

私はのFuncのリストで、この方法を提供することを考えていました。

誰でも私にこれを手伝うことができますか?

+0

問題が何よりも文字列フォーマットで行うことがより多くを持っているように思えます。これはあなたを助けるかもしれません:http://stackoverflow.com/questions/159017/named-string-formatting-in-c-sharp –

+0

FluentValidationに提供された式がすぐに実行されないので、少し違うと思います。私はそれが既存のメソッドがデリゲートを望んでいる理由だと思う。 – Jason

答えて

5

FluentValidationでWithMessageを使用することはできませんが、CustomStateプロパティをハイジャックしてそこにメッセージを注入することはできます。ここに実例があります。あなたのもう一つの選択肢は、FluentValidationをフォークし、WithMethodの追加オーバーロードを行うことです。

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

ベストの答え:

これは、このブログの記事からNugetとJamesFormaterからFluentValidationへの参照を持つコンソールアプリケーションです。 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

ありがとうございます。私はコードをフォークするか、必要な方法でメソッドを使用できるように拡張メソッドを追加する必要がありますが、新しい式(x => new {Name = ...})を処理する方法を理解することはできません。任意のヒントやアイデア? – Jason

+0

上記の例では、名前付きフォーマッタを使用します。正直言って、私は以下のIlyaのコードが好きです。たぶんあなたはAbstractValidatorから継承し、WitNamedMessage(Tターゲット)メソッドを作成することができます。あなたはそれを微調整することができますし、ジェレミースキナーが新しいプッシュをするのを待つ必要はありません。 –

+0

これは非常にお約束しているようです。子オブジェクトとプロパティを別の名前で取得できると思いますか?たとえば、{Test}プレースホルダをobjectUnderValidator.ChildArray [0] .Nameのようなものに置き換えたいとしたらどうでしょうか? – 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には、これが大幅に簡略化されます。彼らは、オブジェクトを受け入れるラムダをとるWithMessageオーバーロードを提供し、あなたはなかった

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

同情:今、あなたはちょうどこの(少しハックのビットが、流暢検証をフォークよりもずっと良い)を行うことができます単に行うことができます:

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}."); 
関連する問題