DEV Community

Dave Brock
Dave Brock

Posted on • Originally published at daveabrock.com on

Blast Off with Blazor: Use .NET 5 to update the HTML head from a Blazor component

So far in this series, we’ve walked through a project intro and also got our feet wet with our first component.

Today, we’re going to take a look at a welcome .NET 5 feature in Blazor: the ability to update your HTML head on the fly without the need for JavaScript interoperability.

When I speak of updating the HTML head, I’m referring to what’s inside the <head> tag in your index.html file. You can now use the following native Blazor components to dynamically update the HTML head:

  • <Title> - the title of the document. You’d use this if you want to update your user on any updates, especially if they are busy with other tabs. You’ve probably seen this with updates for new email messages or new chat notifications.
  • <Link> - gives you the ability to link to other resources. For example, you might want to dynamically update this to change stylesheets when the user clicks a button to specify light or dark mode.
  • <Meta> - allows you to specify a variety of information for things like SEO, social cards, and RSS information.

Since these are individual components, you can definitely use them together in your code. Following the email example, you could update the title bar with an unread message count and also change the “you have a message” favicon appropriately.

In this post, we’re going to dynamically update the HTML <title> tag from our Blazor component. And since we’re already working with the browser’s title bar, I’ll walk you through adding favicons to our site.

This post contains the following content.

Dynamically set page title

As I mentioned, we’re going to dynamically update the <title> tag of our component. Before we do that, we’ll first need to add a package reference, add a script reference, and reference the new package in our project.

Add package reference

From the Client directory of our project, open your terminal and add a package reference using the dotnet CLI.

dotnet add package Microsoft.AspNetCore.Components.Web.Extensions --prerelease
Enter fullscreen mode Exit fullscreen mode

Heads up! At this time, the --prerelease flag is required.

Add script reference

In the index.html file in wwwroot, add the following reference:

<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/headManager.js"></script>
Enter fullscreen mode Exit fullscreen mode

Reference the project

We can use one of two options to reference the new package in our app. Our first option is a direct @using in the Index.razor component:

@using Microsoft.AspNetCore.Components.Web.Extensions.Head
Enter fullscreen mode Exit fullscreen mode

Or, we can include it in our _Imports.razor file. The _Imports.razor file, which sits at the root of our Client project, allows us to reference our app’s imports globally. That way, we won’t have to manually add shared references to all our components that need it. With my new addition, here’s what my _Imports.razor file looks like now:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using Client
@using Client.Shared
@using Data
@using Microsoft.AspNetCore.Components.Web.Extensions.Head
Enter fullscreen mode Exit fullscreen mode

Either method works, but I’m going with the latter option for reusability. We’re now ready to set the page title.

Set the page title

Here’s what we’ll do: while I’m waiting for the NASA Astronomy Picture of the Day (APOD) API call to return, our title bar will say Blasting off…. Then, when we get a response back, it’ll say New! From YEAR!, where YEAR is the year of the image. This value comes from the Year property returned by the API.

In the @code section of our Index.razor component, add the following code to parse the year from our Date property:

private int GetYear(DateTime date)
{
    return date.Year;
}
Enter fullscreen mode Exit fullscreen mode

Then, we can use a one-line ternary statement to return a message based on us whether we get a response from the API.

private string GetPageTitle()
{
    return image is null ? "Blasting off..." : $"New! From {GetYear(image.Date)}!";
}
Enter fullscreen mode Exit fullscreen mode

Now, all that’s left is to drop the <Title> component at the top of our markup, and call our logic to change the title text.

<Title Value="@GetPageTitle()"></Title>
Enter fullscreen mode Exit fullscreen mode

Here’s a GIF of our update in action. (I set a Thread.Sleep(1000) for demonstration purposes, but I don’t recommend it in practice obviously.)

Our title bar in action

How it all works

Before .NET 5, updating the HTML head dynamically required the use of JavaScript interoperability.

You’d likely drop the following JavaScript at the bottom of your index.html:

<script>
    function SetTitle(title) {
        document.title = title;
    }
</script>
Enter fullscreen mode Exit fullscreen mode

Then, you’d have to inject JavaScript interop and probably create a special component for it:

@inject IJSRuntime JSRuntime

// markup here...

@code {
    [Parameter]
    public string Value { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await JSRuntime.InvokeVoidAsync("SetTitle", Value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, you’d add the component on your page to update the title bar dynamically, where your GetPageTitle method would do what it needs to populate the title.

<PageTitle Value="@GetPageTitle()" />
Enter fullscreen mode Exit fullscreen mode

In .NET 5, this still happens—it’s just abstracted away from you. If you take a look at the source code for the <Title> component, it injects an IJSRuntime and takes a Value—just as you’ve done before. Then, in the OnAfterRenderAsync component lifecycle method, it sets the title by calling the headManager.js file, where you’ll see:

export function setTitle(title) {
  document.title = title;
}
Enter fullscreen mode Exit fullscreen mode

Note : The OnAfterRenderAsync method is called after a component has finished rendering.

So, this isn’t doing anything too magical. It’s doing it out-of-the-box and giving you one less component to maintain.

With that done, let’s add favicons to our app.

Add favicons to our app

When you create a Blazor app (or any ASP.NET Core app, for that matter), a bland favicon.ico file is dropped in your wwwroot directory. (For the uninitiated, a favicon is the small icon in the top-left corner of your browser tab, right next to the page title.) This might lead you to believe that if you want to use favicons, you can either use that file or overwrite it with one of your own—then move on with your life.

While you can do that, you probably shouldn’t. These days you’re dealing with different browser requirements and multiple platforms (Windows, iOS, Mac, Android) where just one favicon.ico will not get the job done. For example, iOS users can pin a site to their homescreen—and iOS will grab your favicon icon as the “app” image.

Thanks to Andrew Lock’s wonderful post on the topic, I went over to the RealFaviconGenerator for assistance. (If you want more details on the process, like how to create your own, check out his post.)

Before we do that, we’ll need to pick a new image.

Did you know the .NET team has a branding GitHub repository where you can look at logos, presentation templates, wallpapers, and a bunch of illustrations of the purple .NET bot? For our example, we’ll use the bot using a jetpack because—obviously. We’re blasting off, after all.

Let’s also update our header icon to this image, too. After dropping the file in our wwwroot/images directory, we can edit our NavBar component in our Shared directory to include our new file.

<nav class="flex items-center justify-between flex-wrap bg-black p-6 my-bar">
    <div class="flex items-center flex-shrink-0 text-white mr-6">
        <img src="images/dotnet-bot-jetpack.png">
        <span class="font-semibold text-xl tracking-tight">Blast Off with Blazor</span>
    </div>
    <! -- more html left out for brevity -->
</nav>
Enter fullscreen mode Exit fullscreen mode

I feel better and, as a bonus, the fine folks at NASA don’t have to worry about asking me to take their official logo down.

Our title bar in action

Now, we can head over the RealFaviconGenerator site to upload our new icon. After adjusting any settings to our liking, it’ll give us a bunch of icons, which we’ll unzip to the root of our wwwroot directory.

In index.html in the wwwroot directory, add the following inside the <head> tag:

<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
Enter fullscreen mode Exit fullscreen mode

While it’s not the worst idea to put this markup in a component or partial view, I’m fine with putting it right into my static index.html file—it’s a one-time step and this is the only file that needs it.

What about the tests?

I’ve placed an emphasis on testing our components with the bUnit library. We’ll work on testing this, and the rest of our Index component, in the next post. We need to mock a few things, like HttpClient, and I’m currently researching the best way to do so. Our next post will focus on testing Blazor components with HttpClient dependencies.

Wrap up

In this post, we dynamically updated the HTML head by setting the page title based on the image. Then, we updated our header icon and also showed how to include favicons in a Blazor WebAssembly project.

Stay tuned for the next post where we work on writing tests for our Index component.

Resources

Top comments (0)