ViewModel не передается обратно в контроллер в методе POST

Простая проблема, но я не могу понять, чего не хватает. У меня простая ViewModel (она станет больше):

public class TigerTrackingViewModel
{
    public TigerTrackingViewModel()
    {
         this.TigerTrail = new TigerTrail();
    }
    public Guid YouthGuid { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public TigerTrail TigerTrail { get; set; }
}

TigerTrail - это вложенный объект. Вот все свойства и подсвойства:

public class TigerTrail
{
    public TigerTrail()
    {
        DoneDate = new DateTime(1950, 01, 01);
        TigerTrailRequiredBadges = new Collection<TigerTrailRequiredBadge>();
        TigerTrailElectivedBadges = new Collection<TigerTrailElectiveBadge>();
    }
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<TigerTrailRequiredBadge> TigerTrailRequiredBadges { get; set; }
    public virtual ICollection<TigerTrailElectiveBadge> TigerTrailElectivedBadges { get; set; }
    //public virtual ICollection<Youth> Youth { get; set; }
    public bool? Done { get; set; }
    public DateTime? DoneDate { get; set; }
}

Итак, у него есть TigerTrailRequiredBadges:

public class TigerTrailRequiredBadge
{
    public TigerTrailRequiredBadge()
    {
        DoneDate = new DateTime(1950, 01, 01);
        TigerTrailRequiredBadgeSubRequirements = new Collection<TigerTrailRequiredBadgeSubRequirement>();
    }
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<TigerTrailRequiredBadgeSubRequirement> TigerTrailRequiredBadgeSubRequirements { get; set; }
    public bool Done { get; set; }
    public DateTime DoneDate { get; set; }
}

И там есть TigerTrailRequiredBadgeSubRequirement (s):

public class TigerTrailRequiredBadgeSubRequirement
{
    public TigerTrailRequiredBadgeSubRequirement()
    {
        DoneDate = new DateTime(1950, 01, 01);
    }
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public string ShortCode { get; set; }
    public string Type { get; set; } //Family, Den, Go See It
    public string Description { get; set; }
    public bool Done { get; set; }
    public DateTime DoneDate { get; set; }
}

Еще в классе TigerTrail.cs был также класс Elective Badge:

public class TigerTrailElectiveBadge
{
    public TigerTrailElectiveBadge()
    {
        DoneDate = new DateTime(1950, 01, 01);
    }
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public int Number { get; set; }
    public string Name { get; set; }
    public string Requirement { get; set; }
    public bool Done { get; set; }
    public DateTime DoneDate { get; set; }
}

Итак, есть ВСЕ свойства, которые будут доступны через мою ViewModel. К сожалению, есть по большей части все необходимое. Он большой и уродливый, но я должен заставить его работать.

В методе GET контроллера:

public ActionResult TigerTrail()
    {
        var vm = new List<TigerTrackingViewModel>();
        var pack = Ctx.CubPacks.FirstOrDefault(x => x.Id == PackId);
        var permTrail = Ctx.TigerTrails.FirstOrDefault(x => x.Name.Contains("PERM"));
        foreach (var youth in pack.Youths)
        {
            //if anyone does not have this trail set up, make a new one.
            if (youth.TigerTrail == null)
            {
                youth.TigerTrail = new TigerTrail();
                if (youth.TigerTrail.TigerTrailElectivedBadges == null)
                {
                    youth.TigerTrail.TigerTrailElectivedBadges = new Collection<TigerTrailElectiveBadge>();
                }
                if (youth.TigerTrail.TigerTrailRequiredBadges == null)
                {
                    youth.TigerTrail.TigerTrailRequiredBadges = new Collection<TigerTrailRequiredBadge>();
                }
                youth.TigerTrail = permTrail;
            }

            youth.TigerTrail.Name = youth.FirstName + " " + youth.LastName + " Tiger Trail";
            vm.Add(new TigerTrackingViewModel
            {
                FirstName = youth.FirstName,
                LastName = youth.LastName,
                YouthGuid = youth.YouthGuid,
                TigerTrail = youth.TigerTrail
            });
        }
        return View(vm);
    }

в почтовом методе:

[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths)
{
    return View();
}

Постбек возвращается нулевым каждый раз. Вот вид:

@model List<eTrail.Cubs.ViewModels.TigerTrackingViewModel>
@{
    ViewBag.Title = "Award Tracking";
    Layout = "~/Areas/App/Views/Shared/_BackendDashboard.cshtml";
}
<h1>Award Tracking</h1>
<hr />
<div class="row">
    <div class="span12">
        @using (Html.BeginForm("TigerTrail", "Awards", FormMethod.Post, new { @class = "form-horizontal" }))
        {
            for (int i = 0; i < Model.Count; i++)
        {
        @Html.HiddenFor(x => Model[i].TigerTrail)

            foreach (var item in Model[i].TigerTrail.TigerTrailElectivedBadges)
            {
        @Html.DisplayFor(x => item.Done)
        @Html.DisplayFor(x => item.DoneDate)
        @Html.DisplayFor(x => item.Id)
        @Html.DisplayFor(x => item.Name)
        @Html.DisplayFor(x => item.Number)
        @Html.DisplayFor(x => item.Requirement)
            }

        }      
        <input type="submit" value="submit" />
        }
</div>
</div>

Я добавил все поля HiddenFor повсюду, поскольку было высказано предположение, что все они должны быть там, чтобы его можно было отправить обратно. По-прежнему не повезло. Если я просматриваю исходный код на странице, идентификаторы / имена появляются следующим образом:

<li>
    <input id="elec_Done" name="elec.Done" type="checkbox" value="true"><input name="elec.Done" type="hidden" value="false">
    <b>Pet Care</b>
    Visit a veterinarian or animal groomer
    <input id="elec_Number" name="elec.Number" type="hidden" value="43">
    <input id="elec_DoneDate" name="elec.DoneDate" type="hidden" value="1/1/1950 12:00:00 AM">
</li>

Что теряется при переводе? Как я могу вернуть список контроллеру?

ИЗМЕНИТЬ

Основываясь на двух ответах с момента награждения, мне нужно уточнить это: в методе httppost список, который я должен получать, будет иметь значение null.

[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths) //this is what is null on postback.
{
    . . . Do work with youths . . 
    return RedirectToAction(...);
}

person ledgeJumper    schedule 19.11.2013    source источник
comment
Все свойства вашего Пакета должны быть внутри формы .. Скрыто или иначе.   -  person Simon Whitehead    schedule 19.11.2013
comment
@SimonWhitehead Итак, что бы вы сказали, здесь есть хорошая стратегия. Модель Pack довольно большая, и я использую отрывки от нее, но не все целиком. Итак, а) создать другую виртуальную машину, в которой есть только эти элементы, или б) создать кучу скрытых полей в форме?   -  person ledgeJumper    schedule 19.11.2013
comment
Создайте модель просмотра, содержащую только нужные вам поля. Затем при публикации загрузите текущее состояние из своей БД и примените к нему изменения из вашей сокращенной модели представления.   -  person Simon Whitehead    schedule 19.11.2013
comment
Если вы пытаетесь получить все данные, которые вы передаете обратно, тогда все они должны быть скрыты для. Отображение полей, которые вы показываете, не передается (все остальные для помощников будут). Вы не можете поместить целые классы в for. Если вы хотите, чтобы tigertails отправлялись обратно, вам нужно будет поместить каждое свойство в файл for. Может быть проще просто отправить обратно идентификатор и запрос   -  person Matt Bodily    schedule 26.11.2013


