6

これは、既にここに投稿されている問題と非常によく似た問題です: 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 がこのように動作しているのかを理解しました。

4

1 に答える 1

5

Ok。私は今、自分でこれに答えることができるところまで来ました。

ExcludePropertyErrors 引数を True に設定して ValidationSummary を使用すると、ViewData.TemplateInfo.HtmlFieldPrefix に等しいキーを使用してモデル状態のエラーが検索されます。デフォルトでは、これは String.Empty です。

ビジネス モデルを公開するビュー モデルがある場合は、次のようになります。

namespace ValidationSummary.Models
{
   using System;
   using System.Collections.Generic;
   using System.ComponentModel.DataAnnotations;

   public class TradeModel : IValidatableObject
   {
      public DateTime StartDate { get; set; }

      public DateTime EndDate { get; set; }

      public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
      {
         List<ValidationResult> validationResults = new List<ValidationResult>();

         if (EndDate < StartDate)
         {
            validationResults.Add(new ValidationResult("End date must not be before start date"));
         }

         return validationResults;
      }
   }
}

namespace ValidationSummary.ViewModels
{
   public class Trade
   {
      public Trade()
      {
         this.TradeModel = new Models.TradeModel();
      }

      public Models.TradeModel TradeModel { get; private set; }
   }
}

検証が行われると、モデル レベルで追加されたエラー (ValidationResults) (プロパティ名を受け取る ValidationResult コンストラクターに追加の引数がない場合) が、プロパティ名のプレフィックスと共に ModelState に追加されます。ビュー モデルの - この例では「TradeModel」。

現在検討中の方法がいくつかあります。

  1. ビジネス モデルをビュー モデルの外に公開しないでください。これには、基本的に、ビュー モデルのビジネス モデルではなく、ビュー モデルへのバインドが含まれます。これには 2 つの方法があります。ビュー モデルをビジネス モデルのサブクラスにする。または、ビジネス モデルからビュー モデルにプロパティを複製します。私は前者の方が好きです。
  2. モデル レベルのエラーを String.Empty の新しい ModelState ディクショナリ キーにコピーするコードを記述します。残念ながら、ビジネス モデルを公開するビュー モデルのプロパティ名がキーとして使用されるため、これをエレガントに行うことはできない場合があります。これは、コントローラー/ビューモデルごとに異なる場合があります。
  3. ビューで次を使用します。これにより、ビジネス モデルのエラー メッセージが表示されます。本質的に、これは、ビジネス モデルのモデル レベルのエラーが実際にはプロパティ エラーであるというふりをしています。これらの表示は ValidationSummary と同じではありませんが、これは CSS で解決できる可能性があります。

    @Html.ValidationMessageFor(m => m.TradeModel)

  4. ValidationSummary をサブクラス化します。これには、ModelState のどのキーがビュー モデルのビジネス モデル プロパティを参照しているかを認識できるように変更する必要があります。

于 2012-07-05T13:14:37.570 に答える