84

I'm iterating a List<T> in a razor foreach loop in my view which renders a partial. In the partial I'm rendering a single record for which I want to have 4 in a row in my view. I have a css class for the two end columns so need to determine in the partial whether the call is the 1st or the 4th record. What is the best way of identifying this in my partial to output the correct code?

This is my main page which contains the loop:

@foreach (var myItem in Model.Members){

        //if i = 1
        <div class="grid_20">
        <!-- Start Row -->

        //is there someway to get in for i = 1 to 4 and pass to partial?
        @Html.Partial("nameOfPartial", Model)

        //if i = 4 then output below and reset i to 1
        <div class="clear"></div>
        <!-- End Row -->
        </div>

}

I figure I can create a int that I can update on each pass and render the text no problem here but it's passing the integer value into my partial I'm more concerned about. Unless there's a better way.

Here is my partial:

@{
switch()
case 1:
        <text>
        <div class="grid_4 alpha">
        </text>
break;
case 4:
        <text>
        <div class="grid_4 omega">
        </text>
break;
default:
        <text>
        <div class="grid_4">
        </text>
break;
}

        <img src="Content/960-grid/spacer.gif" style="width:130px; height:160px; background-color:#fff; border:10px solid #d3d3d3;" />
        <p><a href="member-card.html">@Model.Name</a><br/>
        @Model.Job<br/>
        @Model.Location</p>
</div>

Not sure if I'm having a blonde day today and this is frightfully easy but I just can't think of the best way to pass the int value in. Hope someone can help.

lloydphillips
  • 2,775
  • 2
  • 33
  • 58
  • If only there were a way in c# to do a loop with an index... oh wait! There is! http://msdn.microsoft.com/en-us/library/ch45axte.aspx :) – bhamlin Apr 26 '12 at 06:54
  • Creating the index isn't the issue. Passing the index into the partial is the issue. – lloydphillips May 08 '12 at 21:16

11 Answers11

174
 @{int i = 0;}
 @foreach(var myItem in Model.Members)
 {
     <span>@i</span>
     i++;
 }
Community
  • 1
  • 1
noamtcohen
  • 4,432
  • 2
  • 20
  • 14
94
//this gets you both the item (myItem.value) and its index (myItem.i)
@foreach (var myItem in Model.Members.Select((value,i) => new {i, value}))
{
    <li>The index is @myItem.i and a value is @myItem.value.Name</li>
}

More info on my blog post http://jimfrenette.com/2012/11/razor-foreach-loop-with-index/

Jim Frenette
  • 1,599
  • 10
  • 12
11

Or you could simply do this:

@foreach(var myItem in Model.Members)
{    
    <span>@Model.Members.IndexOf(myItem)</span>
}
Manoj De Mel
  • 927
  • 9
  • 16
  • 1
    @RicardoFontana you can always use "`ToList()`" method. Also, the original question was about a `List` – Manoj De Mel Jul 04 '18 at 04:23
  • 7
    Terrible answer. Won't work if you have dupes in the list. Very slow since it searches the list on each iteration. Just bad all around. – jpmc26 Sep 06 '18 at 17:37
  • 1
    @jpmc26 Thank you for pointing out about the duplicate items issue. I was only focusing on the question. There was no mentioned about duplicates. – Manoj De Mel Sep 07 '18 at 04:39
8

Take a look at this solution using Linq. His example is similar in that he needed different markup for every 3rd item.

