読者です 読者をやめる 読者になる 読者になる

しばやん雑記

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

ASP.NET MVC で任意のタイミングでモデルの検証を行う方法

MVC 5 でウィザード的に 1 ページごとに項目を入れていくページを作っていると、実際に DB に格納する前にセッションに入れておいたモデルの状態が正しいか調べたくなりました。

てっきり「あー、UpdateModel / TryUpdateMode の中身参考にしないといけないのか、めんどくさいなー」と思っていたところ、かなり前*1から Controller クラスに ValidateModel / TryValidateModel メソッドが用意されていることに気が付きました。

Controller.TryValidateModel メソッド (System.Web.Mvc)
Controller.ValidateModel メソッド (System.Web.Mvc)

MVC 5 の機能は一通りは触ってるだろうと思っていたら、こんな初歩的な部分を見落としていたことに恥ずかしい限り…。戒めとしてこの記事を書きます。

これらのメソッドは基本的にモデルバインダーが内部で行っている検証とほぼ同じ処理になるので、データアノテーションや IValidatableObject を使った検証まで行ってくれるようです。

public class SampleModel
{
    [Required]
    public string Title { get; set; }

    [Range(0, 1000)]
    public int Price { get; set; }
}

動作を確認するために、上のような簡単なモデルクラスを用意しました。

public ActionResult Confirm()
{
    // わざと検証エラーになるデータを入れる
    var model = new SampleModel
    {
        Price = -1
    };

    // データに不整合が発生していないかチェック
    TryValidateModel(model);

    return View(model);
}

簡単なビューを用意して実行してみたところ、モデルバインダーを使っていないにもかかわらずエラーが表示されることが確認できます。

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

ちなみに以下のようにネストした場合にはルートのプロパティしか検証してくれないので、実際に使う場合にはちょっと工夫とか注意が必要になるかと思います。

public class SampleModel
{
    [Required]
    public string Title { get; set; }

    [Range(0, 1000)]
    public int Price { get; set; }

    public SampleChildModel ChildModel { get; set; }
}

public class SampleChildModel
{
    [Required]
    public string Name { get; set; }
}

以下のようなアクションに書き換えて実行をしてみると、ChildModel.Name プロパティは必須にもかかわらずエラーとなりません。これはいまいちな挙動です。

public ActionResult Confirm()
{
    // わざと検証エラーになるデータを入れる
    var model = new SampleModel
    {
        Price = -1,
        ChildModel = new SampleChildModel()
    };

    // データに不整合が発生していないかチェック
    TryValidateModel(model);
    TryValidateModel(model.ChildModel);

    return View(model);
}

なので、とりあえずはネストしたモデルに対しても TryValidateModel メソッドを実行することで、意図したとおりの挙動にはなります。拡張メソッドを用意してもいいかもしれません。

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

DB 登録前には最終的なモデルのチェックをしたかったのに、このメソッドの存在を知らなかった為に各方面に迷惑をかけた気がします。*2

*1:少なくとも MVC 2 の頃にはあったらしい…

*2:主に蠣殻町の某社さん