Ответы (5)


У меня это успешно работает на основе вашего кода. Исправление для представления вашего метода HttpGet имеет следующий вид:

@using (Html.BeginForm("TigerTrail", "Awards", FormMethod.Post, new { @class = "form-horizontal" }))
{
    for (int i = 0; i < Model.Count; i++)
    {
        @Html.EditorFor(x => x[i].FirstName)

        for (int j = 0; j < Model[i].TigerTrail.TigerTrailElectivedBadges.Count; ++j)
        {
            @Html.EditorFor(x => x[i].TigerTrail.TigerTrailElectivedBadges[j].Name)
        }
    }      
    <input type="submit" value="submit" />
}

Затем вы можете удалить большую часть добавленной вами инициализации коллекции. (Кстати, там есть ошибка - строка youth.TigerTrail = permTrail; полностью заменяет предыдущий пустой, но инициализированный объект.)

Краткое объяснение

Уловка состоит в том, чтобы включить все поля, которые вы хотите использовать, используя EditorFor или HiddenFor, так как все остальные будут пустыми / пустыми / по умолчанию / пустыми. В наши дни коллекции не нужно инициализировать для привязки к модели MVC, они будут созданы автоматически, если в них есть что-то (например, что-то, использующее EditorFor или HiddenFor). DisplayFor не приведет к возврату значения в оба конца; и если никакие значения объекта не совершают поездку, объект не будет в коллекции; и если никакие элементы в коллекции не совершают обратный путь, коллекция также не будет там при обратной передаче, если вы не принудительно создадите пустую коллекцию.

