Редактирование и сохранение сложных объектов - PullRequest
7 голосов
/ 28 января 2012

Скажем, у вас есть следующий объект:

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» на этой странице, но сначала мне нужно сохранить, чтобы работать - KISS)

Проблемы:

  • ModelState.IsValid почти всегда ложно
  • Элементы формы для элементов коллекции часто имеют одинаковые имена, поэтому в этой демонстрации каждый Line1 в коллекции ShippingAddresses выводит на страницу как name="addr_Line1" вместочто-то вроде ShippingAddresses[0]_Line1, как я ожидал.

1 Ответ

10 голосов
/ 28 января 2012

Полагаю, ModelState.IsValid неверно, поскольку вы не заполнили коллекцию адресов доставки должным образом. Это произошло потому, что вы не использовали правильные имена для своих полей ввода. Взгляните на следующую статью , чтобы лучше понять, в каком формате связыватель модели ожидает, что ваши входные поля будут названы, чтобы иметь возможность восстановить значения обратно.

Причина, по которой ваш код не генерирует правильные входные имена, заключается в том, что вы использовали этот цикл 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>

Теперь вам больше не нужно беспокоиться о неправильных именах ввода. И не только это, но вы повторно используете шаблон редактора адресов как для своего платежного адреса, так и для набора адресов доставки. Это делает ваши взгляды более сухими.

...