@ Html.HiddenFor не работает со списками в ASP.NET MVC

97

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

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

для выполнения этой функции, но по какой-то причине список в POST всегда равен нулю.

Очень простой вопрос, кто-нибудь знает, почему MVC так себя ведет?

Антон Смит
источник
1
Обычно вы не скрываете так целые списки. Каков ваш желаемый результат с точки зрения <input />s?
Cᴏʀʏ
1
что MyListсодержит? HiddenForиспользуется только для одного входа за раз.
Дэниел А. Уайт
1
Какой тип Model.MyList? Возможно, вам потребуется выполнить некоторую сериализацию / десериализацию в вашем списке вручную.
Кайл Трауберман
1
[ stackoverflow.com/questions/4381871/… Аналогичный вопрос.
Сандживи Субрамани
1
Похожий вопрос: Использование HiddenFor с intellisense
Сандживи Субрамани

Ответы:

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-loop на это:for(int i = 0; i < Model.Departments.Count(); i++)
Stian
28

HiddenFor не похож на DisplayFor или EditorFor. Он не будет работать с коллекциями, только с отдельными значениями.

Вы можете использовать помощник Serialize HTML, доступный в проекте MVC Futures, для сериализации объекта в скрытое поле, или вам придется написать код самостоятельно. Лучшее решение - просто сериализовать какой-либо идентификатор и повторно получить данные из базы данных при обратной передаче.

Эрик Функенбуш
источник
У вас есть пример? Я пробовал это, и мне не удалось привязать к значению ViewModel при отправке формы.
Алан Макдональд,
@AlanMacdonald - если что-то не удается привязать, это потому, что ваше имя неверно, более чем вероятно из-за того, что вы использовали foreach вместо for с индексатором. Или, может быть, вы не использовали правильные атрибуты в привязке. См. Weblogs.asp.net/shijuvarghese/archive/2010/03/06/…
Эрик Фанкенбуш
Спасибо. На самом деле, когда я пробовал, это было буквально @ Html.Serialize ("Model.ModelIDs", Model.ModelIDs), где Model была моей ViewModel, и у нее было свойство массива ModelIDs int. Так что не было никаких петель или чего-то подобного. Когда форма была отправлена, ModelID всегда были пустыми в привязанной ViewModel.
Алан Макдональд
@AlanMacdonald - Вы не включаете «Модель» в название.
Эрик Фанкенбуш
16

Это немного рубить, но если @Html.EditorForи @Html.DisplayForработают для вашего списка, если вы хотите , чтобы убедиться , что он послал на запрос пост , но не видно, вы могли бы просто стиль его использования , display: none;чтобы скрыть это вместо того, чтобы , например:

<div style="display: none;">@Html.EditorFor(model => model.MyList)</div>
Марк Роудс
источник
Это не сохраняет значение в модели после отправки запроса.
nldev 07
Если .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-
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>
            }
AntDC
источник
3

Я начал копаться в исходном коде HiddenForи думаю, что препятствие, которое вы видите, состоит в том, что ваш сложный объект MyListне может быть неявно преобразован в тип string, поэтому фреймворк обрабатывает ваше Modelзначение как nullи отображает valueатрибут пустым.

Cᴏʀʏ
источник
3

Вы можете взглянуть на это решение .

Поместите только HiddenFor в EditorTemplate.

И в вашем представлении поместите это: @Html.EditorFor(model => model.MyList)

Должно работать.

Уилфарт Бенджамин
источник
3

Столкнулся с той же проблемой. Без цикла for он отправил только первый элемент списка. После прохождения цикла for он может сохранить полный список и успешно публиковать сообщения.

 @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)и заполнить массив, содержащий идентификаторы.

Deckeresq
источник
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)
Gh61
источник