This post is part of the second annual C# advent. Two new posts are published every day between 1st December and 25th December.

In this post, I'm going to show you how to build toast notifications for your Blazor/Razor Components applications. By the time we're done you'll be able to show 4 different toasts depending on the level of importance (information, success, warning and error). The best bit, this will all be achieved without using a single line of JavaScript.

Just for clarity, for the rest of this article you can assume when I say Blazor it is interchangeable with Razor Components (server-side Blazor).

For those of you who are new to Blazor and want a bit more info first. You can checkout some of my other posts:


All of the code in this post is available on my GitHub account.


Overview

Before we go any further I just want to give you a quick run down of the solution we will be building. We are going to create a component called Toast which will react to events invoked by a ToastService. The ToastService will be able to be injected into other components in the application. These components can then use it to issue toast messages. Make sense? I hope so, if not, it should all become clear shortly.

Prerequisites

For this post I'm going to be using Visual Studio 2017, you can use VS Code instead but the experience isn't as rich. Meaning, you won't get much, if any, IntelliSense. You will also need to have the following installed.

Creating a new project (optional)

I'm going to start start by creating a new stand alone Blazor project. But feel free to use a different Blazor template or, if you wish, you can add the upcoming code to an existing project.

Building the Toast service

The first thing we need to do is create a new folder called Services and add a couple of bits. The first is an enum called ToastLevels, in here we need to add the 4 different types of toast as follows.

public enum ToastLevel
{
    Info,
    Success,
    Warning,
    Error
}

The second is a new class called ToastService with the following code.

public class ToastService : IDisposable
{
    public event Action<string, ToastLevel> OnShow;
    public event Action OnHide;
    private Timer Countdown;

    public void ShowToast(string message, ToastLevel level)
    {
        OnShow?.Invoke(message, level);
        StartCountdown();
    }

    private void StartCountdown()
    {
        SetCountdown();

        if (Countdown.Enabled)
        {
            Countdown.Stop();
            Countdown.Start();
        }
        else
        {
            Countdown.Start();
        }
    }

    private void SetCountdown()
    {
        if (Countdown == null)
        {
            Countdown = new Timer(5000);
            Countdown.Elapsed += HideToast;
            Countdown.AutoReset = false;
        }
    }

    private void HideToast(object source, ElapsedEventArgs args)
    {
        OnHide?.Invoke();
    }

    public void Dispose()
    {
        Countdown?.Dispose();
    }
}

The ToastService is going to be the glue that binds any component wanting to issue a toast, with the toast component which will actually display the toast. It has a single public method, ShowToast() which takes the string to be shown in the toast and the level of the toast.

The service also has two events, OnShow and OnHide, and a timer, Countdown. Our toast component will subscribe to the events and use them to show and hide itself. The timer is used internally by the service and is set at 5 seconds. When it elapses it invokes the OnHide event.

Building the Toast component

With the toast service sorted we now need to build the toast component, this will work with the service to get toasts on the screen. Unless I'm building a really trivial component or just hacking about I like to keep the logic and markup of my components in separate files.

There are a couple of reasons I do this. The first is because I regularly develop on a Mac using VS Code and support for Blazor isn't available yet. In fact, it was only recently that some built in Razor support was added. So by splitting the component into two files I can get the full IntelliSense experience in my class files where most of my code goes. And I only have to manage with limited support in my markup files.

The second is that I like the separation of markup and logic, it feels neater to me and a better separation of concerns. The engine of my component, the logic, may not have to change that much once I've written it. But I may spent a fair amount of time tweaking the look and feel of the component, especially if I'm reusing it across difference apps. It's almost always going to the be markup I want to change rather than the logic.

Base class for logic - Toast.cshtml.cs

We are going to put as much of our logic in here as possible. It is also important we inherit from the BlazorComponent base class so Blazor knows that this is a component.

public class ToastBase : BlazorComponent, IDisposable
{
    [Inject] ToastService ToastService { get; set; }

    protected string Heading { get; set; }
    protected string Message { get; set; }
    protected bool IsVisible { get; set; }
    protected string BackgroundCssClass { get; set; }
    protected string IconCssClass { get; set; }

    protected override void OnInit()
    {
        ToastService.OnShow += ShowToast;
        ToastService.OnHide += HideToast;
    }

    private void ShowToast(string message, ToastLevel level)
    {
        BuildToastSettings(level, message);    
        IsVisible = true;
        StateHasChanged();
    }

    private void HideToast()
    {
        IsVisible = false;
        StateHasChanged();
    }

    private void BuildToastSettings(ToastLevel level, string message)
    {
        switch (level)
        {
            case ToastLevel.Info:
                BackgroundCssClass = "bg-info";
                IconCssClass = "info";
                Heading = "Info";
                break;
            case ToastLevel.Success:
                BackgroundCssClass = "bg-success";
                IconCssClass = "check";
                Heading = "Success";
                break;
            case ToastLevel.Warning:
                BackgroundCssClass = "bg-warning";
                IconCssClass = "exclamation";
                Heading = "Warning";
                break;
            case ToastLevel.Error:
                BackgroundCssClass = "bg-danger";
                IconCssClass = "times";
                Heading = "Error";
                break;
        }
        
        Message = message;
    }

