@ Html.HiddenFor ไม่ทำงานกับ Lists ใน ASP.NET MVC


99

ฉันกำลังใช้โมเดลที่มีรายการเป็นคุณสมบัติ ฉันกำลังเติมข้อมูลรายการนี้ด้วยรายการที่ฉันคว้าจาก SQL Server ฉันต้องการให้รายการซ่อนอยู่ในมุมมองและส่งต่อไปยังการดำเนินการ POST ในภายหลังฉันอาจต้องการเพิ่มรายการเพิ่มเติมในรายการนี้ด้วย jQuery ซึ่งทำให้อาร์เรย์ไม่เหมาะสำหรับการขยายในภายหลัง โดยปกติคุณจะใช้

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

เพื่อให้ฟังก์ชันนี้สำเร็จ แต่ด้วยเหตุผลบางประการรายการใน POST จึงเป็นโมฆะเสมอ

คำถามง่ายๆใคร ๆ ก็รู้ว่าทำไม MVC ถึงทำตัวแบบนี้?


1
โดยปกติคุณจะไม่ซ่อนรายการทั้งหมดเช่นนั้น ผลลัพธ์ที่คุณต้องการในแง่ของ<input />s คืออะไร?
Cᴏʀʏ

1
สิ่งที่ไม่MyListประกอบด้วย? HiddenForใช้สำหรับการป้อนข้อมูลครั้งละหนึ่งรายการเท่านั้น
Daniel

1
ประเภทคือModel.MyListอะไร? คุณอาจต้องดำเนินการอนุกรม / deserialization ในรายการของคุณด้วยตนเอง
Kyle Trauberman

1
[ stackoverflow.com/questions/4381871/…คำถามที่คล้ายกัน.
Sanjeevi Subramani

1
คำถามที่คล้ายกัน: การใช้ HiddenFor กับ intellisense
Sanjeevi Subramani

คำตอบ:


163

ฉันเพิ่งเจอปัญหานี้และแก้ไขได้ง่ายๆโดยทำสิ่งต่อไปนี้:

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

การใช้ a for แทน foreach การผูกโมเดลจะทำงานได้อย่างถูกต้องและรับค่าที่ซ่อนอยู่ทั้งหมดของคุณในรายการ ดูเหมือนวิธีที่ง่ายที่สุดในการแก้ปัญหานี้


5
ขอบคุณ! บันทึกคืนของฉัน
TSmith

7
ขอบคุณ - วิธีง่ายๆที่ดี จำเป็นต้องใช้ mod เล็ก ๆ น้อย ๆ : จำเป็นต้องอ้างถึงฟิลด์ Id ของวัตถุ ดังนั้นหากฟิลด์นี้เรียกว่า RowId ดังนั้น:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Krishna Gupta

3
ได้ผลสำหรับฉันแม้ในขณะที่ฉันมีหลายช่องสำหรับโมเดลในคอลเลกชัน เช่น@Html.EditorFor(model => Model.ToGroups[i].Id)ตามด้วย@Html.EditorFor(model => Model.ToGroups[i].Description)ในครั้งต่อไป - ทั้งใน for-loop และคอนโทรลเลอร์ก็สามารถแมปกับรายการโมเดลที่มีฟิลด์เหล่านั้นได้ และเพื่อให้แน่ใจว่าไม่มีสิ่งใดปรากฏบนหน้าจอเพียงแค่ล้อมรอบไว้<div style="display: none;"></div>
Don Cheadle

ยอดเยี่ยม! ทำได้ดีมาก ทำงานให้ฉัน!
AxleWack

3
@ user3186023 ตอบความคิดเห็นเก่า ๆ ที่นี่ แต่คนอื่นอาจจะมีปัญหาเดียวกัน: เปลี่ยนfor-loop เป็น:for(int i = 0; i < Model.Departments.Count(); i++)
Stian

28

HiddenFor ไม่เหมือน DisplayFor หรือ EditorFor จะใช้ไม่ได้กับคอลเล็กชันเพียงค่าเดียว

คุณสามารถใช้ตัวช่วย Serialize HTML ที่มีอยู่ในโครงการ MVC Futures เพื่อทำให้วัตถุเป็นอนุกรมไปยังช่องที่ซ่อนอยู่หรือคุณจะต้องเขียนโค้ดด้วยตัวเอง ทางออกที่ดีกว่าคือเพียงแค่ทำให้ ID ของการเรียงลำดับบางอย่างเป็นอนุกรมและรับข้อมูลอีกครั้งจากฐานข้อมูลในภายหลัง


คุณมีตัวอย่างหรือไม่? ฉันลองสิ่งนี้แล้วและไม่สามารถผูกกับค่า ViewModel ได้เมื่อส่งแบบฟอร์ม
Alan Macdonald

@AlanMacdonald - หากมีบางอย่างล้มเหลวในการผูกอาจเป็นเพราะการตั้งชื่อของคุณไม่ถูกต้องเป็นไปได้มากกว่าเพราะคุณใช้ foreach แทนที่จะใช้กับตัวสร้างดัชนี หรือบางทีคุณอาจไม่ได้ใช้คุณสมบัติที่เหมาะสมในการเชื่อมโยง ดูweblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch

ขอบคุณ. ที่จริงแล้วเมื่อฉันลองใช้มันคือ @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs) โดยที่ Model คือ ViewModel ของฉันและมีคุณสมบัติ ModelIDs int array จึงไม่มีลูปหรืออะไรเลย เมื่อส่งแบบฟอร์ม ModelIDs จะเป็นค่าว่างเสมอใน ViewModel ที่ถูกผูกไว้
Alan Macdonald

