Case: I have a list of items of Class X displayed using Editor Template for Class X.
Problem: How can I get index of an item being processed on the inside of the Editor Template?
Case: I have a list of items of Class X displayed using Editor Template for Class X.
Problem: How can I get index of an item being processed on the inside of the Editor Template?
I've been using this HtmlExtension that returns only the needed id of an iteration. It's basically a regex on ViewData.TemplateInfo.HtmlFieldPrefix
that's capturing the last number.
public static class HtmlExtensions
public static MvcHtmlString Index(this HtmlHelper html)
{
var prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var m = Regex.Match(prefix, @".+\[(\d+)\]");
if (m.Success && m.Groups.Count == 2)
return MvcHtmlString.Create(m.Groups[1].Value);
return null;
}
}
Can be used in an EditorFor-template like this:
@Html.Index()
Use a for loop instead of for each and pass the indexer into the EditorFor
extension; razor should handle the rest.
@for(var i = 0; i < Model.count(); i++)
{
@Html.EditorFor(m => Model.ToArray()[i], new { index = i })
}
Update:
pass in the the index of the item using view data as show above.
In your editor template access the item via the ViewBag
<span> Item Index: @ViewBag.index </span>
Using the EditorTemplate is the best solution when viewing models that contain a list of something.
In order to find the index for the sub-model being rendered you can use the property that Razor sets by default:
ViewData.TemplateInfo.HtmlFieldPrefix
Say, for example, you have the following view models:
public class ParagraphVM
{
public int ParagraphId { get; set; }
public List<LineVM> Lines { get; set; }
}
and
public class LineVM
{
public int Id { get; set; }
public string Text {get; set;}
}
and you want to be able to edit all the "LineVM" within a "ParagraphVM". Then you would use an Editor Template so you would create a view at the following folder (if it doesn't exist) with the same name as the sub-model Views/Shared/EditorTemplates/LineVM.cshtml
:
@model MyProject.Web.MVC.ViewModels.Paragraphs.LineVM
@{
//this will give you the List's element like Lines[index_number]
var field = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<div id="@field">
@Html.EditorFor(l => l.Text)
</div>
Assuming you have a Controller's ActionResult that is returning a View and passing a ParagrapghVM viewmodel to a view, for example Views/Paragraph/_Paragraph.cshtml
:
@model MyProject.Web.MVC.ViewModels.Paragraphs.ParagraphVM
@using (Html.BeginForm("Details", "Paragraphs", FormMethod.Post))
{
@Html.EditorFor(p => p.Lines)
}
This view would render as many editors for the list Lines as items contains that list. So if, for example, the property list ParagraphVM.Lines contains 3 items it would render something like:
<div id="#Lines[0]">
<input id="Lines_0__Text name="Lines[0].Text"/>
</div>
<div id="#Lines[1]">
<input id="Lines_1__Text name="Lines[1].Text"/>
</div>
<div id="#Lines[2]">
<input id="Lines_2__Text name="Lines[2].Text"/>
</div>
With that you can know exactly what position each items is within the list and for example use some javascript to create a carousel or whatever you want to do with it. But remember that to edit that list you don't really need to know the position as Razor takes care of it for you. If you post back the model ParagraphVM, the list Lines will have the values bound (if any) without any additional work.
How about:
@using System
@using System.Text.RegularExpressions
var i = Convert.ToInt32(Regex.Matches(
ViewData.TemplateInfo.HtmlFieldPrefix,
@"\[([0-9]+)?\]")[0].Groups[1].ToString());
You can use @Html.NameFor(m => m.AnyField). That expression will output the full name property including the index. You could extract the index there...
I think the easiest way is:
@Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"(?!\[)\d+(?=\])")
Or as helper:
public static string Index(this HtmlHelper html)
{
Match m = Regex.Match(html.ViewData.TemplateInfo.HtmlFieldPrefix, @"(?!\[)\d+(?=\])");
return m.Success ? m.Value : null;
}
Inspired by @Jona and @Ryan Penfold