6

デフォルトのモデル バインディングを使用してフォーム パラメータをアクションのパラメータである複雑なオブジェクトにバインドすると、フレームワークは最初のリクエストに渡された値を記憶します。つまり、そのアクションへの後続のリクエストは最初と同じデータを取得します。パラメーター値と検証状態は、関連のない Web 要求間で保持されます。

これが私のコントローラーコードです(serviceアプリのバックエンドへのアクセスを表します):

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        return View(RunTime.Default);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }

私の .aspx ビュー ( ViewPage<RunTime> として厳密に型指定されたもの) には、次のようなディレクティブが含まれています。

<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>

これは、モデルのプロパティを自動バインドするためDefaultModelBinderのクラスを使用します。

ページにアクセスし、有効なデータを入力します (例: time = 1)。アプリは新しいオブジェクトを時間 = 1 で正しく保存します。次に、もう一度押して、別の有効なデータ (時間 = 2 など) を入力します。ただし、保存されるデータは元のものです (たとえば、時間 = 1)。これは検証にも影響するため、元のデータが無効であった場合、今後入力するすべてのデータが無効と見なされます。IIS を再起動するか、コードを再構築すると、永続化された状態がフラッシュされます。

この問題は、独自のハードコーディングされたモデル バインダーを作成することで解決できます。その基本的な単純な例を以下に示します。

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }


internal class RunTimeBinder : DefaultModelBinder
{
    public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        // Without this line, failed validation state persists between requests
        bindingContext.ModelState.Clear();


        double time = 0;
        try
        {
            time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
        }
        catch (FormatException)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
        }

        var model = new RunTime(time);
        return new ModelBinderResult(model);
    }
}

何か不足していますか?最初のデータが 1 つのブラウザーに入力され、2 番目のデータが別のブラウザーに入力された場合に問題を再現できるため、ブラウザー セッションの問題ではないと思います。

4

5 に答える 5

5

問題は、コントローラーが呼び出し間で再利用されていたことであることがわかりました。元の投稿から省略した詳細の 1 つは、Castle.Windsor コンテナーを使用してコントローラーを作成していることです。コントローラーに Transient ライフスタイルをマークするのに失敗したため、リクエストごとに同じインスタンスが返されました。したがって、バインダーによって使用されているコンテキストが再利用され、もちろん古いデータが含まれていました。

私は Eilon のコードと私のコードの違いを注意深く分析しているときに問題を発見し、他のすべての可能性を排除しました。キャッスルのドキュメントにあるように、これは「ひどい間違い」です! これを他の人への警告にしましょう!

Eilon さん、お返事ありがとうございます。お時間をいただき申し訳ありません。

于 2008-10-27T20:20:24.637 に答える
2

この問題を再現しようとしましたが、同じ動作は見られません。私はあなたが持っているのとほぼ同じコントローラーとビューを作成し(いくつかの仮定を持っています)、新しい「RunTime」を作成するたびに、その値をTempDataに入れ、リダイレクトを介して送信しました。次に、ターゲットページで値を取得しました。これは、常にそのリクエストで入力した値であり、古い値になることはありません。

これが私のコントローラーです:

public class HomeController:Controller {public ActionResult Index(){ViewData ["Title"]="ホームページ"; string message = "ようこそ:" + TempData ["Message"]; if(TempData.ContainsKey( "value")){int theValue =(int)TempData ["value"]; メッセージ+="" + theValue.ToString(); } ViewData["Message"]=メッセージ; View();を返します。}

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
    return View(RunTime.Default);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
    if (ModelState.IsValid) {
        //service.CreateNewRun(newRunTime);
        TempData["Message"] = "New run created";
        TempData["value"] = newRunTime.TheValue;
        return RedirectToAction("index");
    }
    return View(newRunTime);
}

}

そして、これが私のビュー(Create.aspx)です。

<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>

また、「RunTime」タイプがどのように見えるかわからなかったので、これを作成しました。

   public class RunTime {
        public static readonly RunTime Default = new RunTime(-1);

        public RunTime() {
        }

        public RunTime(int theValue) {
            TheValue = theValue;
        }

        public int TheValue {
            get;
            set;
        }
    }

RunTimeの実装に静的な値などが含まれている可能性はありますか?

ありがとう、

エイロン

