при передаче коллекции в EditorFor() он генерирует недопустимые имена для входных элементов

У меня есть BookCreateModel, который состоит из информации о плоскости книги, такой как Title, PublishYear и т. д., а также набор авторов книг (сложный тип):

public class BookCreateModel
{
    public string Title { get; set; }
    public int Year { get; set; }
    public IList<AuthorEntryModel> Authors { get; set; }
}

public class AuthorEntryModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

в представлении CreateBook я использовал помощник EditorFor:

@Html.EditorFor(m => m.Authors, "AuthorSelector")

Редактировать1:

и шаблон AuthorSelector выглядит следующим образом:

<div class="ptr_authors_wrapper">
    @for (int i = 0; i < Model.Count; i++)
    {
       <div class="ptr_author_line" data-line-index="@i">
        @Html.TextBoxFor(o => o[i].FirstName)
        @Html.TextBoxFor(o => o[i].LastName)
       </div>
    }
</div>
<script>
     ...
</script>

шаблон AuthorSelector содержит некоторые разметки-оболочки, которые должны учитывать индекс каждого визуализируемого элемента, а также некоторый javascript, который обрабатывает взаимодействие дочернего ввода и должен быть отображен один раз (внутри шаблона AuthorSelector), таким образом избавляясь от цикла for/или AuthorSelector шаблон невозможен.

теперь проблема в том, что EditorFor ведет себя немного странно и генерирует имена ввода, подобные этому:

<input id="Authors__0__FirstName" name="Authors.[0].FirstName" type="text" value="" />
<input id="Authors__0__LastName" name="Authors.[0].LastName" type="text" value="" />

как вы можете видеть, вместо создания имен, таких как Authors[0].FirstName, он добавляет дополнительную точку, из-за которой связыватель модели по умолчанию не может анализировать опубликованные данные.

любая идея ?

Спасибо !


person Alireza Sabouri    schedule 09.07.2012    source источник


Ответы (6)


Я бы рекомендовал вам придерживаться соглашений, т.е. заменить:

@Html.EditorFor(m => m.Authors, "AuthorSelector")

с участием:

@Html.EditorFor(m => m.Authors)

а затем переименуйте свой ~/Views/Shared/EditorTemplates/AuthorSelector.cshtml в ~/Views/Shared/EditorTemplates/AuthorEntryModel.cshtml и сделайте его строго типизированным для одной модели AuthorEntryModel и избавьтесь от цикла:

@model AuthorEntryModel
@Html.TextBoxFor(o => o.FirstName)
@Html.TextBoxFor(o => o.LastName)

ASP.NET MVC автоматически отобразит шаблон редактора для всех элементов коллекции и сгенерирует собственные имена.


ОБНОВИТЬ:

Увидев ваше обновление, вот мой ответ:

В вашем основном представлении:

<div class="ptr_authors_wrapper">
    @Html.EditorFor(m => m.Authors)
</div>

В вашем шаблоне редактора:

@model AuthorEntryModel
<div class="ptr_author_line">
    @Html.TextBoxFor(o => o.FirstName)
    @Html.TextBoxFor(o => o.LastName)
</div>

Вы заметите отсутствие скрипта в шаблоне, что совершенно нормально. Скрипты не имеют ничего общего с разметкой. Они идут в отдельные файлы javascript. В этом файле вы можете использовать jQuery, чтобы делать с вашей разметкой все, что вам нужно. Он дает вам такие методы, как .index(), которые позволяют вам получить индекс элемента в совпадающем селекторе, так что вам не нужно писать какие-либо циклы и загрязнять вашу разметку такими вещами, как атрибуты data-line-index.

