しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC で条件付きの検証を行う属性とモデルバインダを実装してみた

昔から ASP.NET MVC の検証を条件付きで実行したいと思っていた人生でした。

言葉で説明すると分かりにくいので、ヨドバシのオンラインショップのフォームがサンプルとして都合良かったので拾ってきました。

f:id:shiba-yan:20140108233132p:plain

このフォームの場合、チェックの有無で検証の有無も切り替わるようになっています。これを ASP.NET MVC で実現する場合、殆どの場合は IValidatableObject を実装して、独自の検証を行うコードを書くのではないかと思います。面倒ですね。

地味にこのようなフォームが必要になるケースって多いと思います。仕事で割と出会っているので、今回は気合い入れて条件付きの検証を行う属性とモデルバインダを実装しました。

成果物は SwissKnife.Mvc に突っ込んであるので GitHub にはソースコードが、NuGet にはバイナリを置いてあります。

NuGet Gallery | SwissKnife.Mvc 0.0.11

簡単にサンプルコードを作成して、どのような動作になるか確認しておきます。まずは以下のようなシンプルなモデルを用意しました。

public class FormModel
{
    public int Type { get; set; }

    [Conditional("Type", 1)]
    [Required]
    public string Value1 { get; set; }

    [Conditional("Type", 2)]
    [Required]
    public string Value2 { get; set; }
}

Conditional 属性が今回作成した条件付き検証を行うための属性になります。今回は Type プロパティの値を見て検証を行うか決定します。

この属性だけでは条件付きの検証を行うのは難しかったので、今回はモデルバインダ自体に手を入れています。なので、デフォルトのモデルバインダを入れ替えておく必要があります。

protected void Application_Start()
{
    // モデルバインダを入れ替える
    ModelBinders.Binders.DefaultBinder = new ConditionalModelBinder();
}

これで使う準備は完了です。

あとは適当にビューを作って試してみましょう。シンプルにラジオボタンが付いているだけのフォームを用意したので、まずは Value1 にチェックを入れて送信してみます。

f:id:shiba-yan:20140108231125p:plain

Value1 のフォームにはエラーメッセージが表示されましたが、Value2 のフォームには表示されていません。

今度はチェックを入れ替えて Value2 を選択して送信してみます。

f:id:shiba-yan:20140108231152p:plain

先ほどの結果とは逆になりました。

今回はラジオボタンを使いましたが、チェックボックスで true の場合のみ検証を有効にすることも出来ますね。ちなみに、以下のような複合型にも適用することが出来るので、個人的には非常に便利になりました。

public class FormModel
{
    public bool Enable { get; set; }

    [Conditional("Enable", true)]
    public ChildFormModel Child { get; set; }
}

public class ChildFormModel
{
    [Required]
    public bool Name { get; set; }

    [Required]
    public bool Value { get; set; }
}

実は条件付きの検証を行うと紹介してきましたが、実際はモデルバインディングと検証はいったん行い、最終段階で対象のプロパティの値を確認して ModelStateDictionary からエラーを削除しているだけです。

バインディングの順序というのはリフレクションに依存し、値を確実に利用するためには一旦全ての処理を完了させる必要があったので、このような実装にしました。

もうちょっと複雑な条件を指定できるようにしたい感はありますが、ご自由に利用ください。