Синтаксис также имеет решающее значение: вся индексация (foo[i].bar[j].baz) должна быть внутри заданной вами лямбды (т. Е. Справа от => перед закрывающей скобкой). Как в:

@Html.EditorFor(x => x[i].TigerTrail.TigerTrailElectivedBadges[j].Name)
                  //  ^^^                                     ^^^

Чтобы это работало, вам нужно будет изменить с ICollection<T> и Collection<T> на IList<T> и List<T>, чтобы вы могли индексировать значки.

Более подробное объяснение

Как упоминали другие комментаторы, если вы не напишете свое собственное связующее устройство модели, единственными данными, которые будут выполнять обратный путь от вашего метода HttpGet через его форму представления до метода HttpPost, являются данные, которые:

  • Явно содержится в полях формы (поэтому HTML <input ...> теги, видимые или скрытые), и
  • Понятен стандартным классом MVC DefaultModelBinder, что означает, что атрибут input name должен иметь определенный формат.

Это означает, что вы должны придерживаться типов, с которыми DefaultModelBinder может работать, и будьте осторожны с EditorFor, HiddenFor и т.п., особенно с коллекциями коллекций.

Поэтому, если вы хотите, чтобы какой-либо из членов TigerTrail вашего TigerTrailViewModel прошел через него, вам необходимо указать EditorFor или HiddenFor для каждого из этих членов. Вам не нужно скрытое поле для самого объекта (например, @Html.HiddenFor(x => x.TigerTrail). Вам нужны поля для тех его членов, которые вы хотите видеть на другой стороне. Если вы хотите, чтобы список факультативных значков TigerTrails пересек его, то для каждое поле значков, которое вы хотите видеть, вы должны убедиться, что эти поля равны EditorFor или HiddenFor, используя указанный выше синтаксис.

В качестве сноски, TextBoxFor или любой другой метод xxxxFor также будет работать нормально, если типы совпадают.

Почему вы должны помещать все в лямбду (справа от =>)? Это связано с использованием в MVC деревьев выражений, о которых, вероятно, не стоит беспокоиться, если вы сильно не заинтересованы, но: лямбда, которую вы предоставляете внутри вызова EditorFor или аналогичного, превращается в дерево выражений, а чем делегат, и MVC анализирует эту сгенерированную компилятором структуру данных во время выполнения, чтобы решить, как записать name атрибутов для входных данных, которые он создает, таким образом, чтобы он мог прочитать их позже.

Если вы используете EditorFor, как в:

@Html.EditorFor(x => x[i].TigerTrail.TigerTrailElectivedBadges[j].Done)

Затем MVC создаст входы а-ля:

<input class="check-box" name="[6].TigerTrail.TigerTrailElectivedBadges[28].Done" type="checkbox" value="true" />

И этот синтаксис имени - тот, который требуется MVC DefaultModelBinder.

Кстати, вы можете вручную сгенерировать входные теги с этими форматами имен, а не использовать EditorFor и т. Д., Если действительно хотите. Это полезно для приложений, в которых пользователь добавляет элементы в коллекцию, если у вас есть JavaScript, который добавляет соответствующие поля формы на лету, когда они это делают. Если формат имени правильный, MVC найдет их при обратной передаче.

Предложения (FWIW, которых может быть немного)

MVC не похож на веб-формы, поэтому нет состояния просмотра, автоматически сохраняющего каждый отдельный элемент на странице для дальнейшего использования. Это хорошо, так как ускоряет работу вашего приложения, сокращает время загрузки и снижает ненужный интернет-трафик. Как правило, рекомендуется включать только данные, которые пользователь может изменить или которые вам нужны, чтобы просто понять другие значения, предоставленные пользователем (например, уникальные идентификаторы записей, которые помогут вам найти в базе данных что-то, что пользователь работает).

Если вы хотите отображать или использовать другие данные при обратной публикации, как правило, лучше найти их снова (на основе идентификаторов и т. Д., Которые либо предоставил пользователь, либо которые вы включили в скрытые поля), чем передавать их в оба конца без всякой необходимости. .

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

Для некоторых приложений (не самых распространенных в приложениях MVC) вполне вероятно, что вы можете захотеть передать все состояние объекта (например, если у вас нет возможности изменить многопользовательскую недружелюбную схему для таблицы базы данных. которые могут редактировать несколько пользователей, но вы хотите, чтобы они не перезаписывали работу друг друга, сохраняя все значения, проверяя различия и отклоняя изменения, если они конфликтуют). В таком случае вам нужно будет передать все данные в оба конца, и вам нужно будет сделать это с помощью HiddenFor и правильного синтаксиса или другого механизма, изобретенного вами.

person El Zorko    schedule 27.11.2013
comment
Думаю, я мог бы полюбить тебя ... большое спасибо за подробное объяснение! И, конечно же, рабочий код. - person ledgeJumper; 27.11.2013
comment
Добро пожаловать :) Когда вы впервые сталкиваетесь с этим, в MVC есть удивительное количество явной черной магии, я сам прошел через ту же кривую обучения. Как ни странно, я думаю, что сначала легче понять работу Linq (особенно, его использование деревьев выражений), а затем легче понять обруч, через который перескакивает MVC; но на это уходит много времени, если вам не нужен Linq, поэтому, вероятно, не рекомендуется. - person El Zorko; 28.11.2013
comment
Ты действительно спас мне жизнь. Да благословит тебя Бог, дорогой. - person Haider Ali Wajihi; 25.08.2016

