我需要将更改保留到包含集合的视图模型,但每次我回发到控制器时,我都会丢失模型绑定。我对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*
}
我有没有理由失去绑定?我想保存整个集合,而不是单独保存集合中的每个项目。
答案 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
,派生类型为ProvinceViewModel
和QuebecViewModel
。由于OntarioViewModel
为ProvinceViewModel
,因此无法初始化(无构造函数),因此您的模型将始终为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)