【问题标题】:ViewModel not being passed back to controller in POST methodViewModel 未在 POST 方法中传递回控制器
【发布时间】:2013-11-19 05:12:33
【问题描述】:

简单的问题,但我无法弄清楚缺少什么。我有一个简单的 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 使用。不幸的是,大多数情况下都需要。它又大又丑,但我必须让它发挥作用。

在Controller 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);
    }

在post方法中:

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

回发每次都返回 null。这是视图:

@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 字段,因为有人建议它们都需要在那里才能回发。仍然没有运气。如果我在页面上查看源代码,则 id/name(s) 会像这样显示:

<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 方法中,我应该收到的列表为空。

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

【问题讨论】:

  • 您的所有 Pack 属性都必须在表单内。隐藏或其他方式。
  • @SimonWhitehead 那么你会说什么是好的策略。包装模型相当大,我正在使用它的零碎部分,但不是全部。那么,a)创建一个只有这些项目的不同虚拟机,或者 b)在表单中抛出一堆隐藏字段?
  • 创建一个仅包含您需要的字段的视图模型。然后在发布时,从您的数据库中加载当前状态,并从您的缩减视图模型中应用您的更改。
  • 如果您试图获取所有要传回的数据,那么它们都需要隐藏起来。您显示的字段的显示不会传回(所有其他帮助者都会)。您不能将整个类放入 for 中。如果您希望tigertails 回发,您需要将每个属性放入for 中。发回 id 并重新查询可能更容易

标签: c# asp.net-mvc asp.net-mvc-4 asp.net-mvc-viewmodel


【解决方案1】:

根据您的代码,我已经成功完成了这项工作。对 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; 行完全替换了前面的空但已初始化的对象。)

简短说明

诀窍是使用EditorForHiddenFor 包含所有您想要的字段,因为所有其他字段将为空/空白/默认/空。这些天来,集合不需要为 MVC 模型绑定初始化,它们会自动创建如果其中有一些内容(即使用 EditorForHiddenFor 的东西)。 DisplayFor 不会导致值进行往返;并且如果没有对象的值发生行程,则该对象将不在集合中;如果集合中没有项目进行往返,则除非您强制使用空集合,否则该集合也不会在回发时出现。

语法也很关键:所有索引 (foo[i].bar[j].baz) 必须在您提供的 lambda 即在右括号之前的 =&gt; 右侧)。如:

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

要使其正常工作,您必须将 ICollection&lt;T&gt;Collection&lt;T&gt; 更改为 IList&lt;T&gt;List&lt;T&gt;,以便您可以索引徽章。

更长的解释

正如其他评论者所提到的,除非您编写自己的模型绑定器,否则唯一可以从您的 HttpGet 方法、通过其视图的表单到 HttpPost 方法往返的数据是:

  • 显式包含在表单字段中(因此 HTML &lt;input ...&gt; 标记可见或隐藏),
  • MVC 标准类DefaultModelBinder 可以理解,这意味着inputname 属性必须采用特定格式。

这意味着您必须坚持使用 DefaultModelBinder 可以使用的类型,并小心使用 EditorForHiddenFor 等,尤其是对于集合的集合。

