我在提交多个模型的表单上遇到问题。我有一份投诉表格,其中包括投诉信息以及一对多投诉人。我正在尝试提交表单,但我在绑定上遇到错误。 ModelState.IsValid始终返回false。
如果我调试并查看ModelState错误,我会得到一个说法: “EntityCollection已经初始化。只应调用InitializeRelatedCollection方法在对象图的反序列化过程中初始化一个新的EntityCollection。”
此外,在调试时,我可以看到投诉模型确实填写了表单提交中的投诉人,所以看起来部分正在运作。
我不确定使用默认的ModelBinder我不能做什么,或者我是不是以正确的方式去做。我似乎无法找到任何具体的例子或文件。在stackoverflow here上可以找到一个非常类似的问题,但它似乎没有处理强类型视图。
控制器代码:
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<ProStand.Models.Complaint>" %>
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<table cellpadding="0" cellspacing="0" class="table">
<tr>
<td>
<label for="DateReceived">Date Received:</label>
<%= Html.TextBox("DateReceived") %>
<%= Html.ValidationMessage("DateReceived", "*") %>
</td>
<td>
<label for="DateEntered">Date Entered:</label>
<%= Html.TextBox("DateEntered")%>
<%= Html.ValidationMessage("DateEntered", "*") %>
</td>
</tr>
<tr>
<td>
<label for="Concluded">Concluded:</label>
<%= Html.CheckBox("Concluded")%>
<%= Html.ValidationMessage("Concluded", "*") %>
</td>
<td>
<label for="IncidentDate">Incident Date:</label>
<%= Html.TextBox("IncidentDate")%>
<%= Html.ValidationMessage("IncidentDate", "*") %></td>
</tr>
</table>
<hr />
<table>
<% if (Model != null) {
int i = 0;
foreach (var complainant in Model.Complainants){ %>
<%= Html.Hidden("Complainants[" + i + "].Id", complainant.Id)%>
<tr>
<td>
<label for="Surname">Surname:</label>
<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
<%= Html.ValidationMessage("Surname", "*")%>
</td>
<td>
<label for="GivenName1">GivenName1:</label>
<%= Html.TextBox("Complainants[" + i + "].GivenName1", complainant.GivenName1)%>
<%= Html.ValidationMessage("GivenName1", "*")%>
</td>
</tr>
<% i++; %>
<% }} %>
<tr>
<td colspan=2>
<input type="submit" value="Submit" />
</td>
</tr>
</table>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
答案 0 :(得分:3)
盲目猜测:
变化:
<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
使用:
<%= Html.TextBox("Complaint.Complainants[" + i + "].Surname",
complainant.Surname)%>
分别 - 添加“投诉”。在“投诉人[......”之前
修改强>:
这不是一个正确的答案。保留它取消删除只是因为这可能会增加一些值,直到弹出正确的答案。
<强> EDIT2:强>
我可能错了,但对我而言,实体框架似乎存在问题(或者 - 使用它的方式)。我的意思是 - asp.net mvc设法从请求中读取值但无法初始化投诉者集合。
Here写了:
InitializeRelatedCollection(TTargetEntity)方法初始化使用默认构造函数创建的现有EntityCollection(TEntity)。使用提供的关系和目标角色名称初始化EntityCollection(TEntity)。
仅在反序列化期间使用InitializeRelatedCollection(TTargetEntity)方法。
更多信息:
例外:
- InvalidOperationException
条件:
- 提供的EntityCollection(TEntity)已初始化。
- 当关系管理器已附加到ObjectContext时。
- 当关系管理器已包含与此名称和目标角色的关系时。
Somewhy InitializeRelatedCollection会被触发两次。不幸的是 - 我没有明白的想法为什么。也许这个小调查对其他人有帮助 - 对EF更有经验。 :)
<强> EDIT3:强>
这不是解决这个特定问题的方法,更像是一种解决方法,一种处理mvc模型部分的正确方法。
创建视图模型仅用于演示目的。从纯POCO创建新的域模型(因为EF将仅在下一版本中支持它们)。使用AutoMapper映射EFDataContext&lt; =&gt; Model&lt; =&gt; ViewModel。
这需要一些努力,但这应该如何处理。这种方法可以消除模型中的演示责任,清除域模型(从模型中删除EF内容),并通过绑定解决您的问题。
答案 1 :(得分:3)
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'集合会被初始化(因为实体框架自动逻辑),然后模型绑定器尝试创建集合本身,这会导致错误。 但是当我们尝试手动更新模型时,集合已经初始化,但模型绑定器不会被要求再次初始化它。
答案 2 :(得分:2)
要在没有逐案解决方法的情况下使其工作,您需要创建自己的模型绑定器并覆盖方法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();
答案 3 :(得分:1)
我通过执行以下操作解决了ModelBinding异常:
// 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
}
主要缺点是仍然抛出异常,并且在运行时可能会很昂贵。优雅的工作可能是围绕现有的DefaultBinder创建一个包装器,并防止它再次实例化EntityCollection,这已经由EF完成了。然后将该集合绑定到表单值(FormCollection)。
请记住,如果您绑定了多个集合,则需要删除每个集合的错误。
在我的实验中,集合成功保存,以及集合所属图表中的根对象。
希望能帮助别人。
答案 4 :(得分:0)
我遇到了同样的问题!最后,您会发现框架无法处理复杂的模型。
我写了一个小的绑定组件,可以初始化帖子上的复杂绑定。
但基本上你要做的就是Arnis L.所说的。