Search code examples
c#html.net-corerazorsyntax

.Net Core 5.0 .cshtml Razor Pages "if" Block Open HTML Tag Without Closing It


I'm not sure I quite have the vocabulary to asking this question properly, I hope the title of this question is worded correctly ... either way, I'm trying to make a table on a .cshtml Razor page using .Net Core 5.0 (Visual Studio 16.10.0). I'm using Entity Framework Core 5.0.11, though I'm not sure that's relevant to this.

I'm pulling data from the database, and what I want to do is build a table with two columns. So, I want the first item I pull to be in the first column, the second item I pull to be in the Second column, the third item I pull to be in the first column (now underneath item one), the fourth item I pull to be in the second column (now under item 2) and so on and so forth.

I'm trying to produce a two column table with an arbitrary amount of items.

(EDIT: Sorry if this table looks all messed up. When I'm in the edit page it looks like I want it to, but when I'm looking at my question on the final page it looks just like the raw text. Just wanted to include that I'm aware of this but not sure what to do about it. Or, maybe it's just my screen and it looks great on yours. Fingers crossed.)

Like this:

Header (but ignore headers in the actual code) Another Header (it just says a header row is required here)
Item 1 Item 2
Item 3 Item 4

Now, here's the code that I'm trying to use, but it doesn't seem to be working:

@if (Model.MyListOfItems is not null) // I can verify that it is not null
{
<table>
    <tbody>
        @{ int count = 0; }
        @foreach (var item in Model.MyListOfItems)
        {
            count++;
            @if (count % 2 == 1)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(i => item.Text)
                    </td>
            }
            else
            {
                    <td>
                        @Html.DisplayFor(i => item.Text)
                    </td>
                </tr>
            }
        }
        @if (count % 2 == 1)
        {
            </tr>
        }
    </tbody>
</table>
}

Now, you can see what I'm doing, right? For odd numbered items, open up a new table row. For even numbered items, close it. If we end on an odd numbered item, we'll have a row open, so that last if statement there closes it up. This way, we get a table like I described above.

However, this doesn't work. It won't compile. I get errors like ; expected for the very first line of the file. It starts complaining about code that comes before this code. I start getting a bunch of such and such doesn't exist in this current context

But here's the thing... I know the rest of the code is good, because if I change it to this

@if (Model.MyListOfItems is not null) // I can verify that it is not null
{
<table>
    <tbody>
        @{ int count = 0; }
        @foreach (var item in Model.MyListOfItems)
        {
            count++;
            @if (count % 2 == 1)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(i => item.Text)
                    </td>
                </tr> <!-- Added this -->
            }
            else
            {
                <tr> <!-- Added this -->
                    <td>
                        @Html.DisplayFor(i => item.Text)
                    </td>
                </tr>
            }
        }
        @if (count % 2 == 1)
        {
            <!-- </tr> --> <!-- Removed This -->
        }
    </tbody>
</table>
}

then it works and compiles just fine. The table obviously doesn't look how I want it to look, but I'm including this to illustrate that I know it's not a problem with my code, or even with the rest of my code, it's the compiler not seeing the end of that <tr> tag in the opening if statement and then not recognizing the } else { as C# code (and because of that is throwing errors all over the page).

I know the compiler is trying to save me from myself, but if I can just force it to compile I'm... at least fairly confident that the table will be properly formed HTML. Even if it's not, if I can get it to compile then I can see what kind of table this code does produce and fix it from there. But, the compiler clearly doesn't appreciate me opening a tag within an if block and then not closing it.

I've run into similar problems to this before, which is also why I'm confident that it's the compiler and not me, but usually I just find some other way to do it. And I'm sure I'll do that this time, I'm going to start trying as soon as I send this, but I figured I'd check with y'all anyway; I figure there must be something I'm missing.

Also, yes, I tried changing else to @else, but that didn't solve the problem, just started throwing errors saying that @else wasn't allowed there.

So, I think, that's a long way around asking what I'm trying to ask, but I think all the exposition was necessary. Anyway, I suppose here's the question:

How do I force the compiler to recognize my closing brackets as code when I open an HTML tag within that block, but don't close the HTML tag?

(EDIT: Added an explicit <tbody> to the table as per response from @Dai)


Solution

  • You can chunk your list of items so that you can iterate through your list of sublists like this:

    <table>
        <tbody>
        @{
            var listOfSubLists = Model.MyListOfItems
                                    .Select((x, i) => new { Index = i, Value = x })
                                    .GroupBy(x => x.Index / 2)
                                    .Select(x => x.Select(v => v.Value).ToList())
                                    .ToList();
        }
        @foreach (var subList in listOfSubLists)
        {
            <tr>
                @foreach (var item in subList)
                {
                    <td>
                        @Html.DisplayFor(i => item.Text)
                    </td>
                }
            </tr>
        }
        </tbody>
    </table>
    

    In this way, you can then use the <table> tag. I think this is a much proper approach than creating the table with the Bootstrap col- class in UI.

    And also .NET6 LINQ will support a built-in chunk method that allows you to chunk your list into a single line of code:

    var listOfSubLists = Model.MyListOfItems.Chunk(2);