因此,如果您希望您的 TigerTrailViewModel 的任何 TigerTrail 成员都能通过,您需要为每个成员指定 EditorForHiddenFor。您不需要对象本身的隐藏字段(例如@Html.HiddenFor(x =&gt; x.TigerTrail)。您确实需要字段用于您想在另一边看到的成员。如果您希望 TigerTrails 的选修徽章列表能够跨越,那么对于您想要查看的徽章的每个字段,您必须确保这些字段也是 EditorForHiddenFor 以及使用上述语法。

作为脚注,TextBoxFor 或任何其他 xxxxFor 方法也可以正常工作,只要类型匹配。

为什么必须将所有内容都放在 lambda 中(=&gt; 的右侧)?这取决于 MVC 对 表达式树 的使用,除非您非常感兴趣,否则可能不值得担心,但是:您在对 EditorFor 或类似的调用中提供的 lambda 转换为表达式树而不是委托,并且 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 不像 Web 窗体,因此没有视图状态会自动保存页面上的每一项供您以后使用。这是一件好事,因为它可以加快您的应用程序,导致更快的加载时间并减少不必要的互联网流量。通常最好只包含用户可能更改的数据,或者您需要了解用户提供的其他值的数据(例如,唯一的记录 ID,这将帮助您在数据库中找到用户正在处理中)。

如果您想在回发时显示或使用其他数据,通常最好再次查找(基于用户提供或您包含在隐藏字段中的 ID 等)而不是在不必要的往返。

因此,如果您的嵌套集合在往返过程中没有返回,那么除非用户可以编辑该数据,否则这无关紧要;因为如果你真的需要它,你可以再次将它从数据库中拉出来。

对于某些应用程序(不是最常见的 MVC 应用程序),当然可以想象您可能希望传输对象的整个状态(例如,如果您无法更改对多用户不友好的架构多个用户可能正在编辑的数据库表,但您希望通过保存所有值、检查差异并在冲突时拒绝编辑来防止他们覆盖彼此的工作)。在这种情况下,您必须在往返过程中传输所有数据,并且您需要使用 HiddenFor 和正确的语法或您自己设计的其他机制来完成。

【讨论】:

  • 我想我可能会爱你...非常感谢你的详尽解释!当然,还有工作代码。
  • 不客气 :) 当您第一次接触 MVC 时,有大量明显的“黑魔法”,我自己也经历过同样的学习曲线。奇怪的是,我认为首先掌握 Linq 的工作原理更容易(尤其是它对表达式树的使用),然后 MVC 跳过的箍更容易理解;但是如果你不需要 Linq,那会花费很多时间,所以可能不推荐。
  • 你真的救了我的命。亲爱的上帝保佑你。
【解决方案2】:

您似乎需要添加表单字段以绑定到 Pack 属性。否则不会有任何东西可以发回给控制器。 http 是无状态的所以原始模型对象没有被保留,所以它必须从表单字段中重构

【讨论】:

  • 所以,如果我不直接与 Pack 类交互,只是与它的子类交互,这就足够了吗? @Html.HiddenFor(x => x.Pack)
  • 是的,隐藏字段是一个选项
  • 这似乎没有这样做。我将视图模型更改为更简单一点,所以它现在是一个 List 对象。我添加了@Html.HiddenFor(x => x.Youths) 并检查了我看到的表单:
  • 是的,对不起。 Pack 是一个复杂的对象,因此您需要为其上的每个属性设置一个隐藏字段。
【解决方案3】:

您需要的答案很简单,您不需要为此使用隐藏字段或其他东西。

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.
}

您使用 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. 
}

如果您还需要什么,请告诉我。

【讨论】:

  • 我更新了问题的结尾以澄清这一点。我不担心返回 View();出现错误,这是有道理的,因为我没有给视图提供一个可以使用的视图模型。问题是我传递给这个 post 方法的对象('youths')是空的。
【解决方案4】:
[HttpPost]
public ActionResult TigerTrail(List<TigerTrackingViewModel> youths)
{
    return View();
}

我假设您的列表不为空,因此您只是忘记将模型传递给视图。像这样:

return View(youths);

如果您需要在隐藏字段中编写很多内容,请考虑每次从数据库中获取模型并仅使用隐藏字段 ID。

【讨论】:

  • 我更新了问题的结尾以澄清这一点。我不担心返回 View();出现错误,这是有道理的,因为我没有给视图提供一个可以使用的视图模型。问题是我传递给这个 post 方法的对象('youths')是空的。
【解决方案5】:

你可以改变一些东西:

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:

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

在您的情况下,考虑到要显示的大量数据,您应该删除所有视图代码以处理内部视图模型......并尝试是否发布了 VM 列表。完成后,开始添加其他属性。

记住:只有输入中的信息会被发布,所以...如果您需要发布的东西而不是,请检查您是否至少将其隐藏。要检查的另一件事是,您的索引 ([i]) 是否不正确。

在这里你可以看到一篇关于绑定到列表的优秀文章: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx(有点旧,但基础仍然适用)

【讨论】:

  • 感谢您的回复。看来我离这里越来越近了。 POST 中返回的列表不再为空。我有一个包含 5 个视图模型的列表,但所有嵌套属性仍然为空,尤其是 TigerTrail 对象。也许我不确定如何深入到 for 循环中,或者我还需要做什么?
  • 我也这样做了,请参阅更新的代码。我进入视图模型并在每个类的构造函数中初始化了我的所有集合。还是没有骰子。 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
相关资源
最近更新 更多