将集合传递给EditorFor()时,它会为输入元素生成无效的名称

时间:2012-07-09 21:27:45

标签: asp.net-mvc asp.net-mvc-3 razor model-binding 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 helper:

@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这样的名称,而是添加了一个额外的点,这使得默认模型绑定器无法解析发布的数据。

任何想法?

谢谢!

6 个答案:

答案 0 :(得分:8)

我建议你坚持惯例,即替换:

@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属性等内容污染您的标记。

答案 1 :(得分:7)

我参加派对有点晚了,但希望这对某人有所帮助。

向下挖掘到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>

由于Children集合的命名模板,当框架将名称写为[0].Children.[0].ChildProperty时,我发现这特别有用。在我的情况下,解决方案是打电话:

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

而不是简单地调用:

@Html.EditorFor(m => m[i])

答案 2 :(得分:2)

不知道它是否仍然相关,但此博客涵盖了您的问题的解决方案: http://btburnett.com/2011/03/correcting-mvc-3-editorfor-template-field-names-when-using-collections.html

- &GT;积分转到itsmatt找到它:) 雅各布

答案 3 :(得分:1)

这是您可以使用的扩展方法,它将呈现部分视图并使用正确的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)

答案 4 :(得分:0)

我还没有找到任何解决此bug的方法,但作为一种解决方法,我使用自定义包装类而不是直接使用集合来更改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"/>

答案 5 :(得分:0)

当我试图找到几乎相同问题的解决方案时,我偶然发现了这一点。我的解决方法是将罐子放在路上,即。为集合创建包装器模型,并在编辑器模板中使用它。在提供的情况下,它将是:

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; }
}

然后将您的编辑器模板重命名为&#34; BookAuthorsModel.cshtml&#34;并使它像这样:

@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映射设置控制器代码。 但这并不适用于某些更复杂的场景,因此可能只是一种解决方法。