部分视图上的ViewModel丢失回到控制器

时间:2015-04-13 19:18:03

标签: c# asp.net-mvc

我需要将更改保留到包含集合的视图模型,但每次我回发到控制器时,我都会丢失模型绑定。我对MVC很新,所以我可能会遗漏一些明显的东西。

  @{ Html.RenderAction("TabList", "TabController", new {Id = Model.Id}); }

我有一个主容器页面,它对控制器有一个渲染动作,可以返回第一个局部视图。

    [HttpGet]
    public ViewResult TabList(Guid orderid)
    {
        // build the viewmodel

        return View("ControlTabList", model);
    }

从那里迭代集合和基于对象类型的不同渲染部分。 (我在这里简化了代码,因为这些项是多态的,并且有某种类型的向下转换)

@model TabListViewModel

@using (Html.BeginForm("UpdateItem", "TabController", FormMethod.Post, new {Id = "myForm"}))
{
    @Html.AntiForgeryToken()
    <input type="submit" value="Send"  id="submitButton"/>


    @for (int i = 0, c = this.Model.Count; i < c; i++)
    {
        var currentItem = this.Model.ElementAt(i);

        @switch (currentItem.Code)
        {
            case "1":
                Html.RenderPartial("Partials/ItemOne", currentItem); 
                break;
            case "2":
                Html.RenderPartial("Partials/ItemTwo",currentItem); 
                break;
            default:
                Html.RenderPartial("Partials/ItemThree",currentItem); 
                break;

        }
    }
}

当我回发到控制器时,我的ViewModel将始终为null。

    [HttpPost]
    public ActionResult UpdateItems(TabListViewModel model)
    {
       /* i will remove the redirect here, as the model above is always null*
    }

我有没有理由失去绑定?我想保存整个集合,而不是单独保存集合中的每个项目。

3 个答案:

答案 0 :(得分:3)

实施失败有两个原因。

首先是使用partials来显示集合中的每个项目。如果您检查生成的html,您会看到每个id的{​​{1}}和name属性相同。重复的currentItem是无效的html,重复的名称属性意味着您无法绑定回集合。假设id有一个属性currentItem,那么正确的名称将是string Name<input name="Name[0]../>等。请注意name属性中的索引器,它允许您绑定到集合

其次,您的<input name="Name[1]../>似乎是您添加了派生类型的基本类型的集合。回发时,TabListViewModel只会初始化基类型的项,因为它无法知道要初始化的派生类型。在last question中,我假设基类型为DefaultModelBinder,派生类型为ProvinceViewModelQuebecViewModel。由于OntarioViewModelProvinceViewModel,因此无法初始化(无构造函数),因此您的模型将始终为null。虽然可以编写自定义抽象abstract,但作为一个自我承认的新手,这可能最好留下,直到您更好地了解MVC和模型绑定过程(this article将帮助您开始)

解决此问题的最简单方法是使用包含每种类型集合的视图模型,并使用ModelBinder循环或自定义for。例如

查看模型

EditorTemplate

然后为每种类型

创建public class ProvinceVM { public List<QuebecViewModel> QuebecProvinces { get; set; } public List<OntarioViewModel> OntarioProvinces { get; set; } }

EditTemplate

/Views/Shared/EditorTemplates/QuebecViewModel.cshtml

然后在主视图中

@model QuebecViewModel
@Html.TextBoxFor(m => m.someProperty)
....

请注意,这两个选项都会生成控件,例如

@model ProvinceVM
@using (Html.BeginForm())
{
  @Html.EditorFor(m => m.QuebecProvinces)
  // Ditto for OntarioProvinces, or you can use a `for` loop as follows
  for(int i = 0; i < Model.OntarioProvinces.Count; i++)
  {
    @Html.TextBoxFor(m => m.OntarioProvinces[i].someProperty)
    ....
  }
}

,当你回发到

时会被正确绑定
<input name="QuebecProvinces[0].someProperty" ..../>
<input name="QuebecProvinces[1].someProperty" ..../>

答案 1 :(得分:1)

视图中似乎有太多逻辑。创建MVC是为了利用Soc - 关注点的分离(但是视图本身并不直接是模型中的可观察对象)。这使开发人员能够为系统的每个部分编写清晰,准确的代码。然而,由于其作为框架的灵活性,使得决定由开发人员决定。

这个想法是为了清晰的视野,轻型控制器和沉重的课程。

你好像在这里跑了一圈。从您的代码中,它似乎显示一个视图,然后返回一个视图,该视图根据由控制器(正在构建部分视图的视图模型)发送给它的参数呈现部分视图,该参数由render action id属性接收。

我认为代码可维护性需要实施一些明确的考虑因素

首先关闭。好像你想要实现部分视图的显示。该部分视图基于参数Model.Id确定。您没有说明此参数是如何注入RenderAction的,但这并不重要。

@{ Html.RenderAction("TabList", "TabController", new {Id = Model.Id}); }

上面的代码不需要存在。它在呼唤自己。如果不是这种情况(我可能不完全理解为什么你有那个)那么应该用对局部视图的调用来替换,使用主视图,代码正在注入{{1}参数。在这个用例中,它是View ModelId本身。

TabList

“构建视图模型”是主代码发生的地方,这是确定将要显示什么的东西,因此它不是直接显示元素,而是需要放在控制器。想想这样。当视图获取其模型时,视图需要显示的所有内容都应该已存在于模型中。如果您正在使用多态,那么没有理由在视图中的for循环中编写switch case。就是不行。应将其发送到相应的接口

所以构建viewmodel可能是这样的

[HttpGet]
// Model id is passed into the controller in which ever fashion it was 
// used to pass into the RenderAction method
public ViewResult TabList(int modelId = 3) // allows for a default on 3 
{
    // build the viewmodel

    return View();
}

所以现在你有一个viewmodel,其中包含基于modelId过滤的对象,现在只返回该模型。

//build viewmodel
TabListViewModel model = new TabListViewModel{
   PartialStuff = dbcontext.entity.FirstOrDefault(_ => _.ModelId == modelId),
}

View然后声明该模型

return View(model);

并且您有一个局部视图仅根据模型

接收所需的对象
@model TabListViewModel

部分视图的模型定义为“Partialstuff”,表单对象由该模型创建。这允许强类型模型将值传递回控制器。

@Render.PartialView("_ItemStuff",Model.PartialStuff)

答案 2 :(得分:-1)

我使用EditorTemplate更新了以下代码,以摆脱我用于迭代派生类型的多态集合的繁重切换语句。

  @for (int i = 0, c = this.Model.Count; i < c; i++)
 {
    var currentItem = this.Model.ElementAt(i);
     @Html.EditorFor(model => currentItem)
 }

然后我在所有EditorTemplates中添加了以下前缀,以防止重复的名称和控件的ID。

@{
    ViewData.TemplateInfo.HtmlFieldPrefix = Model.ProvinceCode;
}

我创建了一个自定义的ModelBinder来拦截和绑定表单数据,以创建一个我可以使用的ViewModel。

    [HttpPost]
    public ActionResult UpdateItem([ModelBinder(typeof(ProvinceCustomModelBinder))]ProvinceVM model)
相关问题