MVC не может переопределять имя EditorTemplate при использовании в EditorFor для дочернего объекта

Я пытаюсь использовать EditorTemplate для отображения дочерней коллекции в таблице в представлении родителей. Проблема, с которой я столкнулся, заключается в том, что это работает, только если шаблон назван точно таким же, как класс childs. Когда я пытаюсь использовать шаблон с немного другим именем и передаю это имя в качестве аргумента templateName для редактора, я получаю ошибку времени выполнения. Я надеялся, что могу использовать разные дочерние EditorTemplates для разных целей с той же дочерней коллекцией. Вот сокращенный пример:

Модель:

public class Customer
{
  int id { get; set; }
  public string name { get; set; }

  public List<Order> Orders { get; set; }
}
public class Order
{
    public int id { get; set; }
    public DateTime orderdate { get; set; }
    public decimal amount { get; set; }

    public Customer customer { get; set; }
}

Клиентский метод Index():

public ActionResult Index()
{
  Customer customer = new Customer() {id = 1, name = "Acme Corp.", Orders = new List<Order>()};
  customer.Orders.Add(new Order() {id = 1, orderdate = DateTime.Now, amount = 100M});
  customer.Orders.Add(new Order() { id = 2, orderdate = DateTime.Now, amount = 200M });
  return View(customer);
}

Просмотр клиента Index.cshtml:

@model TemplateTest.Customer

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Customer</title>
</head>
<body>
  <div>
      @Html.EditorFor(Model=>Model.name)

      <table>
      <thead>
          <tr>
              <th>Order ID</th>
              <th>Order Date</th>
              <th>Amount</th>
          </tr>
      </thead>
          @Html.EditorFor(Model=>Model.Orders)
      </table>

  </div>
</body>
</html>

шаблон Order.cshmtl в Views/Shared/EditorTemplates (добавлен "цвет" для проверки того, что я использую этот шаблон):

@model TemplateTest.Order

<tr>
  <td>@Html.DisplayFor(Model=>Model.id)</td>
  <td style="color:blue">@Html.EditorFor(Model=>Model.orderdate)</td>
  <td>@Html.EditorFor(Model=>Model.amount)</td>
</tr>

Это прекрасно работает. Но если я переименую EditorTemplate в "OrderList.cshtml" и изменим дочернюю строку EditorFor на

@Html.EditorFor(Model=>Model.Orders, "OrderList")

когда я запускаю его снова, я получаю это исключение:

"Элемент модели, переданный в словарь, имеет тип" System.Collections.Generic.List`1 [TemplateTest.Order] ", но для этого словаря требуется элемент модели типа" TemplateTest.Order "."

Любая идея, почему EditorFor не использует шаблон "OrderList", указанный в аргументе "templateName"? В противном случае, что это за аргумент?

Ответ 1

TL; DR > Именованные шаблоны не работают с коллекциями, используйте цикл foreach, чтобы обойти его. Ниже приведены подробные сведения о том, почему и пример.


Ты сказал:

Любая идея, почему редактор EditorFor не использует шаблон "OrderList" я указанном в аргументе "templateName"? В противном случае, что это аргумент для?

EditorFor фактически использует шаблон OrderList, который вы указали, - но вы наткнулись на что-то очень запутанное. В некоторых исследованиях появилось много намеков, но я нашел настоящие гайки и болты в этом сообщении: Проблема с MVC EditorFor именованным шаблоном

Короче говоря, происходит то, что работает по умолчанию: @Html.EditorFor(Model=>Model.Orders) фактически вызывает шаблон MVC по умолчанию по соглашению, но это совсем не очевидно.

Подумайте об этом так:

В рабочей версии вы передаете тип List<Order> со ссылкой на Model.Orders (MANY orders), но шаблон указан с помощью модели Order (single, NOT MANY).

Интересно. Почему это работает? На первый взгляд кажется, что он должен не работать. Но это работает из-за того, что происходит за кулисами.

Перефразируемый из вышеупомянутого поста:

Когда вы используете @Html.EditorFor(c => c.Orders), выбирается соглашение MVC шаблон по умолчанию для IEnumerable. Этот шаблон является частью структуры MVC, и то, что он делает, генерирует Html.EditorFor() для каждый элемент в перечислении. Затем этот шаблон генерирует соответствующий шаблон редактора для каждого элемента в списке отдельно- в вашем случае все экземпляры Order, поэтому для каждого элемента используется шаблон Order.

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

Теперь, когда вы пытаетесь сделать то же самое, но с использованием именованного шаблона, явно установив свой EditorFor для использования конкретного шаблона редактора OrderList, вы получите тот шаблон редактора, который проходит всю нумерацию, - и это является источником ошибки, которую вы опубликовали.

Другими словами, недостающему случаю удается пропустить "магическую" часть рабочего процесса и что именно поэтому он терпит неудачу. Но, семантически это выглядит хорошо и здорово, не так ли? Там путаница.

Рабочий регистр:

your call                                default MVC template      your template
@Html.EditorFor( Model => Model.Orders)  IEnumerable template      Order template

Неудачный случай:

your call                                           your template
@Html.EditorFor(Model=>Model.Orders, "OrderList")   OrderList template       ERROR!!!

Существует несколько способов устранения ошибки, но многие из них проблематичны, поскольку они вызывают отображение элементов управления HTML таким образом, чтобы вы не могли обращаться к отдельным элементам управления по индексу POST. Uhhg. (Примечание: рабочий процесс действительно корректно отображает HTML)

Чтобы получить правильные представления HTML-элементов, кажется, что вы должны использовать обычный цикл for (а не foreach) и передавать каждый из отдельных объектов Order в пользовательский шаблон (который я назвал OrderEditorTemplateDefault).

@for (int i = 0; i < Model.Orders.Count ; i++) 
{
    @Html.EditorFor(c => Model.Orders[i], "OrderEditorTemplateDefault")
} 

Часть вашего вопроса указана:

Я надеялся, что могу использовать разные дочерние EditorTemplates для разных с той же дочерней коллекцией.

Вы можете сделать это, введя условие внутри цикла и выбрав альтернативный шаблон там (либо для всего списка, либо для порядка по порядку, просто зависит от того, как вы записываете условие)

@for (int i = 0; i < Model.Orders.Count ; i++) {
    if (someCondition) {
        @Html.EditorFor(c => Model.Orders[i], "OrderEditorTemplateDefault")
    } else {
        @Html.EditorFor(c => Model.Orders[i], "OrderEditorTemplateALTERNATE")
    }
} 

Извините, так много. Надеюсь, что это поможет.