【问题标题】:Editing and saving complex objects编辑和保存复杂对象
【发布时间】:2012-01-27 20:01:36
【问题描述】:

假设你有以下对象:

public class Address
{
  public String Line1 { get; set; }
  public String Line2 { get; set; }
  public String City { get; set; }
  public String State { get; set; }
  public String ZipCode { get; set; }

  public Address()
  {
  }
}

public class Contact
{
  public String FirstName { get; set; }
  public String LastName { get; set; }
  public String Telephone { get; set; }

  public Address BillingAddress { get; set; }
  public List<Address> ShippingAddresses { get; set; }

  public Contact()
  {
    // Assume anything that _could_ be null wouldn't be. I'm excluding
    // most "typical" error checking just to keep the examples simple
    this.BillingAddress = new Address();
    this.ShippingAddresses = new List<Address>();
  }
}

假设属性被[Required][Display] 和其他属性修饰。

然后是我的控制器(为了演示而简化):

public ActionResult Edit(String id)
{
  Contact contact = ContactManager.FindByID(id);
  return View(model: contact);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact contact)
{
  if (ModelState.IsValid) //always fails
  {
    ContactManager.Save(contact);
    return RedirectToAction("Saved");
  }
  return View(model: contact);
}

我经常看到在 MVC 中编辑这样的对象的演示,但他们不断地将对象的集合分解为自己的形式(例如,编辑联系人,然后编辑联系人的特定地址)。另一方面,我试图在同一页面内编辑所有这些信息,但没有成功。例如:

@model Contact

// simplified for brevity
@using (Html.BeginForm())
{
  @Html.LabelFor(x => x.FirstName): @Html.EditorFor(x => x.FirstName)
  @Html.LabelFor(x => x.LastName): @Html.EditorFor(x => x.LastName)
  @Html.LabelFor(x => x.Telephone): @Html.EditorFor(x => x.Telephone)

  <div>
    @Html.LabelFor(x => x.BillingAddress.Line1): @Html.EditorFor(x => x.BillingAddress.Line1)
    @Html.LabelFor(x => x.BillingAddress.Line2): @Html.EditorFor(x => x.BillingAddress.Line2)
    @Html.LabelFor(x => x.BillingAddress.City): @Html.EditorFor(x => x.BillingAddress.City)
    @Html.LabelFor(x => x.BillingAddress.State): @Html.EditorFor(x => x.BillingAddress.State)
    @Html.LabelFor(x => x.BillingAddress.ZipCode): @Html.EditorFor(x => x.BillingAddress.ZipCode)
  </div>

  <div>
  @foreach (Address addr in Model.ShippingAddresses)
  {
    <div>
      @Html.LabelFor(x => addr.Line1): @Html.EditorFor(x => addr.Line1)
      @Html.LabelFor(x => addr.Line2): @Html.EditorFor(x => addr.Line2)
      @Html.LabelFor(x => addr.City): @Html.EditorFor(x => addr.City)
      @Html.LabelFor(x => addr.State): @Html.EditorFor(x => addr.State)
      @Html.LabelFor(x => addr.ZipCode): @Html.EditorFor(x => addr.ZipCode)
    </div>
  }
  </div>
}

我一直遇到的问题是,当我去保存信息时,ModelState.IsValid 永远不会通过。这样做有诀窍,还是只是在 MVC 领域之外?我想获取一个像Contact 这样的对象并将所有信息转储在一页上进行编辑,然后成功地重新保存它,但我似乎无法让它工作。 (我的下一步是绑定 ajax,以便您可以在该页面上动态添加/删除“ShipingAddresses”,但我需要先保存才能工作--K.I.S.S)

问题:

  • ModelState.IsValid 几乎总是假的
  • 集合项的表单元素通常具有相同的名称,因此在此演示中,ShippingAddresses 集合中的每个Line1 都以name="addr_Line1" 的形式转储到页面,而不是像我期望的那样ShippingAddresses[0]_Line1

【问题讨论】:

  • 您可以将[AcceptVerbs(HttpVerbs.Post)] 缩短为[HttpPost]
  • Contact是否有默认构造函数?
  • @ChrisS:是的,为了论证,它们都有空的构造函数。

标签: razor asp.net-mvc-4 asp.net-mvc-templates


【解决方案1】:

我猜ModelState.IsValid 是错误的,因为您没有正确填写收货地址集合。发生这种情况是因为您没有为输入字段使用正确的名称。查看following article 以更好地了解模型绑定器希望您的输入字段以什么格式命名,以便能够重新构造值。

