web-dev-qa-db-ja.com

@ Html.HiddenForはASP.NET MVCのリストでは機能しません

プロパティとしてリストを含むモデルを使用しています。このリストには、SQL Serverから取得したアイテムを入力しています。リストをビューで非表示にして、POSTアクションに渡すようにします。後で、jQueryを使用してこのリストにさらに項目を追加すると、配列を後で拡張するのに適さないようにすることができます。通常は使用します

@Html.HiddenFor(model => model.MyList)

この機能を実現しますが、何らかの理由でPOSTのリストは常にnullです。

非常に簡単な質問です。MVCがこのように動作する理由は誰でも知っていますか?

87
Anton Smith

私はこの問題に出くわし、次のことを実行するだけで解決しました。

@for(int i = 0; i < Model.ToGroups.Length; i++)
{
    @Html.HiddenFor(model => Model.ToGroups[i])
}

Foreachの代わりにforを使用すると、モデルバインディングが正しく機能し、リスト内のすべての非表示の値が取得されます。この問題を解決する最も簡単な方法のようです。

137
Daniel Mackay

HiddenForはDisplayForやEditorForとは異なります。コレクションでは機能せず、単一の値のみが機能します。

MVC Futuresプロジェクトで使用可能なSerialize HTMLヘルパーを使用して、オブジェクトをHiddenフィールドにシリアル化できます。または、コードを自分で記述する必要があります。より良い解決策は、何らかのIDを単純にシリアル化し、ポストバック時にデータベースからデータを再取得することです。

28

ちょっとしたハックですが、リストで@Html.EditorForまたは@Html.DisplayForが機能する場合、投稿リクエストで送信されるが表示されないことを確認したい場合は、display: none;代わりに非表示にします。例:

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>
13
Mark Rhodes

Newtonsoftを使用してオブジェクトをJSON文字列に逆シリアル化し、それをHiddenフィールドに挿入する方法はどうですか。 (Model.DataResponse.Entity.Commissionは、単純な"CommissionRange"オブジェクトのListですJSONに表示されます)

@using (Ajax.BeginForm("Settings", "AffiliateProgram", Model.DataResponse, new AjaxOptions { UpdateTargetId = "result" }))
   {
      string commissionJson = JsonConvert.SerializeObject(Model.DataResponse.Entity.Commission);
      @Html.HiddenFor(data => data.DataResponse.Entity.Guid)
      @Html.Hidden("DataResponse_Entity_Commission", commissionJson)
      [Rest of my form]
   }

レンダリング:

<input id="DataResponse_Entity_Commission" name="DataResponse_Entity_Commission" type="hidden" value="[{"RangeStart":0,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":1,"RangeEnd":2,"CommissionPercent":3.00000},{"RangeStart":2,"RangeEnd":0,"CommissionPercent":2.00000},{"RangeStart":3,"RangeEnd":2,"CommissionPercent":1.00000},{"RangeStart":15,"RangeEnd":10,"CommissionPercent":5.00000}]">

私の場合、ポストバックする前に、非表示フィールドのjsonを編集するためにいくつかのJSを行います

私のコントローラーでは、再びNewtonsoftを使用して逆シリアル化します。

string jsonCommissionRange = Request.Form["DataResponse_Entity_Commission"];
List<CommissionRange> commissionRange = JsonConvert.DeserializeObject<List<CommissionRange>>(jsonCommissionRange);
8
Adam Hey

Html.HiddenForは、1つの値のみのために設計されています。非表示フィールドを作成する前に、何らかの方法でリストをシリアル化する必要があります。

たとえば、リストのタイプが文字列の場合、リストをカンマ区切りリストに結合し、コントローラーにポストバックした後にリストを分割できます。

6
Kyle Trauberman

非表示になっているのはEditorForに従う必要があることがわかりました(モデル値がコントローラーに戻らない理由を解明しようとして数時間後)。

