При передаче коллекции в 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")
Edit1:
и шаблон 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
, он добавляет дополнительную точку, которая делает связующее устройство по умолчанию неспособным анализировать опубликованные данные.
любая идея?
Спасибо!
Ответы
Ответ 1
Я бы рекомендовал вам придерживаться соглашений, то есть заменить:
@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 автоматически отобразит шаблон редактора для всех элементов коллекции и сгенерирует собственные имена.
UPDATE:
После просмотра вашего обновления здесь мой ответ:
В вашем основном представлении:
<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>
Вы заметите отсутствие script в шаблоне, что совершенно нормально. Скрипты не имеют ничего общего с разметкой. Они входят в отдельные файлы javascript. В этом файле вы можете использовать jQuery, чтобы делать все, что вам нужно, с вашей разметкой. Он дает вам такие методы, как .index()
, которые позволяют вам получить индекс элемента в совпадающем селекторе, чтобы вам не нужно было писать какие-либо петли и загрязнять вашу разметку такими вещами, как атрибуты data-line-index
.
Ответ 2
Я немного опаздываю на вечеринку, но, надеюсь, это помогает кому-то.
Перекодировав до 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])
Ответ 3
Не знаю, действительно ли это актуально, но этот блог охватывает решение вашей проблемы:
http://btburnett.com/2011/03/correcting-mvc-3-editorfor-template-field-names-when-using-collections.html
- > Кредиты идут в itsmatt, чтобы найти его:)
Jakob
Ответ 4
Здесь вы можете использовать метод расширения, который будет отображать частичный вид и использовать правильный префикс поля 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)
Ответ 5
Я еще не нашел решения для этой ошибки, но в качестве обходного пути я изменил свой 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"/>
Ответ 6
Я наткнулся на это, когда пытался найти решение для почти той же проблемы. Мое обходное решение состояло в том, чтобы вытащить банк вниз по дороге, т.е. создайте модель обертки для коллекции и используйте ее в шаблоне редактора. В данном случае это будет:
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 кода контроллера.
Однако это не подходит для более сложных сценариев, и, вероятно, это всего лишь обходной путь.