您的代码没有生成正确的输入名称的原因是您使用了这个 foreach 循环,其中视图丢失了导航上下文的跟踪。

所以试试这样:

@for (var i = 0; i < Model.ShippingAddresses.Count; i++)
{
    <div>
        @Html.LabelFor(x => x.ShippingAddresses[i].Line1): 
        @Html.EditorFor(x => x.ShippingAddresses[i].Line1)

        @Html.LabelFor(x => x.ShippingAddresses[i].Line2): 
        @Html.EditorFor(x => x.ShippingAddresses[i].Line2)

        @Html.LabelFor(x => x.ShippingAddresses[i].City): 
        @Html.EditorFor(x => x.ShippingAddresses[i].City)

        @Html.LabelFor(x => x.ShippingAddresses[i].State): 
        @Html.EditorFor(x => x.ShippingAddresses[i].State)

        @Html.LabelFor(x => x.ShippingAddresses[i].ZipCode): 
        @Html.EditorFor(x => x.ShippingAddresses[i].ZipCode)
    </div>    
}

备注:请注意,我还使用 EditorFor 调用替换了您的重复标签,以有效地为这些字段生成输入字段。

甚至更好地使用这样的编辑器模板:

@model Contact

@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.FirstName): 
    @Html.EditorFor(x => x.FirstName)

    @Html.LabelFor(x => x.LastName): 
    @Html.EditorFor(x => x.LastName)

    @Html.LabelFor(x => x.Telephone): 
    @Html.EditorFor(x => x.Telephone)

    @Html.EditorFor(x => x.BillingAddress)

    @Html.EditorFor(x => x.ShippingAddresses)
}

然后定义一个自定义编辑器模板,该模板将自动为ShippingAddresses 集合 (~/Views/Shared/EditorTemplates/Address.cshtml) 的每个元素呈现:

@model Address
<div>
    @Html.LabelFor(x => x.Line1): 
    @Html.EditorFor(x => x.Line1)

    @Html.LabelFor(x => x.Line2): 
    @Html.EditorFor(x => x.Line2)

    @Html.LabelFor(x => x.City): 
    @Html.EditorFor(x => x.City)

    @Html.LabelFor(x => x.State): 
    @Html.EditorFor(x => x.State)

    @Html.LabelFor(x => x.ZipCode): 
    @Html.EditorFor(x => x.ZipCode)
</div>

现在您不必再担心输入错误的名称了。不仅如此,您还为您的帐单地址和送货地址的集合重复使用地址编辑器模板。它使您的观点更加干燥。

【讨论】:

  • 看,这是我对 MVC 和模板还不太了解的地方;如果我希望元素具有“合理”的名称(即不是所有的名称都相同),那么lamba 会在其中发挥重要作用吗?
  • @BradChristie,绝对是至关重要的。但是,如果您遵循以下简单规则,您将始终正确:如果您发现自己在视图中编写循环(forforeach),那么您做错了。警报应立即响起。所以停止并用模板替换这个循环(如果您正在呈现输入字段,则编辑器,如果您以只读方式显示数据,则显示)。
  • 跟进:假设我没有使用列表,而是使用“地址”自定义集合。模板仍然允许我这种灵活性吗?例如MyPage.cshtml=>@Html.EditorFor(x =&gt; x.Addresses) -- Addresses.cshtml=>&lt;div&gt;for(i ...){ @Html.EditorFor(model =&gt; model[i]) -- (然后Address.cshtml你描述的模板?)
  • @BradChristie,你为什么还在展示 for 循环?我对循环说不。模板绝对适用于任何集合(自定义或非自定义),而不仅仅是集合,任何类型。因此,如果我们假设在您的视图模型上有一个属性IEnumerable&lt;MyElement&gt; FooBars { get; set; },那么在您的主视图中您不编写循环(记住这一点),您只需编写@Html.EditorFor(x =&gt; x.FooBars),然后在~/Views/Shared/EditorTemplates/MyElement.cshtml 中定义一个自定义编辑器模板它将为此集合的每个元素自动呈现...
  • ... 模板的位置和名称很重要。它应该位于~/Views/Shared/EditorTemplates~/Views/XXX/EditorTemplates 中,其中XXX 是您当前控制器的名称。在第一种情况下,您正在定义一个可以在整个应用程序中重复使用的模板,而在第二种情况下,您正在定义一个仅用于属于 XXX 控制器的视图的模板。模板的名称也很重要。它必须是集合元素类型的名称。在这个例子中它应该是MyElement.cshtml 因为你有`IEnum
猜你喜欢
  • 1970-01-01
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多