    public void Dispose()
    {
        ToastService.OnShow -= ShowToast;
    }
}

Hopefully the above makes sense but let's walk through it just to be sure.

To start we are injecting the ToastService into the component. Then we're defining a few properties which will be used in the markup portion of the component.

Next, we're overriding one of Blazors component lifecycle events, OnInit (you can read about Blazors other lifecycle events in this post). In here we're wiring up the events we defined in the ToastService to handlers in the component.

Then we have the event handlers, ShowToast and HideToast. ShowToast takes the message and the toast level and passes them to BuildToastSettings. This then sets various CSS class names, the heading and message. The IsVisible property is then set on the component and StateHasChanged is called. HideToast just sets IsVisible to false and calls StateHasChanged.

You may be wondering what StateHasChanged is and why are we calling it? Let me explain.

A component usually needs to re-render when its state changes, a property value updates for example. When this update comes from within the component itself or via a value passed into the component using the [Parameter] directive, i.e. something the component knows about and can monitor. Then a re-render is triggered automatically.

However, if an update happens to the components state which is from an external source, for example an event. Then this automatic process is bypassed and a manual call has to be made to let the component know something has changed. This is where StateHasChanged comes in.

In our case we are updating the components values based on an external event, OnShow from the ToastService. This means we have to call StateHasChanged to let the component know it needs to re-render.

Now we have our components logic in place let's move onto the markup.

Razor view for markup - Toast.cshtml

@inherits ToastBase

<div class="toast @(IsVisible ? "toast-visible" : null) @BackgroundCssClass">
    <div class="toast-icon">
        <i class="fa [email protected]" aria-hidden="true"></i>
    </div>
    <div class="toast-body">
        <h5>@Heading</h5>
        <p>@Message</p>
    </div>
</div>

With all the logic in the base class it makes the markup file very clean and clear. The only bit of logic left is for adding or removing the toast-visible CSS class. Speaking of which, we also need some styling to go with our markup.

.toast {
    display: none;
    padding: 1.5rem;
    color: #fff;
    z-index: 1;
    position: absolute;
    width: 25rem;
    top: 2rem;
    border-radius: 1rem;
    left: 50%;
}

.toast-icon {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 1rem;
    font-size: 2.5rem;
}

.toast-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    padding-left: 1rem;
}

    .toast-body p {
        margin-bottom: 0;
    }

    .toast-visible {
        display: flex;
        flex-direction: row;
        animation: fadein 1.5s;
    }

@keyframes fadein {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

Putting everything together

We almost have a working toast component, we just need to wire up a couple of things then we should be able to give it a test.

Registering with DI

We need to register our ToastService with Blazors DI container. This is done in the Startup class in the same way you would with any ASP.NET Core application.

We're registering the service as scoped, the reason for this is that it will give the correct behaviour in both Blazor and Razor Components apps. However, it's worth noting that scoped and singleton act exactly the same in Blazor. If you are interested in how the various service lifetimes work in Blazor and Razor Components applications checkout my imaginatively named post, Service lifetimes in Blazor.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ToastService>();
}

Adding the Toast component to the main layout

We also need to add the Toast component into our MainLayout component as follows.

@inherits BlazorLayoutComponent

<Toast></Toast>

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

As well as a link to FontAwesome in the head tag of Index.html.

<head>
    ...
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" integrity="sha384-gfdkjb5BdAXd+lj+gudLWI+BXq4IuLW5IT+brZEZsLFm++aCMlF1V92rMkPaX4PP" crossorigin="anonymous">
    ...
</head>

So we don't have to use full qualified named in our components when injecting the ToastService we can add a using statement to the _ViewImports.cshtml in the root of the application.

@using BlazorToastNotifications.Services

Add toast calls to the index page

The last thing to do is to modify the Index component so we can show off our new toasts.

@page "/"
@inject ToastService toastService

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<button class="btn btn-info" onclick="@(() => toastService.ShowToast("I'm an INFO message", ToastLevel.Info))">Info Toast</button>
<button class="btn btn-success" onclick="@(() => toastService.ShowToast("I'm a SUCCESS message", ToastLevel.Success))">Success Toast</button>
<button class="btn btn-warning" onclick="@(() => toastService.ShowToast("I'm a WARNING message", ToastLevel.Warning))">Warning Toast</button>
<button class="btn btn-danger" onclick="@(() => toastService.ShowToast("I'm an ERROR message", ToastLevel.Error))">Error Toast</button>

The finished result!

With all that in place we should now be able to spin up our app and click each of the buttons and see the 4 different toast messages.

Summary

I hope you have enjoyed reading this post and if you're new to Blazor I hope I've piqued your interest and inspired you to find out more. Here are a few links that are worth checking out.

I think it's really exciting to see how much is already achievable in Blazor. Being able to create notifications like this using just HTML, CSS and C# without having to write a single line of JavaScript is just fantastic. And things are only going to get better as WebAssembly and .NET Core runtimes continue to develop.

All the code for this post can be found on GitHub. I will also be packing this all up into a Nuget package in the next couple of days so you can just install it into your Blazor projects.