40


18

ASP.NET MVCに問題があるように見えます。それぞれのページに同じ名前を使用していても、種類が異なる(radio / hidden / etc)場合は、次のようにします。最初のフォームの投稿(私は例えば 'Date’ラジオボタンを選択します)、もしフォームが再レンダリングされれば(例えば結果ページの一部として)、私は他のフォームのSearchTypeの隠された値が最後のラジオボタンの値(この場合はSearchType.Name)に変更されます。

以下は削減目的のフォームの例です。

<%Html.BeginForm( "Search"、 "Search"、FormMethod.Post); %> <%= Html.RadioButton( "SearchType"、SearchType.Date、true)%> <%= Html.RadioButton( "SearchType"、SearchType.Name)%> <%Html.EndForm(); %>

<%Html.BeginForm( "Search"、 "Search"、FormMethod.Post); %> <%= Html.Hidden( "SearchType"、SearchType.Colour)%> <%Html.EndForm(); %>

<%Html.BeginForm( "Search"、 "Search"、FormMethod.Post); %> <%= Html.Hidden( "SearchType"、SearchType.Reference)%> <%Html.EndForm(); %>

結果のページソース(これは結果ページの一部になります)


RC1を持っている人なら誰でもこれを確認できますか?

たぶん、私は列挙型を使っているからです。 知りません。 隠しフィールドに 'manual' input()タグを使用することでこの問題を回避できることを付け加えておく必要がありますが、MVCタグ(<%= Html.Hidden(…​)%>)を使用すると、.NET MVCによって置き換えられます。毎回。

どうもありがとう。

更新:

今日もこのバグを見ました。 投稿されたページを返し、MVCセットの隠しフォームタグをHtmlヘルパーと共に使用すると、頭が切り取られるようです。 私はこれについて Phil Haackに連絡しました。他にどこを向けるべきか私にはわからないし、Davidによって指定されているようにこれが期待される動作であるべきではないと思います。

12 Answer


35


はい、この動作は現在仕様によるものです。 明示的に値を設定していても、同じURLに投稿した場合は、モデルの状態を調べてその値を使用します。 通常、これにより、元の値ではなく、ポストバックで送信した値を表示できます。

考えられる解決策は2つあります。

解決策1

各フィールドに固有の名前を使用してください。 デフォルトでは、指定した名前がHTML要素のIDとして使用されます。 複数の要素に同じIDを持たせるのは無効なHTMLです。 そのため、一意の名前を使用することをお勧めします。

解決策2

隠しヘルパーを使用しないでください。 あなたは本当にそれを必要としないようです。 代わりに、これを実行できます。


もちろん、これについてもっと考えると、ポストバックに基づいて値を変更することはテキストボックスには意味がありますが、隠された入力には意味がありません。 v1.0ではこれを変更できませんが、v2ではこれを検討します。 しかし、そのような変化の影響を慎重に検討する必要があります。


10


他のモデルと同じように、ModelStateを使用してモデルを埋めることを想定していました。ビュー内の式でModelを明示的に使用するときは、ModelStateではなくModelを使用する必要があります。

これは設計上の選択であり、その理由はわかりません。検証が失敗した場合、入力値がモデル内のデータ型に解析できない可能性があり、ユーザーが入力した値に誤りがある場合はレンダリングする必要があります。

私が理解できない唯一のことは、開発者が明示的に設定したModelが使用されていることが設計上の理由でないこと、および検証エラーが発生した場合はModelStateが使用されていることです。

私は多くの人がのような回避策を使っているのを見ました

  • ModelState.Clear():すべてのModelState値をクリアしますが、基本的にMVCでのデフォルト検証の使用を無効にします

  • ModelState.Remove( "SomeKey"):ModelState.Clear()と同じですが、ModelStateキーのマイクロマネージメントが必要です。これは非常に手間がかかり、MVCの自動バインディング機能ではうまくいきません。 FormキーとQueryStringキーも管理していた20年前のことです。

  • HTML自体のレンダリング:追加の機能を使用すると、HTML Helperメソッドの処理が多くなり、詳細になり、捨てられます。 例:@ Html.HiddenForをm.Nameに置き換えます) "id =" @ Html.IdFor(m ⇒ m.Name) "value =" @ Html.AttributeEncode(Model.Name) ">。 または@ Html.DropDownListForを…​に置き換えます。

  • 設計上の問題を回避するために、デフォルトのMVC HTMLヘルパーを置き換えるカスタムHTMLヘルパーを作成します。 これはHTMLをレンダリングするよりも一般的な方法ですが、他のすべての機能を保持しながらModelよりModelStateの優先順位を無効にするには、さらにHTML MVCの知識またはSystem.Web.MVCの逆コンパイルが必要です。

  • POST-REDIRECT-GETパターンを適用する:これは環境によっては簡単ですが、対話や複雑さが増す環境では困難です。 このパターンには長所と短所があります。ModelよりModelStateが設計上選択されているため、このパターンを適用する必要はありません。

