05 November 2023

.NET 8 Blazor: How to APPEND content to the Head section, rather than replacing it

The HeadContent section is handy but doesn't allow you to append content in both the MainLayout and Page. But there is a neat workaround.

The HeadContent section in .NET Blazor is a simple way to specify content for the <Head> section of the HTML page from either MainLayout or Page level. Unfortunately, if you add a HeadContent tag in the page, it will overwrite any head content in the MainLayout.razor. Basically, you cannot add head content from both.

In our opinion, the functionality should APPEND content rather than replace it. For example, in MainLayout.razor you may want to add CSS references for a particular section of the site. And then within the page itself, you may want to specify meta-tags, and a canonical tag.

The PageTitle works in a similar way, but that makes more sense as you would likely want any page title specified at the page level to overwrite a more general one from the MainLayout.razor. But it doesn't really seem appropriate for other head content.

Fortunately in .NET 8, there is a new feature that fairly simply lets us achieve the desired result.

Section Content

The new SectionContent tag functionality provides a simple way to define a content section in your MainLayout.razor, and then fill this with content generated in a child page. So our simple solution is to feed content from the page into a SectionOutlet within the HeadContent of the MainLayout.razor. It makes more sense when you look at the code.

First, make sure you have a HeadOutlet tag in your App.razor.

 <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="CactusoftBlazor.styles.css" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="css/site.min.css" />
    <link rel="icon" type="image/png" href="favicon.ico" />

    <HeadOutlet @rendermode="RenderMode.InteractiveServer" />
</head>

<body>
    <CascadingBlazoredModal>
        <Routes @rendermode="RenderMode.InteractiveServer" />
    </CascadingBlazoredModal>

    <script src="_framework/blazor.web.js"></script>
</body>
</html>

For simplicity, we will add the sections functionality into the _Imports.razor:

 @using Microsoft.AspNetCore.Components.Sections

We then need a HeadContent section in our MainLayout.razor. Into this, we place a SectionOutlet tag:

 <HeadContent>
    <link href="staticfiles/allcruiselinejobs/skin.min.css" rel="stylesheet" />
    <link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
    <link rel="stylesheet" href="./_content/IntlTelInputBlazor/css/intlTelInput.css">
    <SectionOutlet SectionName="HeadContentFromPage" />
</HeadContent>

Finally, in our pages (by which we mean .razor controls that have an @page route specified), we can create head content in a SectionContent control rather than HeadContent.

 <SectionContent SectionName="HeadContentFromPage">
    <link rel="canonical" href="/path-to-page" />
</SectionContent>

This will get rendered within the SectionOutlet of the MainLayout.razor, thus joining the other head content there which gets set to the App.razor to be rendered in the HeadOutlet.

.

Summary

So we took a slightly roundabout route to get there, but we end up being able to add content to the <Head> section of an HTML page from both the MainLayout.razor AND the page, in a way that was not possible in previous .NET versions.

Ooops. Some kind of error occurred. We have logged it.