于 2008-10-27T17:53:19.970 に答える
2

これが関連しているかどうかはわかりませんが、 <%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %> への呼び出しは実際には間違ったオーバーロードを選択する可能性があります (Time は整数であるため、object htmlAttributesではなくオーバーロードを選択しstring valueます。

レンダリングされた HTML を確認すると、これが発生しているかどうかがわかります。int を に変更するViewData.Model.Time.ToString()と、正しいオーバーロードが強制されます。

あなたの問題は何か違うように聞こえますが、私はそれに気づき、過去にやけどを負いました.

于 2008-10-27T17:59:09.163 に答える
0

セブ、あなたが例で何を意味するのかわかりません。Unityの設定については何も知りません。Castle.Windsor で状況を説明します。Unity を正しく構成するのに役立つかもしれません。

既定では、Castle.Windsor は、特定の型を要求するたびに同じオブジェクトを返します。これがシングルトンのライフスタイルです。Castle.Windsor のドキュメントには、さまざまなライフスタイルのオプションに関する適切な説明があります。

ASP.NET MVC では、コントローラー クラスの各インスタンスは、処理するために作成された Web 要求のコンテキストにバインドされます。したがって、IoC コンテナーがコントローラー クラスの同じインスタンスを毎回返す場合、そのコントローラー クラスを使用した最初の Web 要求のコンテキストにバインドされたコントローラーを常に取得します。特に、ModelStateおよび によって使用されるその他のオブジェクトはDefaultModelBinder再利用されるため、バインドされたモデル オブジェクトと の検証メッセージはModelState古くなります。

したがって、MVC がコントローラー クラスのインスタンスを要求するたびに、IoC が新しいインスタンスを返す必要があります。

Castle.Windsor では、これをトランジェント ライフスタイルと呼びます。これを構成するには、次の 2 つのオプションがあります。

  1. XML 構成: コントローラーを表す構成ファイル内の各要素に lifestlye="transient" を追加します。
  2. コード内構成: コントローラーの登録時に一時的なライフスタイルを使用するようにコンテナーに指示できます。これは、Ben が言及した MvcContrib ヘルパーが自動的に行うことです。MvcContrib ソース コードの RegisterControllers メソッドを見てください。

Unity は Castle.Windsor のライフスタイルに似たコンセプトを提供していると思います。そのため、Unity を構成して、コントローラーの一時的なライフスタイルに相当するものを使用する必要があります。MvcContrib はUnity をサポートしているようです。

お役に立てれば。

于 2008-11-02T12:05:24.097 に答える
0

ASP.NET MVC アプリで Windsor IoC コンテナーを使用しようとしたときに同様の問題に遭遇したため、それを機能させるために同じ発見の旅をしなければなりませんでした。他の人に役立つ可能性のある詳細の一部を次に示します。

これを使用すると、Global.asax の初期設定が次のようになります。

  if (_container == null) 
  {
    _container = new WindsorContainer("config/castle.config");
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 
  }

そして、コントローラーインスタンスを要求されたときに実行する WindsorControllerFactory を使用します。

  return (IController)_container.Resolve(controllerType);

Windsor はすべてのコントローラーを正しくリンクしていましたが、何らかの理由でパラメーターがフォームから関連するコントローラー アクションに渡されませんでした。代わりに、正しいアクションを呼び出していましたが、それらはすべて null でした。

デフォルトでは、コンテナーがシングルトンを返すようになっています。これは明らかにコントローラーにとって悪いことであり、問​​題の原因です。

http://www.castleproject.org/monorail/documentation/trunk/integration/windsor.html

ただし、ドキュメントには、コントローラーのライフスタイルを一時的に変更できることが指摘されていますが、構成ファイルを使用している場合に実際にその方法を説明していません。それは十分に簡単であることが判明しました:

<component 
  id="home.controller" 
  type="DoYourStuff.Controllers.HomeController, DoYourStuff" 
  lifestyle="transient" />

そして、コードを変更しなくても、期待どおりに動作するはずです (つまり、コンテナーの 1 つのインスタンスによって毎回提供される一意のコントローラー)。その後、私が知っている良い男の子/女の子のようなコードではなく、構成ファイルですべての IoC 構成を行うことができます。

于 2008-11-03T09:14:19.997 に答える