Id & name error when using EditorTemplate
It took me about 3 hours to be sure why the following (explained) error occurs; and finally i'm pretty sure it is mvc bug. I would like to know your opinion.
I created an editor template and placed it in the ~ / Shared / EditorTemplates folder. And I carefully used the filename equal to the type name (because I don't want to use the second parameter with the "@ Html.EditorFor" helper method). As a result, the template editor worked fine: except for postbacks; I definitely couldn't get the values ββfrom the model because the model was always null. I tried changing the id and the names of the inputs and picking carefully and made the answer back: it works fine.
So the problem is this: when I use editorTemplate it gives "Question. [0] .QuestionContext" as name and "Question__0_QuestionContext" as id for input and selection; however I was expecting "Question [0] .QuestionContext" as the name and "Question_0__QuestionContext" as the id.
Then I realized I was using @foreach inside the EditorTemplate. Then I switch to @for, but nothing has changed. You can find the template below:
@using DenemeMvc3Application3.Helpers;
@model DenemeMvc3Application3.Controllers.LocalizedNameViewModels
<table>
<thead>
<tr>
<td>@Html.LabelFor(modelItem => modelItem.ElementAtOrDefault(0).LanguageDropDownList)</td>
<td>@Html.LabelFor(modelItem => modelItem.ElementAtOrDefault(0).Value)</td>
</tr>
</thead>
<tbody>
@for (int index = 0; index < Model.Count; index++)
{
@Html.EditorFor(modelItem => modelItem[index])
}
</tbody>
</table>
So, I changed my template to a simpler one by declaring a foreach loop outside the editortemplate and inside the view, and using the editor template for the specific item only. And it worked. So I did a little research on how templates work on mvc3 using reflector; and i found the error i suppose:
The problem is caused by System.Web.Mvc.TemplateInfo class public string GetFullHtmlFieldName (string partialFieldName) method that is used by System.Web.Mvc.Html.SelectExtensions class private static MvcHtmlString SelectInternal (this HtmlHelper htmlHelper, string optionLabel, string name, selectListrable method bool allowMultiple, IDictionary htmlAttributes). Thus, whenever we use @ Html.DropDownListFor (/ blah blah /) inside a foreach or for loop in the editor, we get an error.
I also want to show you the error code below to clarify:
// TemplateInfo method
public string GetFullHtmlFieldName(string partialFieldName)
{
/*Here the extra dot: causing the error.*/
return (this.HtmlFieldPrefix + "." + (partialFieldName ?? string.Empty)).Trim(new char[] { '.' });
}
// SelectExtensions method
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple, IDictionary<string, object> htmlAttributes)
{
/* blah blah */
string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
/* blah blah */
}
// SelectExtensions method -> @Html.DropDownListFor
private static MvcHtmlString DropDownListHelper(HtmlHelper htmlHelper, string expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
return htmlHelper.SelectInternal(optionLabel, expression, selectList, false, htmlAttributes);
So what do you think?
source to share
Then I realized I was using @foreach inside the EditorTemplate. Then I'll go back to @for, but nothing has changed.
How to get rid of all loops and just:
@model IEnumerable<FooViewModel>
<table>
<thead>
<tr>
<td>@Html.LabelFor(modelItem => modelItem.ElementAtOrDefault(0).LanguageDropDownList)</td>
<td>@Html.LabelFor(modelItem => modelItem.ElementAtOrDefault(0).Value)</td>
</tr>
</thead>
<tbody>
@Html.EditorForModel()
</tbody>
</table>
Much better? There are no loops and correct names and IDs. This is ASP.NET MVC that will automatically render an editor template for every item in your model collection.
and the corresponding editor template ( ~/Views/Shared/EditorTemplates/FooViewModel.cshtml
):
@model FooViewModel
<tr>
<td>@Html.EditorFor(x => x.SomeProperty)</td>
<td>@Html.EditorFor(x => x.SomeOtherProperty)</td>
</tr>
source to share
I have a more complex rendering problem, it turns out the same:
instead of getting
xxxx.yyy.zzz.MyCollection[%index%]
gets:
xxxx.yyy.zzz.MyCollection.[%index%]
I think the original MVC method:
public string GetFullHtmlFieldName(string partialFieldName)
{
/*Here the extra dot: causing the error.*/
return (this.HtmlFieldPrefix + "." + (partialFieldName ?? string.Empty)).Trim(new char[] { '.' });
}
is not perfect because the value of the partialFieldName parameter could be, for example, "[0] .aaa"
There is already a problem in ASP.NET MVC CodePlex:
GetFullHtmlFieldName should really respect indexers
A "FIX" on this could be:
public static IHtmlString EnsureCorrectCollectionName(this HtmlHelper htmlHelper, string partialName, IHtmlString somethingToBeRendered)
{
//TODO: check http://aspnet.codeplex.com/workitem/5495 to remove this fix
if (partialName.StartsWith("["))
{
string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(partialName);
if (fullHtmlFieldName.EndsWith("." + partialName))
{
string htmlCode = somethingToBeRendered.ToHtmlString();
string nameAttribute = "name=\"" + fullHtmlFieldName + "\"";
var p = htmlCode.IndexOf(nameAttribute);
int endIndex = p + nameAttribute.Length - partialName.Length - 2;
var correctedHtmlCodeStart = htmlCode.Substring(0, endIndex);
var correctedHtmlCodeEnd = htmlCode.Substring(endIndex+1);
return new HtmlString(correctedHtmlCodeStart + correctedHtmlCodeEnd);
}
}
return somethingToBeRendered;
}
Darin Dimitrov suggested using
@Html.EditorForModel()
but how then can the right hidden input "xxx.yyy.zzz.MyCollection.Index" be generated inside the model editor code with value = "% index%"
@model FooViewModel
<tr>
<td>@Html.EditorFor(x => x.SomeProperty)</td>@*rendered as name="xxx.yyy.zzz.MyCollection[%index%].SomeProperty"*@
<td>
@Html.EditorFor(x => x.SomeOtherProperty)@*rendered as name="xxx.yyy.zzz.MyCollection[%index%].SomeOtherProperty"*@
<input type="hidden" name="xxx.yyy.zzz.MyCollection.Index" value="@(%index%)"/>
</td>
</tr>
?
source to share