Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

when passing a collection to EditorFor(), it generates invalid names for input elements

I have a BookCreateModel which consists of book's plane info such as Title, PublishYear & etc plus a collection of book Authors (complex type) :

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

in CreateBook view I have used EditorFor helper :

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

Edit1:

and AuthorSelector template is as below:

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

the AuthorSelector template contains some wrapper markups which need to be aware of each rendered item's index plus some javascript which handle the child input's interactions and need to be rendered once (inside the AuthorSelector template), thus getting rid of the for loop/or the AuthorSelector template is not possible.

now the problem is EditorFor act a little strange and generate input names like this :

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

as you can see instead of generating names like Authors[0].FirstName it adds an extra dot which makes the default model binder unable to parse posted data.

any idea ?

Thanks !

like image 276
Alireza Sabouri Avatar asked Jul 09 '12 21:07

Alireza Sabouri


People also ask

What are the advantages of the HTML EditorFor () method?

The advantages of EditorFor is that your code is not tied to an <input type="text" . So if you decide to change something to the aspect of how your textboxes are rendered like wrapping them in a div you could simply write a custom editor template ( ~/Views/Shared/EditorTemplates/string.

How does HTML EditorFor work?

Simply put, the Html. EditorFor method allows the developer to retain control over the display of form elements by data type (ie. string, boolean, int…etc) or model attribute at a global level rather than at an individual view level. This allows for cleaner ASP markup and easily scalable form controls.

How do I add an EditorFor style?

EditorFor does not allow for styling as there are no parameters for additional attributes. The reason for this is because the EditorFor doesn't always generate a single element as it can be overridden. To style a specific type of element you need to use the specific editor you want to use.


2 Answers

I would recommend you sticking to conventions, i.e. replace:

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

with:

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

and then rename your ~/Views/Shared/EditorTemplates/AuthorSelector.cshtml to ~/Views/Shared/EditorTemplates/AuthorEntryModel.cshtml and make it strongly typed to a single AuthorEntryModel model and get rid of the loop:

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

ASP.NET MVC will automatically render the editor template for all elements of the collection and generate proper names.


UPDATE:

After seeing your update here's my response:

In your main view:

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

In your editor template:

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

You will notice the absence of script in the template which is perfectly normal. Scripts have nothing to do in markup. They go into separate javascript files. In this file you could use jQuery to do whatever you need to do with your markup. It gives you methods such as .index() that allow you to get the index of the element in the matched selector so that you don't need to write any loops and pollute your markup with things like data-line-index attributes.

like image 170
Darin Dimitrov Avatar answered Oct 12 '22 23:10

Darin Dimitrov


I'm a little late to the party, but hopefully this helps someone.

Digging down to System.Web.Mvc.Html.DefaultEditorTemplates.CollectionTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper), the framework's default template handles this by temporarily setting the HtmlFieldPrefix to an empty string and explicitly passing the prefix and index into a call to 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>

I found this particularly useful when the framework was writing names as [0].Children.[0].ChildProperty due to a named template for the Children collection. In my case, the solution was to call:

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

rather than simply calling:

@Html.EditorFor(m => m[i])
like image 21
Zack Martin Avatar answered Oct 13 '22 00:10

Zack Martin