これは、既にここに投稿されている問題と非常によく似た問題です: ASP.NET MVC: TryUpdateModel に設定された検証メッセージが ValidationSummary を表示しない
その古いトピックが以前のバージョンの MVC に関連していたかどうかはわかりませんが、MVC3 では、同様の線に沿って奇妙な動作が発生しています。
Tradeというモデルクラスがあります。これは IValidatableObject を継承しているため、Validate メソッドを実装しています。この中に、モデル全体の検証があります (プロパティの検証を強制するデータ注釈とは対照的です)。検証は次のとおりです。
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validationResults = new List<ValidationResult>();
if (this.EndDate < this.StartDate)
{
validationResults.Add(new ValidationResult("End date must be greater than start date"));
}
return validationResults;
}
取引の表示に役立つビューモデルがあります。これには、TradeModel プロパティを介した取引モデルへの参照が含まれています。したがって、基本的にビュー モデルは Trade モデルであり、カウンターパーティなどのドロップダウン リストの母集団に関する追加情報が追加されます。
CSHTML クラスには ValidationSummary が含まれており、引数として「true」が指定されています。これは、モデル エラーのみを表示することを意味します。
次のように、新しい Trade を作成するための HttpPost コントローラー メソッドを実装すると...
[HttpPost]
public ActionResult Create(FormCollection collection)
{
var trade = new Trade();
if (this.TryUpdateModel(trade))
{
if (this.SaveChanges(this.ModelState, trade))
{
return this.RedirectToAction("Index");
}
}
return this.View(trade);
}
...StartDate > EndDate で取引を開始すると、TryUpdateModel が false を返し、ユーザーが取引に戻るように指示されます。これは論理的に思えます。残念ながら、ValidationSummary はエラー メッセージを表示しません。
Create メソッドにブレークポイントを置いて ModelState を調べると、辞書にエラー メッセージがあることがわかります。これは、プロパティに対するものではなく、「TradeModel」のキーに対するものです。繰り返しますが、これは論理的に思えます。
これがなぜなのかについての 1 つの理論は、ValidationSummary は、String.Empty ではないキーに対する検証エラーはすべてプロパティ検証エラーであると想定しているということです。モデルへの参照を含むビュー モデルがあるため、検証エラーを無視しています。したがって、キーは「TradeModel」になります。
この理論を水から吹き飛ばすのはこれです:コントローラーのCreate関数を次のように書き直すと...
[HttpPost]
public ActionResult Create(Trade trade, FormCollection collection)
{
if (this.SaveChanges(this.ModelState, trade))
{
return this.RedirectToAction("Index");
}
return this.View(trade);
}
...したがって、バインディングを「自動的に」実行する MVC に依存し、同じテスト シナリオを再実行すると、意図したエラー メッセージがユーザーに表示されます。
ブレークポイントを追加して ModelState を見ると、以前と同じキーに対して同じエラー メッセージが表示されますが、今回は ValidationSummary がそれらをピックアップします!
次のように検証を修正すると、いずれかの方法で記述されたコントローラーの Create 関数で動作します。
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validationResults = new List<ValidationResult>();
if (this.EndDate < this.StartDate)
{
validationResults.Add(new ValidationResult("End date must be greater than start date", new[] { "StartDate" }));
}
return validationResults;
}
明らかに、モデル レベルの検証エラーの問題にすぎません。
これについて何か助けていただければ幸いです。ビュー モデルのインスタンスを手動で作成し、TryUpdateModel を使用してバインディングを呼び出す必要があるのには理由があります (ここでは説明しません)。
前もって感謝します!
アップデート
プロパティ エラーを除外するように指示されたときに、ValidationSummary が String.Empty の ModelState にキーを持つエラーのみを表示するという理論は、実際には正しいようです。ソース コードを調べたところ、実際には ViewData.TemplateInfo.HtmlFieldPrefix を使用して、モデル レベルで検証エラーを見つけています。デフォルトでは、これは String.Empty です。
したがって、この値を「TradeModel」に変更することは論理的に思えますが、すべての HTML ID または名前にプレフィックスが付けられるため、バインディングは失敗します。
ビュー モデルにビジネス モデルへの参照が含まれている場合、IValidatableObject によって ModelState に追加されたエラーには、ビュー モデルのビジネス モデル プロパティ名 (この場合は「TradeModel」) に等しいプレフィックスを含むキーが追加され、次のようになります。 「TradeModel.CounterpartyId」などのキー。モデル レベルのエラーは、ビュー モデルのビジネス モデル プロパティ名 (「TradeModel」) に等しいキーで追加されます。
したがって、ビュー モデルがこのように構築されている場合、ビジネスはモデル レベルの検証エラーを追加できないようです。
私が困惑しているのは、Trade ビュー モデル オブジェクトを引数として受け取るようにコントローラーの Create 関数が記述されているときに、これが「実際の」プロジェクトで実際に機能する理由です。昨日はこれを見逃していたに違いありませんが、今日見ると、MVC がバインドして検証がトリガーされると、辞書の末尾に String.Empty の値を持つ余分なキーが追加されているように見えます。これには、TradeModel のキーで追加されたエラーの複製が含まれています。ご想像のとおり、ValidationSummary がそれらを取得します。
では、なぜ MVC はライブ プロジェクトでこれを行うのに、単純なテスト アプリではそうしないのでしょうか?
ビュー モデルを引数として取るようにコントローラー関数が記述されている場合、検証が 2 回トリガーされるのを見てきました。たぶん、これは毎回微妙に違うことをしているのですか?
更新...もう一度
実際のプロジェクトで機能する理由は、モデル状態で見つかったすべてのエラーを String.Empty のキーを持つ新しいエントリにコピーするコードがベース コントローラー (他のすべてのコントローラーから継承) に埋め込まれているためです。このコードは、2 つのシナリオのうちの 1 つのみで呼び出されていました。したがって、実際のアプリとテスト アプリの間に実際の違いはありません。
何が起こっているのか、なぜ ValidationSummary がこのように動作しているのかを理解しました。