强类型视图中多模型表示的模型绑定

我在提交多个模型的表单上遇到问题。 我有一份投诉表格,其中包括投诉信息以及一对多投诉人。 我正在尝试提交表单,但我在绑定上遇到错误。 ModelState.IsValid始终返回false。

如果我调试并查看ModelState错误,我会得到一个说法:“EntityCollection已经被初始化。应该只调用InitializeRelatedCollection方法来在对象图的反序列化期间初始化一个新的EntityCollection”。

此外,在调试时,我可以看到投诉模型确实从表单提交中填充了投诉人,因此看起来该部分正在运行。

我不确定使用默认的ModelBinder我是不是可以做什么,或者我是不是以正确的方式去做。 我似乎无法找到任何具体的例子或文件。 这里可以在stackoverflow上找到一个非常类似的问题,但它似乎没有处理强类型视图。

控制器代码:

public ActionResult Edit(int id) { var complaint = (from c in _entities.ComplaintSet.Include("Complainants") where c.Id == id select c).FirstOrDefault(); return View(complaint); } // // POST: /Home/Edit/5 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Edit(Complaint complaint) { if (!ModelState.IsValid) { return View(); } try { var originalComplaint = (from c in _entities.ComplaintSet.Include("Complainants") where c.Id == complaint.Id select c).FirstOrDefault(); _entities.ApplyPropertyChanges(originalComplaint.EntityKey.EntitySetName, complaint); _entities.SaveChanges(); return RedirectToAction("Index"); } catch { return View(); } } 

查看代码(这是由创建/编辑视图调用的部分视图,它也是使用Complaint强类型的):

 <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>   

盲目猜测:

更改:

 <%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%> 

有:

 <%= Html.TextBox("Complaint.Complainants[" + i + "].Surname", complainant.Surname)%> 

分别 – 添加“投诉”。 在“投诉人[……”之前

编辑

这不是一个正确的答案。 保留它取消删除只是因为这可能会增加一些值,直到弹出正确的答案。

EDIT2:

我可能错了,但对我而言,entity framework似乎存在问题(或者 – 使用它的方式)。 我的意思是 – asp.net mvc设法从请求中读取值但无法初始化投诉者集合。

在这里写道:

InitializeRelatedCollection(TTargetEntity)方法初始化使用默认构造函数创建的现有EntityCollection(TEntity)。 通过使用提供的关系和目标角色名称初始化EntityCollection(TEntity)。

InitializeRelatedCollection(TTargetEntity)方法仅在反序列化期间使用。

更多信息:

例外:

  • 出现InvalidOperationException

条件:

  • 当提供的EntityCollection(TEntity)已经初始化时。
  • 当关系管理器已经附加到ObjectContext时。
  • 当关系管理器已经包含与此名称和目标角色的关系时。

为什么InitializeRelatedCollection会被触发两次。 不幸的是 – 我没有明白的想法为什么。 也许这个小调查对其他人有帮助 – 对EF更有经验。 🙂

EDIT3:
这不是解决这个特定问题的方法,更像是一种解决方法,一种处理mvc模型部分的正确方法。

创建视图模型仅用于演示目的。 从纯POCO创建新的域模型(因为EF将仅在下一版本中支持它们)。 使用AutoMapper映射EFDataContext <=>模型<=> ViewModel。

这需要一些努力,但这应该是如何处理的。 这种方法可以从模型中删除演示文稿的责任,清除域模型(从模型中删除EF内容),并通过绑定解决您的问题。

 public ActionResult Edit([Bind(Exclude = "Complainants")]Complaint model) { TryUpdateModel(model.Complainants, "Complainants"); if (!ModelState.IsValid) { // return the pre populated model return View(model); } } 

这适合我!

我认为当Complaint对象被创建时,它的’Complainants’集合被初始化(因为entity framework自动逻辑),然后模型绑定器尝试创建集合本身,这导致错误。 但是当我们尝试手动更新模型时,集合已经初始化,但模型绑定器不会被要求再次初始化它。

要在没有逐案解决方法的情况下使其工作,您需要创建自己的模型绑定器并覆盖方法SetProperty:

 public class MyDefaultModelBinder : DefaultModelBinder { protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) { ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; propertyMetadata.Model = value; string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName); // Try to set a value into the property unless we know it will fail (read-only // properties and null values with non-nullable types) if (!propertyDescriptor.IsReadOnly) { try { if (value == null) { propertyDescriptor.SetValue(bindingContext.Model, value); } else { Type valueType = value.GetType(); if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>)) { IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model); IList list = ls.GetList(); foreach (var item in (IEnumerable)value) { list.Add(item); } } else { propertyDescriptor.SetValue(bindingContext.Model, value); } } } catch (Exception ex) { // Only add if we're not already invalid if (bindingContext.ModelState.IsValidField(modelStateKey)) { bindingContext.ModelState.AddModelError(modelStateKey, ex); } } } } } 

不要忘记在Global.asax中注册您的活页夹:

 ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder(); 

我通过执行以下操作解决了ModelBindingexception:

 // Remove the error from ModelState which will have the same name as the collection. ModelState.Remove("Complaints"/*EntityCollection*/); if (ModelState.IsValid) // Still catches other errors. { entities.SaveChanges(); // Your ObjectContext } 

主要缺点是仍然抛出exception,并且在运行时可能很昂贵。 优雅的工作可能是围绕现有的DefaultBinder创建一个包装器,并防止它再次实例化EntityCollection,这已经由EF完成。 然后将该集合绑定到表单值(FormCollection)。

请记住,如果您绑定了多个集合,则需要删除每个集合的错误。

在我的实验中,集合成功保存以及集合所属的图形中的根对象。

希望能帮助别人。

我遇到了同样的问题! 最后,您会发现框架无法处理复杂的模型。

我写了一个小的绑定组件,可以初始化post上的复杂绑定。

但基本上你要做的就是Arnis L.所说的。