私が他の何か間違ったことをしていない限り、これは私が見つけたものです。私は二度と間違いを犯しません。

別のクラスのリストを含むモデルのコンテキスト内。

これは機能しません:

        @{
            for (int i = 0; i < Model.Categories.Count; i++)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].Id)
                        @Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
                        @Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)                            
                        @Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)                            
                    </td>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)                                                        
                        @Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                        @Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                    </td>
                    <td style="text-align: center">
                        @Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)                            
                        @Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
                    </td>
                </tr>
            }
        }

このように......

            for (int i = 0; i < Model.Categories.Count; i++)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => Model.Categories[i].Id)
                        @Html.HiddenFor(modelItem => Model.Categories[i].ProductCategoryId)
                        @Html.HiddenFor(modelItem => Model.Categories[i].CategoryName)                            
                        @Html.DisplayFor(modelItem => Model.Categories[i].CategoryName)                            
                    </td>
                    <td>
                        @Html.EditorFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                        @Html.HiddenFor(modelItem => Model.Categories[i].DailyPurchaseLimit)                            
                        @Html.ValidationMessageFor(modelItem => Model.Categories[i].DailyPurchaseLimit)
                    </td>
                    <td style="text-align: center">
                        @Html.EditorFor(modelItem => Model.Categories[i].IsSelected)
                        @Html.HiddenFor(modelItem => Model.Categories[i].IsSelected)                            
                    </td>
                </tr>
            }
4
AntDC

これを確認できます solution

EditorTemplate内にHiddenForのみを配置します。

そして、あなたのビューにこれを入れてください:@Html.EditorFor(model => model.MyList)

動作するはずです。

3

私はHiddenForのソースコードを掘り始めました。あなたが見ている障害は、あなたの複雑なオブジェクトMyListstring型に暗黙的に変換できないため、フレームワークがModelの値はnullであり、value属性を空にします。

3
Cᴏʀʏ

同じ問題に直面した。 forループなしでは、リストの最初の要素のみがポストされます。 forループを繰り返し処理した後、完全なリストを保持して正常に投稿できます。

 @if (Model.MyList!= null)
    {
    for (int i = 0; i < Model.MyList.Count; i++)
      {
        @Html.HiddenFor(x => x.MyList[i])
      }
    }
3
Keerthi

別のオプションは次のとおりです。

<input type="hidden" value=@(string.Join(",", Model.MyList)) />
2
TiagoBrenck

これを修正する別の可能な方法は、リスト内の各オブジェクトにIDを与え、@Html.DropDownListFor(model => model.IDs)を使用して、IDを保持する配列を設定することです。

1
deckeresq

多分遅くなりますが、コレクションの隠しフィールドの拡張メソッドを作成しました(単純なデータ型のアイテムを使用):

だからここにある:

/// <summary>
/// Returns an HTML hidden input element for each item in the object's property (collection) that is represented by the specified expression.
/// </summary>
public static IHtmlString HiddenForCollection<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) where TProperty : ICollection
{
    var model = html.ViewData.Model;
    var property = model != null
                ? expression.Compile().Invoke(model)
                : default(TProperty);

    var result = new StringBuilder();
    if (property != null && property.Count > 0)
    {
        for(int i = 0; i < property.Count; i++)
        {
            var modelExp = expression.Parameters.First();
            var propertyExp = expression.Body;
            var itemExp = Expression.ArrayIndex(propertyExp, Expression.Constant(i));

            var itemExpression = Expression.Lambda<Func<TModel, object>>(itemExp, modelExp);

            result.AppendLine(html.HiddenFor(itemExpression).ToString());
        }
    }

    return new MvcHtmlString(result.ToString());
}

使い方は次のように簡単です。

@Html.HiddenForCollection(m => m.MyList)
1
Gh61

foreachループの代わりにforループを使用すると、わずかにクリーンなソリューションになります。

@foreach(var item in Model.ToGroups)
{
    @Html.HiddenFor(model => item)
}
0
Sebastian