Похоже, вам нужно добавить поля формы для привязки к свойствам Pack. В противном случае не будет ничего, что можно было бы отправить обратно контроллеру. http не имеет состояния, поэтому исходный объект модели не сохраняется, поэтому его нужно восстанавливать из полей формы

person TGH    schedule 19.11.2013
comment
Итак, если я не взаимодействую напрямую с классом Pack, только с его дочерними элементами, будет ли этого достаточно? @ Html.HiddenFor (x = ›x.Pack) - person ledgeJumper; 19.11.2013
comment
Да, скрытое поле - это вариант - person TGH; 19.11.2013
comment
похоже, что это не так. Я изменил модель просмотра, чтобы она была немного проще, так что сейчас это объекты List ‹Youth›. Я добавил @ Html.HiddenFor (x = ›x.Youths) и, изучив форму, я вижу следующее:‹ input id = Youths name = Youths type = hidden value = System.Collections.Generic.List`1 [eTrail.Models. Global.Youth] › - person ledgeJumper; 19.11.2013
comment
Да, прости. Pack - сложный объект, поэтому вам нужно скрытое поле для каждого свойства на нем. - person TGH; 19.11.2013

Ответ, что вам нужно, прост, и вам не нужно использовать скрытое поле или что-то для этого.

public ActionResult TigerTrail()
{
    var vm = new List<TigerTrackingViewModel>();
    //other codes
    return View(vm) //Here is the important for you. Returning something inside View()is to show the Model and data.
}

Другой ваш View, который использует HttpPost, ничего не возвращает внутри View ();

[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths)
{
    return View(); //Here is where you did not give any data/model to the View. So, if you don't return something inside View(), you can not present any data or can not use model in the View.
}

Все, что вам нужно сделать, это:

[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths)
{
    return View(youths); //It will work. 
}

Позвольте мне знать, если вам нужно что-нибудь еще.

person mcansozeri    schedule 25.11.2013
comment
Я обновил конец своего вопроса, чтобы прояснить это. Меня не беспокоит возврат View (); возникает ошибка, что имеет смысл, поскольку я не даю представлению модель представления для работы. Проблема заключается в том, что объекты, которые я передаю («молодые люди») этому методу публикации, равны нулю. - person ledgeJumper; 25.11.2013

[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths)
{
    return View();
}

Я предполагаю, что ваш список не нулевой, поэтому вы просто забываете передать свою модель представлению. Нравится:

return View(youths);

Если вам нужно много чего написать в своем скрытом поле, подумайте о том, чтобы каждый раз получать свою модель из базы данных и использовать только идентификатор скрытого поля.

person Sir l33tname    schedule 25.11.2013
comment
Я обновил конец своего вопроса, чтобы прояснить это. Меня не беспокоит возврат View (); возникает ошибка, что имеет смысл, поскольку я не даю представлению модель представления для работы. Проблема заключается в том, что объекты, которые я передаю («молодые люди») этому методу публикации, равны нулю. - person ledgeJumper; 25.11.2013

Вы можете изменить некоторые вещи:

1) В вашей ViewModel удобно инициализировать ваши сложные свойства в конструкторе. Особенно это касается коллекций. Как-то кажется, что переплетчик не может этого сделать. И это заканчивается свойством Null. Я думаю, что это НЕ ваш случай, поскольку весь ваш список виртуальных машин пуст, но как только вы решите проблему, эта проблема может появиться.

public class TigerTrackingViewModel
{
    public TigerTrackingViewModel(){
       this.TigerTrail = new TigerTrail(); //Add this
    }

    public Guid YouthGuid { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public TigerTrail TigerTrail { get; set; }
}

Это нужно сделать для всех ваших сложных свойств.

2) При отображении коллекции элементов необходимо отобразить ее с помощью индекса, а для этого изменить foreach на нормальный для:

@for (int i=0; i < Model.Count)
{
    @Html.HiddenFor(x => Model[i].TigerTrail)
    ...
    ...
}

В вашем случае, учитывая большое количество отображаемых данных, вы должны удалить весь код представления для обработки внутренних моделей представления ... и попытаться получить список размещенных виртуальных машин. Как только у вас это получится, начните добавлять другие свойства.

Помните: публикуется только информация, которая находится во входных данных, поэтому ... если вам нужно что-то опубликовать, а этого нет, проверьте, есть ли у вас это хотя бы как скрытое. Еще нужно проверить, неверен ли ваш индекс ([i]).

Здесь вы можете увидеть отличную статью о привязке к спискам: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx (он немного устарел, но основы все еще применимый )

person Romias    schedule 25.11.2013
comment
Спасибо за ответ. Похоже, я подхожу ближе. Возвращенный список в POST больше не является нулевым. У меня есть список из 5 моделей просмотра, но все вложенные свойства по-прежнему равны нулю, особенно объект TigerTrail. Может быть, я не уверен, как углубиться в цикл for, или мне нужно еще что-то сделать? - person ledgeJumper; 25.11.2013
comment
Я тоже это сделал, см. Обновленный код. Я вошел в модель просмотра и инициализировал все свои коллекции в конструкторе каждого класса. По-прежнему нет игральных костей. :( - person ledgeJumper; 25.11.2013