【问题标题】:Losing ViewModel on Partial View post back to controller在部分视图上丢失 ViewModel 回发给控制器
【发布时间】:2015-04-13 19:18:02
【问题描述】:

我需要对包含集合的视图模型进行持久更改,但是每次我回发到控制器时,我都会丢失模型绑定。我对 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 将始终为空。

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

我丢失绑定有什么原因吗?我想保存整个集合,而不是单独保存集合中的每个项目。

【问题讨论】:

  • 你的帖子没有[ValidateAntiForgeryToken]
  • 我忘了包括,但它应该在示例中
  • 检查您发布的数据,看看为什么它没有进入模型。也许名称不同或其他。它位于Request.Form。名称应与模型中的属性匹配。
  • 当我看到 request.form 或流阅读器时,我看到从部分返回的字段属性,但不是我期望的 ViewModel 或对象集合。
  • 属性名和ViewModel中的属性名一样吗? ViewModel 是否有一个空的构造函数?

标签: c# asp.net-mvc


【解决方案1】:

实施失败的原因有两个。

首先是使用部分显示集合中的每个项目。如果您检查生成的 html,您将看到每个 currentItemidname 属性是相同的。重复的id 是无效的 html,重复的名称属性意味着您无法绑定回集合。假设currentItem 有一个属性string Name,那么正确的名称应该是&lt;input name="Name[0]../&gt;&lt;input name="Name[1]../&gt; 等。注意name 属性中的索引器,它允许您绑定到一个集合。

其次,您的TabListViewModel 似乎是您添加了派生类型的基本类型的集合。当您回帖时,DefaultModelBinder 只会初始化基类型的项目,因为它无法知道要初始化哪个派生类型。从您的last question,我假设基本类型是ProvinceViewModel,派生类型是QuebecViewModelOntarioViewModel。因为ProvinceViewModelabstract,所以它不能被初始化(没有构造函数),所以你的模型总是空的。虽然可以编写自定义摘要 ModelBinder,但作为一个自认是新手,最好还是等到您对 MVC 和模型绑定过程有更好的了解后(this article 将帮助您入门)

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

查看模型

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" ..../>

当您回帖时将正确绑定

public ActionResult UpdateItem(ProvinceVM model) // suggest you use a more appropriate name (at least pluralize it)

【讨论】:

  • 正确,派生类型的重复名称和 id 导致了问题。我确实在这里使用了自定义模型绑定器以及 editorTemplates。
【解决方案2】:

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

这个想法是为了干净的视图、轻型控制器和重型类。

你在这里似乎有点绕圈子。从您的代码中,它似乎显示了一个视图,然后返回一个视图,该视图基于由渲染操作 id 属性接收的控制器(正在为 partialview 构建视图模型)发送给它的参数呈现局部视图。

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

首先。似乎您想要实现局部视图的显示。此部分视图是根据参数 Model.Id 确定的。你没有说这个参数是如何被注入到RenderAction 中的,但这并不重要。

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

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

[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();
}

“构建视图模型” 是主要代码发生的地方,这是决定要显示什么的东西,因此因为它不是直接需要放置的显示元素控制器。像这样想。当视图得到它的模型时,视图需要显示的所有东西都应该已经在模型中了。如果您使用多态性,则没有理由在视图中的 for 循环内编写 switch 案例。就是不行。这应该被发送到其相应的接口

所以构建视图模型可能是这样的

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

所以现在您有了一个包含已根据 modelId 过滤的对象的视图模型,现在只需返回该模型。

return View(model);

视图然后声明该模型

@model TabListViewModel

你有一个局部视图,它只接收基于模型所需的对象

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

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

@model PartialStuff


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

 // Add values with the model directly attached 
 @Html.TextBoxFor(_ => _.stuffFromTheModel)

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-27
      • 2012-05-19
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多