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); }
簡単なビューを用意して実行してみたところ、モデルバインダーを使っていないにもかかわらずエラーが表示されることが確認できます。
ちなみに以下のようにネストした場合にはルートのプロパティしか検証してくれないので、実際に使う場合にはちょっと工夫とか注意が必要になるかと思います。
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 メソッドを実行することで、意図したとおりの挙動にはなります。拡張メソッドを用意してもいいかもしれません。
DB 登録前には最終的なモデルのチェックをしたかったのに、このメソッドの存在を知らなかった為に各方面に迷惑をかけた気がします。*2