person Darin Dimitrov    schedule 09.07.2012
comment
спасибо, AuthorSelector также генерирует некоторую разметку-оболочку для каждого элемента, поэтому замена цикла for новым синтаксисом невозможна. Я обновил вопрос, пожалуйста, посмотрите - person Alireza Sabouri; 10.07.2012
comment
да, это тоже вариант, но я думаю, что цель использования EditorTemplate состоит в том, чтобы инкапсулировать множество пометок и кодов, которые освобождают разработчика от повторения себя снова и снова. div-обертка в данном случае является упрощенным примером; на самом деле это намного длиннее, представьте, как сложно было бы копировать/вставлять этот фрагмент разметки снова и снова в разных местах. - person Alireza Sabouri; 10.07.2012
comment
@ sos00, да, именно в этом и заключается цель редактора tempate => инкапсулировать многоразовую разметку. Я говорю разметка, а не скрипты. Скрипты помещаются в отдельные (и, если вы будете использовать повторно) файлы javascript. Вы можете написать плагины и так далее, чтобы ваши скрипты можно было использовать повторно. - person Darin Dimitrov; 10.07.2012
comment
Спасибо :), я тоже имел в виду разметку, а не скрипты. ваше решение требует, чтобы разработчик копировал разметки везде, где он хочет получить набор элементов Author. в любом случае, я надеялся узнать, является ли это известной ошибкой, и кто-то знает какое-либо обходное решение для нее, поэтому я проголосовал за ваш ответ, но подожду еще немного, чтобы узнать, есть ли у кого-нибудь еще информация об этой ошибке (?). - person Alireza Sabouri; 10.07.2012
comment
Использование шаблонов редактора позволяет предоставить шаблон для каждого элемента коллекции. Насколько я вижу, вы не можете определить шаблон для коллекции в целом (скажем, вам нужен верхний и нижний колонтитулы), из которого вы можете затем вызвать шаблон редактора для элементов без наличия проблема дополнительной точки во входном префиксе. Решение @sos00 по добавлению класса-оболочки работает, но похоже на обходной путь. - person Dave Watts; 11.08.2012

Я немного опоздал на вечеринку, но, надеюсь, это кому-то поможет.

Копаясь до System.Web.Mvc.Html.DefaultEditorTemplates.CollectionTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper), шаблон фреймворка по умолчанию обрабатывает это, временно устанавливая HtmlFieldPrefix в пустую строку и явно передавая префикс и индекс в вызове EditorFor().

<div class="ptr_authors_wrapper">
@{
    var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;

    ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty;

    for (int i = 0; i < Model.Count; i++)
    {
        <div class="ptr_author_line" data-line-index="@i">
            @* You can also use null instead of "TextBox" to let the framework resolve which editor to use. *@
            @Html.EditorFor(o => o[i].FirstName, "TextBox", String.Format("{0}[{1}].FirstName", prefix, i))
            @Html.EditorFor(o => o[i].LastName, "TextBox", String.Format("{0}[{1}].LastName", prefix, i))
        </div>
    }

    ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}
</div>
<script>
     ...
</script>

Я нашел это особенно полезным, когда фреймворк записывал имена как [0].Children.[0].ChildProperty из-за именованного шаблона для коллекции Children. В моем случае решение состояло в том, чтобы позвонить:

@Html.EditorFor(m => m[i], null, String.Format("{0}[{1}]", prefix, i))

а не просто звонить:

@Html.EditorFor(m => m[i])
person Zack Martin    schedule 22.12.2013
comment
Большое спасибо за это. Ваш ответ только что сделал мое утро. - person Erik Dietrich; 01.03.2016

Не знаю, актуально ли это еще, но в этом блоге описано решение вашей проблемы: http://btburnett.com/2011/03/correcting-mvc-3-editorfor-template-field-names-when-using-collections.html

--> Кредиты идут на itsmatt, чтобы найти его :) Джейкоб

person Jakob Engelbrecht Olesen    schedule 16.01.2013

Вот метод расширения, который вы можете использовать, который будет отображать частичное представление и использовать правильный префикс поля HTML:

Метод расширения

    /// <summary>
    /// Helper method that renders the specified partial view as a HTML-encoded string using the specified
    /// collection as the model, with the intention that the partial view will use an editor template on the items
    /// in the collection.
    /// </summary>
    /// <typeparam name="TModel">the model type</typeparam>
    /// <typeparam name="TProperty">the property type</typeparam>
    /// <param name="htmlHelper">the <see cref="HtmlHelper"/> instance</param>
    /// <param name="partialViewName">the name of the partial view to render</param>
    /// <param name="collectionExpression">the model collection property expression</param>
    /// <returns>the HTML-encoded string</returns>
    public static MvcHtmlString PartialContainingEditorForCollection<TModel, TProperty>
        (this HtmlHelper<TModel> htmlHelper, string partialViewName,
         Expression<Func<TModel, TProperty>> collectionExpression)
        where TProperty : IEnumerable
    {
        var viewData = htmlHelper.ViewContext.ViewData;
        var model = (TModel) viewData.Model;
        var collection = collectionExpression.Compile().Invoke(model);

        var htmlFieldPrefix = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(
            ExpressionHelper.GetExpressionText(collectionExpression));

        return htmlHelper.Partial(partialViewName, collection,
                                  new ViewDataDictionary
                                      {
                                          TemplateInfo = new TemplateInfo {HtmlFieldPrefix = htmlFieldPrefix}
                                      });
    }

Пример использования

@Html.PartialContainingEditorForCollection("_TableWithSummary", m => Model.FormModel.ItemsToOrder)
person Dave Watts    schedule 10.08.2012
comment
Не могли бы вы поставить родительский вид и дочерний вид? - person jlp; 14.02.2014

Я еще не нашел решения этой ошибки, но в качестве обходного пути я изменил свой UpdateModel, используя собственный класс-оболочку вместо прямого использования коллекции:

public class BookCreateModel
{
    public string Title { get; set; }
    public int Year { get; set; }
    public BookAuthorsList Authors { get; set; }
}

public class BookAuthorsList
{
    public IList<AuthorEntryModel> AuthorsList { get; set; }
}

public class AuthorEntryModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

и, таким образом, сгенерированные входные данные больше не будут вызывать проблем с именами :)

<input  id="Authors_AuthorsList_0__FirstName" name="Authors.AuthorsList[0].FirstName" type="text"/>
<input  id="Authors_AuthorsList_0__LastName" name="Authors.AuthorsList[0].LastName" type="text"/>
person Alireza Sabouri    schedule 12.07.2012

Я наткнулся на это, когда пытался найти решение почти той же проблемы. Мой обходной путь состоял в том, чтобы пнуть банку по дороге, т.е. создайте модель-оболочку для коллекции и используйте ее в шаблоне редактора. В приведённом случае это будет:

public class BookCreateModel
{
    public string Title { get; set; }
    public int Year { get; set; }
    public BookAuthorsModel Authors { get; set; }
}

public class BookAuthorsModel
{
    IList<AuthorEntryModel> Items { get; set; }
}

Затем переименуйте свой шаблон редактора в «BookAuthorsModel.cshtml» и сделайте так:

@model BookAuthorsModel
<div class="ptr_authors_wrapper">
    @for (int i = 0; i < Model.Items.Count; i++)
    {
       <div class="ptr_author_line" data-line-index="@i">
        @Html.TextBoxFor(o => Items.o[i].FirstName)
        @Html.TextBoxFor(o => Items.o[i].LastName)
       </div>
    }
</div>
<script>
     ...
</script>

И когда вы хотите использовать его, просто позвоните:

@Html.EditorFor(m => m.Authors)

Затем он должен генерировать поля ввода следующим образом:

<input id="Authors_Items_0__FirstName" name="Authors.Items[0].FirstName" type="text" value="" />
<input id="Authors_Items_0__LastName" name="Authors.Items[0].LastName" type="text" value="" />

В моем случае я также изменил настройки сопоставления Automapper соответствующим образом в коде контроллера. Однако это непригодно для некоторых более сложных сценариев и, таким образом, это, вероятно, просто обходной путь.

person davke    schedule 18.09.2015