@ Html.HiddenFor не працює зі списками в ASP.NET MVC


97

Я використовую модель, яка містить список як властивість. Я заповнюю цей список предметами, які я захоплюю з SQL Server. Я хочу, щоб Список був прихований у поданні та переданий до дії POST. Пізніше я, можливо, захочу додати до цього списку більше елементів за допомогою jQuery, що робить масив непридатним для подальшого розширення. Зазвичай ви б використовували

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

для досягнення цієї функціональності, але чомусь список у POST завжди є нульовим.

Дуже просте запитання, хтось знає, чому MVC поводиться так?


1
Зазвичай ви не приховуєте цілих списків таким чином. Який бажаний результат у перерахунку на <input />s?
Cᴏʀʏ

1
що MyListмістить? HiddenForвикористовується лише для одного введення за раз.
Daniel A. White

1
Що це за тип Model.MyList? Можливо, вам доведеться виконати деяку серіалізацію / десеріалізацію у списку вручну.
Кайл Трауберман

1
[ stackoverflow.com/questions/4381871/… Подібне запитання.
Sanjeevi Subramani

Відповіді:


161

Я просто натрапив на це питання і вирішив його просто, зробивши наступне:

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

Використовуючи for замість foreach, прив'язка моделі буде працювати коректно і відбиратиме всі ваші приховані значення у списку. Здається, найпростіший спосіб вирішити цю проблему.


5
Дякую! врятував мою ніч.
TSmith

7
Дякую - приємне просте рішення. Потрібен лише крихітний мод: потрібно вказати поле Id об'єкта. Отже, якщо поле називається RowId, то:@Html.HiddenFor(model => Model.ToGroups[i].RowId)
Кришна Гупта

3
працював у мене, навіть коли у мене було кілька полів на моделях у колекції. Тобто @Html.EditorFor(model => Model.ToGroups[i].Id)слідує @Html.EditorFor(model => Model.ToGroups[i].Description)наступного разу - обидва в циклі for. І контролер зміг зіставити його зі списком моделей із цими полями. І щоб переконатися, що жодне з них не з’явилося на екрані, просто оточіть його<div style="display: none;"></div>
Дон Чедл,

Блискуче! Чудово зроблено. Працювали для мене!
AxleWack

3
@ user3186023 Відповідаючи на справді старий коментар тут, але, можливо, хтось інший матиме таку ж проблему: Змініть forцикл на такий:for(int i = 0; i < Model.Departments.Count(); i++)
Стіан

28

HiddenFor не схожий на DisplayFor або EditorFor. Він не працюватиме з колекціями, лише окремі значення.

Ви можете використовувати помічник Serialize HTML, доступний у проекті MVC Futures, щоб серіалізувати об’єкт у прихованому полі, або вам доведеться написати код самостійно. Краще рішення - просто серіалізувати якийсь ідентифікатор і знову отримати дані з бази даних після зворотного зв'язку.


У вас є приклад? Я спробував це, і не вдалося прив’язати до значення ViewModel під час подання форми.
Алан Макдональд

@AlanMacdonald - якщо щось не вдається прив’язати, це тому, що ваші імена невірні, більш ніж ймовірно, тому що ви використовували foreach замість for з індексатором. Або, можливо, ви не використовували належні атрибути в прив'язці. Дивіться weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Erik Funkenbusch

Дякую. Насправді, коли я спробував, це було буквально @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs), де Model був моїм ViewModel, і він мав властивість intID масиву ModelIDs. Тож не було ніяких петель чи нічого. Коли форма була надіслана, ідентифікатори моделі завжди були нульовими у зв’язаному ViewModel.
Алан Макдональд

@AlanMacdonald - Ви не вказуєте "Модель" у назві.
Ерік Функенбуш

16

Це трохи зламати, але якщо ваш список працює @Html.EditorForабо @Html.DisplayForпрацює, якщо ви хочете переконатись, що його надіслано на запит на публікацію, але він не видно, ви можете просто створити його стилем для використання, display: none;щоб приховати його, наприклад:

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

Це не економить значення в моделі під час публікації запиту.
nldev

Якщо .EditorFor налаштований працювати правильно, то це також має працювати, я вважаю.
Марк Родс

9

А як щодо використання 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);

Це працювало для мене. Я вважав, що набагато чистіше прийняте рішення.
e-on

6

Html.HiddenForпризначений лише для одного значення. Перш ніж створювати приховане поле, вам потрібно буде серіалізувати список.

Наприклад, якщо ваш список має рядковий тип, ви можете приєднати його до списку, розділеного комами, а потім розділити список після публікації назад у контролері.


4

Я щойно з'ясував (через пару годин, намагаючись з'ясувати, чому значення моделей не повертаються до контролера), за якими слід приховувати 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>
            }

3

Я почав копати вихідний код для HiddenFor, і я вважаю, що ви бачите блокнот дорожнього руху, що ваш складний об'єкт MyListневірно конвертований у тип string, тому рамка розглядає ваше Modelзначення як nullі робить valueатрибут порожнім.


3

Ви можете поглянути на це рішення .

Помістіть лише HiddenFor всередину EditorTemplate.

І у своєму поданні поставте це: @Html.EditorFor(model => model.MyList)

Це має спрацювати.


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.
Тадей Малі

2

foreachПетлі замість forпетлі може бути трохи чистіше розчин.

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

1

Іншим можливим способом виправити це буде надання кожному об’єкту у вашому списку ідентифікатора, а потім використання @Html.DropDownListFor(model => model.IDs)та заповнення масиву, що містить ідентифікатори.


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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.