問題

つまり問題は、ModelStateからModelが入力されるということです。ビューでは、Modelを使用するように明示的に設定します。 検証エラーがない限り、誰でもModel値(変更された場合)が使用されることを期待しています。その場合はModelStateを使用できます。

現在MVC Helperエクステンションでは、ModelState値がModel値よりも優先されます。

溶液

そのため、この問題に対する実際の修正は次のようになります。各式でModel値を取得するには、その値に対する検証エラーがない場合はModelState値を削除する必要があります。 その入力コントロールに検証エラーがある場合は、ModelState値を削除しないでください。通常どおりに使用されます。 私はこれが問題を正確に解決すると思います、それはほとんどの回避策より良いです。

コードはここにあります:

/// ///検証エラーが存在しない場合、モデル上の指定されたプロパティに対応するModelStateエントリを削除します。 ///ポストバック後にサーバー上でModel値を変更するときに///を呼び出し、ModelStateエントリが優先されないようにします。 /// public static void RemoveStateFor(このHtmlHelperヘルパー、Expression> expression){//最初に名前の期待値を取得する。 これは、helper.NameFor(expression)と同等です。string name = ExpressionHelper.GetExpressionText(expression); string fullHtmlFieldName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

//この入力コントロールModelState modelStateにmodelstateエラーが存在するかどうかを確認します。 if(!helper.ViewData.ModelState.TryGetValue(fullHtmlFieldName、out modelState)|| modelState.Errors.Count == 0){//モデル状態エラーが存在しない場合にのみModelState値を削除します。モデルhelper.ViewData.ModelState.Remove(name); }}

そして、MVCエクステンションを呼び出す前に、これを行うために独自のHTMLヘルパーエクステンションを作成します。

public static MvcHtmlString TextBoxForModel(このHtmlHelper、htmlHelper、式>式、文字列フォーマット= ""、辞書htmlAttributes = null){RemoveStateFor(htmlHelper、expression); htmlHelper.TextBoxFor(expression、format、htmlAttributes)を返します。 }

パブリックstatic IHtmlString HiddenForModel(このHtmlHelper htmlHelper、Expression> expression){RemoveStateFor(htmlHelper、expression); htmlHelper.HiddenFor(expression)を返します。 }

この解決策は問題を取り除きますが、MVCが通常あなたに提供しているものは何でも逆コンパイル、分析、そして再構築することを必要としません(経時的な変更の管理、ブラウザの違いなども忘れないでください)。

検証エラーが発生してもModelStateでない限り、「モデル値」の論理は設計上のものであるはずです。 もしそうであれば、それほど多くの人を噛むことはなかったでしょうが、それでもMVCが何を意図しているのかをカバーしていました。


6


私はちょうど同じ問題に遭遇しました。 渡された値に対するTextBox()の優先順位のようなHTMLヘルパーは、http://msdn.microsoft.com/en-us/library/dd492984(VS.100).aspx[Documentation]から推測されるものとは正反対の動作をするようです。 :

_ テキスト入力要素の値。 この値がnull参照(Visual BasicではNothing)の場合、要素の値はViewDataDictionaryオブジェクトから取得されます。 そこに値が存在しない場合、値はModelStateDictionaryオブジェクトから取得されます。 _

私には、値が渡された場合はそれが使用されることを読みました。 しかし、TextBox()のソースを読むと:

string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);

実際の順序は文書化されているものと正反対であることを示しているようです。 実際の順番は次のようになります。

  1. ModelState

  2. ViewData

  3. 値(呼び出し元によってTextBox()に渡される)


5


これは予想される動作です - MVCはフォームに余分な情報を渡すためにビューステートやその他の裏技を使用しないため、どのフォームを送信したのかわかりません(フォーム名は送信されたデータの一部ではありません)。名前と値のペアのリスト)

MVCがフォームを元に戻すとき、同じ名前の送信された値が存在するかどうかを確認するだけです - 名前付き値がどのフォームから来たのか、またはどのような種類のコントロールであったのかさえわかりません。ラジオ、テキスト、または非表示を使用します。HTTPを介して送信された場合は、名前=値だけです。


5


ヘッズアップ - このバグはまだMVC 3に存在します。 私はRazorマークアップ構文を使用しています(これは本当に重要です)が、オブジェクトプロパティに対して毎回同じ値を生成するforeachループで同じバグに遭遇しました。


4


foreach(ModelState.Keys.ToList()ではvar s)if(s.StartsWith( "detalleProductos"))ModelState.Remove(s);

