web-dev-qa-db-ja.com

データ注釈を使用した依存プロパティのカスタムモデル検証

それ以来、優れた FluentValidation ライブラリを使用して、モデルクラスを検証しています。 Webアプリケーションでは、 jquery.validate プラグインと組み合わせて使用​​し、クライアント側の検証も実行します。欠点の1つは、検証ロジックの多くがクライアント側で繰り返され、1か所で集中化されなくなることです。

このため、私は代替手段を探しています。 多く の例があります あり モデル検証を実行するためのデータ注釈の使用法を示しています。とても有望に見えます。私が見つけられなかった1つのことは、別のプロパティ値に依存するプロパティを検証する方法です。

たとえば、次のモデルを見てみましょう。

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }
    [Required]
    public DateTime? EndDate { get; set; }
}

EndDateStartDateよりも大きいことを確認したいと思います。カスタム検証ロジックを実行するために、 ValidationAttribute を拡張するカスタム検証属性を作成できます。残念ながら、モデルインスタンスを取得する方法が見つかりませんでした。

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // value represents the property value on which this attribute is applied
        // but how to obtain the object instance to which this property belongs?
        return true;
    }
}

CustomValidationAttribute は、検証されているオブジェクトインスタンスを含むこのValidationContextプロパティを持っているため、仕事をしているようです。残念ながら、この属性は.NET 4.0でのみ追加されています。だから私の質問は:.NET 3.5 SP1で同じ機能を実現できますか?


更新:

FluentValidationはすでにサポートしています ASP.NET MVC 2のクライアント側の検証とメタデータのようです。

それでも、依存するプロパティを検証するためにデータ注釈を使用できるかどうかを知ることは良いでしょう。

43
Darin Dimitrov

MVC2には、DataAnnotationsを機能させる方法を示すサンプルの "PropertiesMustMatchAttribute"が付属しており、.NET 3.5と.NET 4.0の両方で機能するはずです。そのサンプルコードは次のようになります。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";

    private readonly object _typeId = new object();

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
        : base(_defaultErrorMessage)
    {
        OriginalProperty = originalProperty;
        ConfirmProperty = confirmProperty;
    }

    public string ConfirmProperty
    {
        get;
        private set;
    }

    public string OriginalProperty
    {
        get;
        private set;
    }

    public override object TypeId
    {
        get
        {
            return _typeId;
        }
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
            OriginalProperty, ConfirmProperty);
    }

    public override bool IsValid(object value)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
        object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
        object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
        return Object.Equals(originalValue, confirmValue);
    }
}

モデルクラスのプロパティに配置するのではなく、その属性を使用するときは、クラス自体に配置します。

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
    public string NewPassword { get; set; }
    public string ConfirmPassword { get; set; }
}

「IsValid」がカスタム属性で呼び出されると、モデルインスタンス全体がそれに渡されるため、そのように依存プロパティ値を取得できます。簡単にこのパターンに従って、日付比較属性、またはより一般的な比較属性を作成できます。

Brad Wilsonのブログに良い例があります 検証のクライアント側の部分を追加する方法を示していますが、その例が.NET 3.5と.NETの両方で機能するかどうかはわかりませんが4.0。

29
Travis Illig

私はこの非常に問題があり、最近ソリューションをオープンソースにしました: http://foolproof.codeplex.com/

上記の例に対するFoolproofのソリューションは次のとおりです。

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}
14
Nick Riggs

PropertiesMustMatchの代わりに、MVC3で使用できるCompareAttributeを指定します。このリンクによると http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1

public class RegisterModel
{
    // skipped

    [Required]
    [ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }                       

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
    public string ConfirmPassword { get; set; }
}

CompareAttributeは、実際にはSystem.ComponentModel.DataAnnotationsの一部ではないが、System.Web.Mvc =に追加された非常に便利な新しいバリデーターです。 DLLチームによる。特に名前はよくありませんが(比較するのは同等かどうかを確認することだけなので、おそらくEqualToがより明白になるでしょう)、このバリデーターは使用法から簡単にわかります。あるプロパティの値が別のプロパティの値と等しいことを確認しますコードから、属性が比較している他のプロパティの名前である文字列プロパティを受け取ることがわかりますこのタイプのバリデータの古典的な使用法ここで使用しているのは、パスワードの確認です。

7
orcy

あなたの質問が尋ねられてから少し時間がかかりましたが、メタデータが(少なくとも時々)好きな場合は、属性にさまざまな論理式を提供できる別の代替ソリューションがあります:

[Required]
public DateTime? StartDate { get; set; }    
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }

サーバーおよびクライアント側で機能します。詳細 ここにあります

4
jwaliszko

.NET 3.5のDataAnnotationsのメソッドでは、検証された実際のオブジェクトまたは検証コンテキストを提供することができないため、これを達成するために少し手間をかける必要があります。私はASP.NET MVCに精通していないことを認めなければならないので、MCVと正確に組み合わせてこれを行う方法を言うことはできませんが、スレッド静的な値を使用して引数自体を渡すことができます。動作する可能性のあるものの例を次に示します。

最初に、コールスタックを介してオブジェクトを渡すことなく、オブジェクトを渡すことができる、ある種の「オブジェクトスコープ」を作成します。

public sealed class ContextScope : IDisposable 
{
    [ThreadStatic]
    private static object currentContext;

    public ContextScope(object context)
    {
        currentContext = context;
    }

    public static object CurrentContext
    {
        get { return context; }
    }

    public void Dispose()
    {
        currentContext = null;
    }
}

次に、ContextScopeを使用するバリデーターを作成します。

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
         Event e = (Event)ObjectContext.CurrentContext;

         // validate event here.
    }
}

そして最後になりましたが、オブジェクトがContextScopeを通過していることを確認してください:

Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}

これは便利ですか?

3
Steven