@AlanMacdonald - คุณไม่ได้ใส่ "Model" ไว้ในชื่อ
Erik Funkenbusch

16

มันเป็นการแฮ็กเล็กน้อย แต่ถ้า@Html.EditorForหรือใช้@Html.DisplayForงานได้กับรายการของคุณหากคุณต้องการตรวจสอบให้แน่ใจว่ามีการส่งคำขอโพสต์ แต่ไม่สามารถมองเห็นได้คุณสามารถจัดสไตล์ให้ใช้display: none;เพื่อซ่อนมันแทนเช่น:

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

สิ่งนี้ไม่ได้บันทึกค่าในโมเดลเมื่อโพสต์คำขอ
nldev

หากตั้งค่า. EditorFor ให้ทำงานอย่างถูกต้องฉันก็เชื่อเช่นนี้
Mark Rhodes

10

สิ่งที่เกี่ยวกับการใช้ Newtonsoft เพื่อแยกวัตถุลงในสตริง json จากนั้นแทรกลงในช่องที่ซ่อนอยู่เช่น ( Model.DataResponse.Entity.Commissionคือรายการของวัตถุ"CommissionRange" ที่เรียบง่ายตามที่คุณเห็นใน 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}]">

ในกรณีของฉันฉันทำบางสิ่ง JS เพื่อแก้ไข json ในช่องที่ซ่อนไว้ก่อนที่จะโพสต์กลับ

ในคอนโทรลเลอร์ของฉันฉันใช้ Newtonsoft อีกครั้งเพื่อยกเลิกการเชื่อมต่อ:

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

สิ่งนี้ได้ผลสำหรับฉัน ฉันคิดว่ามันเป็นวิธีที่สะอาดกว่ามาก

6

Html.HiddenForออกแบบมาสำหรับค่าเดียวเท่านั้น คุณจะต้องทำให้รายการของคุณเป็นลำดับก่อนที่จะสร้างฟิลด์ที่ซ่อนอยู่

ตัวอย่างเช่นหากรายการของคุณเป็นสตริงประเภทคุณสามารถรวมรายการเป็นรายการที่คั่นด้วยจุลภาคจากนั้นแยกรายการหลังจากโพสต์กลับในคอนโทรลเลอร์ของคุณ


4

ฉันเพิ่งค้นพบ (หลังจากสองสามชั่วโมงในการพยายามหาสาเหตุว่าทำไมค่าโมเดลจึงไม่กลับไปที่คอนโทรลเลอร์) ที่ซ่อนไว้ควรเป็นไปตาม EditorFor

เว้นแต่ฉันจะทำสิ่งอื่นผิดนี่คือสิ่งที่ฉันพบ ฉันจะไม่ทำผิดอีก

ในบริบทของ Model ที่มีรายการของคลาสอื่น

สิ่งนี้จะไม่ทำงาน:

        @{
            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>
            }

3

ฉันเริ่มขุดดูซอร์สโค้ดHiddenForและฉันคิดว่าสิ่งกีดขวางที่คุณเห็นคือวัตถุที่ซับซ้อนของคุณMyListไม่สามารถแปลงเป็นประเภทได้โดยปริยายstringดังนั้นเฟรมเวิร์กจึงถือว่าModelค่าของคุณเป็นnullและแสดงvalueแอตทริบิวต์ว่างเปล่า



3

ประสบปัญหาเดียวกัน หากไม่มีการวนซ้ำจะโพสต์เฉพาะองค์ประกอบแรกของรายการ หลังจากวนซ้ำไปซ้ำมามันสามารถเก็บรายชื่อทั้งหมดและโพสต์ได้สำเร็จ

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

2

อีกทางเลือกหนึ่งคือ:

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

นี่เป็นความคิดแรกของฉันด้วย แต่ฉันมีโมเดลมุมมองซึ่งคาดว่า int [] สำหรับฟิลด์ MyList และสตริงที่คั่นด้วยเครื่องหมายจุลภาคจะไม่ถูกแยกวิเคราะห์ลงในอาร์เรย์โดยกลไกการผูก MVC
ธ เดชมาลี


1

อีกวิธีหนึ่งที่เป็นไปได้ในการแก้ไขปัญหานี้คือให้แต่ละอ็อบเจ็กต์ใน List ของคุณมี ID จากนั้นใช้@Html.DropDownListFor(model => model.IDs)และเติมข้อมูลอาร์เรย์ที่เก็บ ID ไว้


1

อาจจะช้า แต่ฉันสร้างวิธีการขยายสำหรับฟิลด์ที่ซ่อนจากคอลเล็กชัน (ด้วยรายการประเภทข้อมูลอย่างง่าย):

นี่คือ:

/// <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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.