ModelState.Remove( "TimeStamp"); ModelState.Remove( "OtherOfendingHiddenFieldNamePostedToSamePage1"); ModelState.Remove( "OtherOfendingHiddenFieldNamePostedToSamePage2");

ビュー(モデル)を返します。


3


「デザインの問題」を再現するための例、そして可能な仕事量。 「バグ」を見つけようとしていた3時間の損失に対する回避策はありませんが…​ この「設計」はまだASP.NET MVC 2.0 RTMにあることに注意してください。

    [HttpPost]
    public ActionResult ProductEditSave(ProductModel product)
    {
        //Change product name from what was submitted by the form
        product.Name += " (user set)";

        //MVC Helpers are using, to find the value to render, these dictionnaries in this order:
        //1) ModelState 2) ViewData 3) Value
        //This means MVC won't render values modified by this code, but the original values posted to this controller.
        //Here we simply don't want to render ModelState values.
        ModelState.Clear(); //Possible workaround which works. You loose binding errors information though...  => Instead you could replace HtmlHelpers by HTML input for the specific inputs you are modifying in this method.
        return View("ProductEditForm", product);
    }

フォームに元々これが含まれている場合: + <%= Html.HiddenFor(m ⇒ m.ProductId)%> +

元の "Name"(フォームがレンダリングされたときの値)が "dummy"の場合、フォームが送信された後は、 "dummy(user set)"がレンダリングされることになります。 `+ ModelState.Clear()+`がなくても、「ダミー」が表示されます!!!!!!

正しい回避策:


すべてのmvcフォーム開発者がそれを頭に入れておく必要があるので、これはまったく良い設計ではないと思います。


2


この問題はまだMVC 5に存在しており、明らかにそれは問題ないと考えられていません。

設計上、これは想定された動作ではありません。 そうではなく、隠しフィールドの値を他のタイプのフィールドと同様に機能させ、特別に扱わないようにするか、不明瞭なコレクションから値を取得します(これはViewStateを思い出させます)。

いくつかの結果(私たちにとって正しい値はモデルの値、正しくない場合はModelStateの値):

  • `+ Html.DisplayFor()+`は正しい値を表示します(モデルから取得します)

  • `+ Html.ValueFor +`はしません(ModelStateから取得します)

  • + ModelMetadata.FromLambdaExpression(expression、htmlHelper.ViewData).Model + 正しい値を引き出します

私たちの解決策は単に私たち自身の拡張機能を実装することです:

        ///
        /// Custom HiddenFor that addresses the issues noted here:
        /// http://stackoverflow.com/questions/594600/possible-bug-in-asp-net-mvc-with-form-values-being-replaced
        /// We will only ever want values pulled from the model passed to the page instead of
        /// pulling from modelstate.
        /// Note, do not use 'ValueFor' in this method for these reasons.
        ///
        public static IHtmlString HiddenTheWayWeWantItFor(this HtmlHelper htmlHelper,
                                                    Expression> expression,
                                                    object value = null,
                                                    bool withValidation = false)
        {
            if (value == null)
            {
                value = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model;
            }

            return new HtmlString(String.Format("",
                                    htmlHelper.IdFor(expression),
                                    htmlHelper.NameFor(expression),
                                    value));
        }


1


これは '仕様による’ことがありますが、文書化されているものではありません。

_ _

Public Shared Function Hidden(

  ByVal htmlHelper As System.Web.Mvc.HtmlHelper,
  ByVal name As String, ByVal value As Object)
As String

System.Web.Mvc.Html.InputExtensionsのメンバー

概要:隠し入力タグを返します。

パラメータ:htmlHelper:HTMLヘルパー。 name:値を検索するために使用されるフォームフィールド名とSystem.Web.Mvc.ViewDataDictionaryキー。 value:隠し入力の値です。 nullの場合は、System.Web.Mvc.ViewDataDictionary、次にSystem.Web.Mvc.ModelStateDictionaryを調べて値を調べます。 _ _

これは、valueパラメーターがnull(または指定されていない)の場合にのみ、HtmlHelperが他の場所で値を探すことを示唆しているように思われます。

私のアプリでは、html.Hidden( "remote"、True)が `++`としてレンダリングされるフォームがあります。

ViewData.ModelStateディクショナリの内容によって値が上書きされていることに注意してください。

それとも私は何かが足りない?


1


回避策があります。

public static class HtmlExtensions {private static readonly String hiddenFomat = @ ""; public static MvcHtmlString HiddenEx(このHtmlHelper、htmlHelper、文字列名、T []値){var builder = new StringBuilder(values.Length * 100); (Int32 i = 0; i <values.Length; builder.AppendFormat(hiddenFomat、htmlHelper.Id(name)、values [i] .ToString()、htmlHelper.Name(name))); MvcHtmlString.Create(builder.ToString())を返します。 }}