web-dev-qa-db-ja.com

間違った値のASP.Net MVC Html.HiddenFor

プロジェクトでMVC 3を使用していますが、非常に奇妙な動作が見られます。

モデルの特定の値に隠しフィールドを作成しようとしていますが、問題は何らかの理由でフィールドに設定された値がモデルの値に対応していないことです。

例えば.

私はこのコードをテストとして持っています:

<%:Html.Hidden("Step2", Model.Step) %>
<%:Html.HiddenFor(m => m.Step) %>

両方の非表示フィールドの値は同じだと思います。私がやることは、ビューを初めて表示するときに値を1に設定し、送信後にModelフィールドの値を1増やすことです。

したがって、ページを最初にレンダリングするときは両方のコントロールの値が1ですが、2回目にレンダリングする値は次のとおりです。

<input id="Step2" name="Step2" type="hidden" value="2" />
<input id="Step" name="Step" type="hidden" value="1" />

ご覧のとおり、最初の値は正しいですが、2番目の値はビューを最初に表示したときと同じように見えます。

私は何が欠けていますか? * For Htmlヘルパーは何らかの方法で値をキャッシュしていますか?その場合、このキャッシュを無効にするにはどうすればよいですか?.

ご協力いただきありがとうございます。

127
willvv

それは正常であり、HTMLヘルパーの仕組みです。最初にPOSTリクエストの値を使用し、その後モデルの値を使用します。これは、コントローラーアクションでモデルの値を変更しても、同じ変数がPOST要求は変更が無視され、POSTされた値が使用されます。

考えられる回避策の1つは、値を変更しようとしているコントローラーアクションのモデル状態からこの値を削除することです。

// remove the Step variable from the model state 
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;

もう1つの可能性は、常にモデルの値を使用してPOST値を無視するカスタムHTMLヘルパーを作成することです。

そしてさらに別の可能性:

<input type="hidden" name="Step" value="<%: Model.Step %>" />
183
Darin Dimitrov

Wizardを書くときに同じ問題に遭遇しました。
「ステップ1」からのデータやエラーは、「StateStateが「非難」することに最終的に気付くまで、「ステップ2」などと混同されます。

これは私の簡単な解決策でした:

if (oldPageIndex != newPageIndex)
{
    ModelState.Clear(); // <-- solution
}

return View(model[newPageIndex]);
18
Peter B

このコードは機能しません

// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;

... HiddenFor always(!)は、モデル自体ではなくModelStateから読み取るためです。 「Step」キーが見つからない場合、変数タイプのデフォルトが生成され、この場合は0になります

これが解決策です。私は自分でそれを書いたが、それを共有することを気にしないでください。多くの人がこのいたずらなHiddenForヘルパーに苦労しているのを見るからです。

public static class CustomExtensions
{
    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        string text = ExpressionHelper.GetExpressionText(expression);
        string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
        ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        if (modelState.ContainsKey(fullName))
        {                
            ValueProviderResult currentValue = modelState[fullName].Value;
            modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
        }
        else
        {
            modelState[fullName] = new ModelState
            {
                Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), CultureInfo.CurrentUICulture)
            };
        }
    }
}

次に、ビュー内から通常どおり使用します。

@Html.HiddenFor2(m => m.Id)

それはコレクションでも動作することを言及する価値があります。

1

呼び出し間で同じモデル状態を使用し、バックエンドでモデルプロパティを変更するときと同じ状況に苦労しています。ただし、textboxforまたはhiddenforを使用する場合、それは問題ではありません。

最初はページスクリプトを使用してモデル値をjs変数として保存することにより、この状況を回避します。最初はそのためにhiddenfieldが必要だからです。

これが役立つかどうかはわかりませんが、考慮してください。