10

我在提交多个模型的表单上绑定时遇到问题。我有一份投诉表格,其中包括投诉信息以及一对多投诉人。我正在尝试提交表单,但在绑定时遇到错误。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<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>
4

5 回答 5

3

盲猜:

改变:

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

和:

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

分别 - 添加“投诉”。在“投诉人[...”之前

编辑

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

编辑2:

我可能错了,但对我来说,实体框架(或 - 你使用它的方式)似乎存在问题。我的意思是-asp.net mvc 设法从请求中读取值,但无法初始化投诉者集合。

这里是这样写的:

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

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

更多信息:

例外:

  • 无效操作异常

条件:

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

不知为何 InitializeRelatedCollection 被触发了两次。不幸的是-我没有什么好主意。也许这个小调查会对其他人有所帮助——对 EF 更有经验。:)

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

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

这需要一些努力,但这就是应该处理的方式。这种方法从您的模型中删除了表示责任,清理了您的域模型(从您的模型中删除了 EF 内容)并解决了您的绑定问题。

于 2009-07-21T11:48:05.297 回答
3
public ActionResult Edit([Bind(Exclude = "Complainants")]Complaint model)
{
  TryUpdateModel(model.Complainants, "Complainants");
  if (!ModelState.IsValid)
  {
      // return the pre populated model
      return View(model);
  }

}

这对我有用!

我认为当创建投诉对象时,它的“投诉人”集合被初始化(因为实体框架自动逻辑),然后模型绑定器也尝试创建集合本身,这会导致错误。但是当我们尝试手动更新模型时,集合已经初始化,但模型绑定器不会被要求再次初始化它。

于 2010-02-01T11:10:48.980 回答
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();
于 2010-10-08T13:48:48.397 回答
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)。

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

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

希望对其他人有所帮助。

于 2009-10-01T02:42:49.470 回答
0

我有同样的问题!最后你会发现框架无法处理复杂的模型。

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

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

于 2009-09-17T04:20:51.943 回答