foreach( var myItem in Model.Members.Select(x,i) => new {Member = x, Index = i){
    ...
}
brightgarden
  • 410
  • 4
  • 12
  • But if I'm passing my Member to the partial how am I going to get the index in there too? I'd assume I'd have to create a separate ViewModel that encapsulates my Member to pass into my partial. Feels icky. Just wondering if there might be a better way? – lloydphillips Apr 26 '12 at 03:43
  • Ok. Yes, that would be one way, or you could use the anonymous object as your model and specify dynamic as the model type. Is there any way to move the switch logic into the parent template so there would be no need to pass in the index value? If you did this, your original arguably less elegant solution would still work. – brightgarden Apr 26 '12 at 13:07
7

You could also use deconstruction and tuples and try something like this:

@foreach (var (index, member) in @Model.Members.Select((member, i) => (i, member)))
{
  <div>@index - @member.anyProperty</div>

  if(index > 0 && index % 4 == 0) { // display clear div every 4 elements
      @: <div class="clear"></div>
  }
}

For more info you can have a look at this link

ppolyzos
  • 6,791
  • 6
  • 31
  • 40
3

Is there a reason you're not using CSS selectors to style the first and last elements instead of trying to attach a custom class to them? Instead of styling based on alpha or omega, use first-child and last-child.

http://www.quirksmode.org/css/firstchild.html

Timothy Strimple
  • 22,920
  • 6
  • 69
  • 76
  • I might have multiple rows so I'd have to create a content div to wrap each row in order to define the first and last child within that row (which would then lead me back to needing an int variable). I see what you are saying though, it's something I'll try to explore. – lloydphillips Apr 26 '12 at 03:40
  • Just so we're clear, you have multiple rows, and multiple columns. Why is this not a table? ;) – Timothy Strimple Apr 26 '12 at 03:45
2

All of the above answers require logic in the view. Views should be dumb and contain as little logic as possible. Why not create properties in your view model that correspond to position in the list eg:

public int Position {get; set}

In your view model builder you set the position 1 through 4.

BUT .. there is even a cleaner way. Why not make the CSS class a property of your view model? So instead of the switch statement in your partial, you would just do this:

<div class="@Model.GridCSS">

Move the switch statement to your view model builder and populate the CSS class there.

Louise Eggleton
  • 969
  • 2
  • 15
  • 27
1

IndexOf seems to be useful here.

@foreach (myItemClass ts in Model.ItemList.Where(x => x.Type == "something"))
    {
       int currentIndex = Model.ItemList.IndexOf(ts);
       @Html.HiddenFor(x=>Model.ItemList[currentIndex].Type)

...

RichC
  • 49
  • 3
1

I prefer to use this extension method:

public static class Extensions
{
    public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
        => self.Select((item, index) => (item, index));
}

Source:

https://stackoverflow.com/a/39997157/3850405

Razor:

@using Project.Shared.Helpers 

@foreach (var (item, index) in collection.WithIndex())
{
<p>
    Name: @item.Name Index: @index
</p>
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

Very Simple:

     @{
         int i = 0;
         foreach (var item in Model)
         {
          <tr>
          <td>@(i = i + 1)</td>`
          </tr>
         }
      }`
phd
  • 82,685
  • 13
  • 120
  • 165
  • The problem with this answer is that assumes the value is being shown inside a table. Also i++ it's better and more efficient than i = i+1; it is useful for someone on this scenario, but some people here are a little too picky. – Gabrielkdc Dec 12 '19 at 02:02
0

In case you want to count the references from your model( ie: Client has Address as reference so you wanna count how many address would exists for a client) in a foreach loop at your view such as:

 @foreach (var item in Model)
                        {
                            <tr>
                                <td>
                                    @Html.DisplayFor(modelItem => item.DtCadastro)
                                </td>

                                <td style="width:50%">
                                    @Html.DisplayFor(modelItem => item.DsLembrete)
                                </td>
                                <td>
                                    @Html.DisplayFor(modelItem => item.DtLembrete)
                                </td>
                                <td>
                                    @{ 
                                        var contador = item.LembreteEnvolvido.Where(w => w.IdLembrete == item.IdLembrete).Count();
                                    }
                                    <button class="btn-link associado" data-id="@item.IdLembrete" data-path="/LembreteEnvolvido/Index/@item.IdLembrete"><i class="fas fa-search"></i> @contador</button>
                                    <button class="btn-link associar" data-id="@item.IdLembrete" data-path="/LembreteEnvolvido/Create/@item.IdLembrete"><i class="fas fa-plus"></i></button>
                                </td>
                                <td class="text-right">
                                    <button class="btn-link delete" data-id="@item.IdLembrete" data-path="/Lembretes/Delete/@item.IdLembrete">Excluir</button>
                                </td>
                            </tr>
                        }

do as coded:

@{ var contador = item.LembreteEnvolvido.Where(w => w.IdLembrete == item.IdLembrete).Count();}

and use it like this:

<button class="btn-link associado" data-id="@item.IdLembrete" data-path="/LembreteEnvolvido/Index/@item.IdLembrete"><i class="fas fa-search"></i> @contador</button>

ps: don't forget to add INCLUDE to that reference at you DbContext inside, for example, your Index action controller, in case this is an IEnumerable model.

Sergio Rezende
  • 762
  • 8
  • 25