Group list items into pages, rows and columns I have a list of labels for printing. Now I have go over the list and print the labels. The layout is specified by how many labels per page and per row on a page I can print.

@{
    var labels = new List&lt;int&gt; { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
    var labelsPerPage = 10;
    var labelsPerRow = 2;
    var labelCount = labels.Count;
}

@for (var i = 0; i &lt; labelCount; i++) {
    if (i % labelsPerPage == 0) {
        @Html.Raw("&lt;table class=\"page\"&gt;")
    }

    if (i % labelsPerRow == 0) {
        @Html.Raw("&lt;tr&gt;")
    }

    &lt;td class="label"&gt;@PrintLabel(labels[i])&lt;/td&gt;

    if (i % labelsPerRow == (labelsPerRow - 1)) {
        @Html.Raw("&lt;/tr&gt;")
    }

    if (i % labelsPerPage == (labelsPerPage - 1)) {
        @Html.Raw("&lt;/table&gt;")
    }
}

@helper PrintLabel(int label) {
    @label
}


Is there a better method (eg. using LINQ or nested for/foreach loops) for going through the list and doing something at the start and end of each page and each row?

I would prefer a nested structure to a flat one so it can be used inside a Razor template. The flat version fails here because Razor requires HTML tags to be closed at the end of a code block.

I'm not looking for performance here, I'd rather have more readable code and possibly getting rid of any Html.Raw() calls.
You're closing tags only when a row or table is full. Is data always going to fit the table size?


if (i % labelsPerRow == (labelsPerRow - 1)) {
    @Html.Raw("&lt;/tr&gt;")
}

if (i % labelsPerPage == (labelsPerPage - 1)) {
    @Html.Raw("&lt;/table&gt;")
}



You could make a functional helper and use that instead. Notice that after the loop, the row and table are ensured to be closed. The purpose is to allow the consumer to pick how to build a TAggregate from TSource items and observer methods. We're observing every item, table start/end and row start/end.

public static class GridRenderer
{
    public static TAggregate Render&lt;TSource, TAggregate&gt;(IEnumerable&lt;TSource&gt; source, TAggregate seed, int pageSize, int rowSize,
        Func&lt;TSource, TAggregate, TAggregate&gt; itemObserver,
        Func&lt;TAggregate, TAggregate&gt; beginPageObserver,
        Func&lt;TAggregate, TAggregate&gt; endPageObserver,
        Func&lt;TAggregate, TAggregate&gt; beginRowObserver,
        Func&lt;TAggregate, TAggregate&gt; endRowObserver)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (pageSize &lt;= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
        if (rowSize &gt; pageSize) throw new ArgumentOutOfRangeException(nameof(rowSize));
        // .. check observers for null

        var items = source.ToList();
        var result = seed;

        if (!items.Any()) return result;

        for (var i = 0; i &lt; items.Count; i++)
        {
            if (i % pageSize == 0)
            {
                result = beginPageObserver(result);
            }

            if (i % rowSize == 0)
            {
                result = beginRowObserver(result);
            }

            result = itemObserver(items[i], result);

            if ((i + 1) % rowSize == 0)
            {
                result = endRowObserver(result);
            }

            if ((i + 1) % pageSize == 0)
            {
                result = endPageObserver(result);
            }
        }

        if (items.Count % rowSize != 0)
        {
            result = endRowObserver(result);
        }

        if (items.Count % pageSize != 0)
        {
            result = endPageObserver(result);
        }

        return result;
    }
}


Let's say your data does not fit the table size and row size.

var renderer = new StringBuilder();
GridRenderer.Render(new[] {1, 2, 3, 4, 5}, renderer, 4, 2,
    (item, cur) =&gt; cur.AppendLine($"    &lt;td class=\"label\"&gt;{item}&lt;/td&gt;"),
    cur =&gt; cur.AppendLine("&lt;table class=\"page\"&gt;"),
    cur =&gt; cur.AppendLine("&lt;/table&gt;"),
    cur =&gt; cur.AppendLine("  &lt;tr&gt;"),
    cur =&gt; cur.AppendLine("  &lt;/tr&gt;"));
var layout = renderer.ToString();


The grid still gets created correctly.

&lt;table class="page"&gt;
  &lt;tr&gt;
    &lt;td class="label"&gt;1&lt;/td&gt;
    &lt;td class="label"&gt;2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td class="label"&gt;3&lt;/td&gt;
    &lt;td class="label"&gt;4&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;table class="page"&gt;
  &lt;tr&gt;
    &lt;td class="label"&gt;5&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;


Note I've used a StringBuilder, but you could also use any other class to render the output. You could also change the flow a bit if you want an empty table when no data is available.