ViewModel没有在POST方法中传递回控制器

时间:2013-11-19 05:12:34

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

简单的问题,但我无法弄清楚缺少什么。我有一个简单的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将如下所示:

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

翻译中有什么损失?如何将List返回控制器?

修改

基于两个答案,因为我需要澄清这个问题:在httppost方法中,我应该接收的列表是空的。

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

5 个答案:

答案 0 :(得分:7)

我已根据您的代码成功运行了这项工作。您的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所有字段包含在内,因为所有其他字段都为null / blank / default / empty。这些天不需要为MVC模型绑定初始化集合,它们将自动创建如果其中包含某些内容(即使用EditorForHiddenFor)。 DisplayFor 不会导致值往返;如果没有对象的值进行旅行,则该对象不会进入该集合;如果一个集合中没有任何项目进行往返,除非你强制收集空集,否则该集合不会在回发中出现。

语法也很重要:所有索引(foo[i].bar[j].baz)都必须在里面你提供的lambda(,即在=>的右侧在结束括号之前)。如:

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

要实现这一点,您必须从ICollection<T>Collection<T>更改为IList<T>List<T>,以便您可以为徽章编制索引。< / p>

更长的解释

正如其他评论者提到的那样,除非你编写自己的模型绑定器,否则从HttpGet方法,通过其视图形式到HttpPost方法的往返数据的唯一数据是:

  • 明确包含在表单字段中(因此HTML <input ...>标记是可见还是隐藏),
  • MVC标准类DefaultModelBinder可理解,这意味着input的{​​{1}}属性必须采用特定格式。

这意味着您必须坚持使用name可以使用的类型,并注意DefaultModelBinderEditorFor等,尤其是对于集合的集合。

因此,如果您希望任何TigerTrailViewModel的TigerTrail成员完成此任务,则需要为每个成员指定HiddenForEditorFor。您不需要对象本身的隐藏字段(例如HiddenFor。您确实需要在其他方面要查看其成员的字段。如果您想要TigerTrails和选择性徽章列表跨越,然后对于您想要查看的徽章的每个字段,您必须使用上述语法确保这些字段为@Html.HiddenFor(x => x.TigerTrail)EditorFor

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

为什么你必须将所有内容都放在lambda中(TextBoxFor的右边)?这归结为MVC使用表达式树,除非你对此感兴趣,否则可能不值得担心,但是:你在调用中提供的lambda =>或类似的变为表达式树而不是委托,MVC在运行时解析这个编译器生成的数据结构,以计算如何为它创建的输入写出EditorFor属性,它可以在以后回读的方式。

如果您使用name,请执行以下操作:

EditorFor

然后MVC将创建输入la:

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

该名称语法是MVC&#39; <input class="check-box" name="[6].TigerTrail.TigerTrailElectivedBadges[28].Done" type="checkbox" value="true" /> 所需的语法。

顺便提一下,如果你真的想要,你可以用这些名称格式手动生成输入标签,而不是使用DefaultModelBinder等。这对于用户将项目添加到集合的应用程序非常有用,如果您有JavaScript,则可以动态添加相关的表单字段。如果名称格式正确,MVC将在回发时找到它们。

建议(FWIW,可能不多)

MVC不像Web窗体,因此没有视图状态会自动保存页面上的每个项目以供日后使用。这是一件好事,因为它可以加速您的应用程序,从而加快加载速度并减少不必要的互联网流量。一般来说,良好的做法是仅包含用户可能更改的数据,或者只需了解用户提供的其他值(例如,唯一的记录ID,这将有助于您在用户正在处理的数据库。)

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

因此,如果你的嵌套馆藏不能在回程中重新开始,那么除非用户能够编辑这些数据,否则它并不重要;因为如果你真的需要它,你可以再次将它从数据库中拉出来。

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

答案 1 :(得分:0)

看起来您需要添加表单字段以绑定到Pack属性。否则将没有任何内容回发给控制器。 http是无状态的,因此原始模型对象不会被保留,因此必须从表单字段

重建

答案 2 :(得分:0)

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

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

如果您还有其他需要,请告诉我。

答案 3 :(得分:0)

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

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

return View(youths);

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

答案 4 :(得分:0)

你可以改变一些事情:

1)在ViewModel中,最好在构造函数中初始化复杂属性。收藏品特别适用。不知何故,模型绑定器似乎无法做到这一点。这以Null属性结束。 我认为这不是你的情况,因为你的整个VM列表都是空的,但是一旦你解决了问题,就会出现这个问题。

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

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

请记住:仅发布输入中的信息,因此...如果您需要发布的内容而不是,请检查您是否至少将其隐藏起来。要检查的另一件事是,您的索引([i])是否正确。

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