Implementing Broadcasting in Blazor

Introduction

So, here's the deal with Blazor: it doesn't come with a built-in broadcasting model. What does that mean? Well, it means that components that don't have any direct relationship can't really chat with each other. Imagine if your components were at a party but couldn't mingle unless they were already friends or family.

This is where this article comes in; we're here to change that. We're going to implement our own broadcasting system in Blazor. We are inviting all the components on a blind date and making them have a conversation, regardless of whether they know each other or not.

By doing this, we're opening up a whole new world of communication for our components.

The Test app

Alright, picture this: we have three amigos — Sender, DashboardOne, and DashboardTwo. Sender's job is to let the user add a programming language and send them out. Meanwhile, DashboardOne and DashboardTwo eagerly wait to catch these details and display them.

The Demo

The Detailed Video: https://www.youtube.com/watch?v=9w6eZ6T7z54

JavaScript Initializer

First things first, we need a JavaScript initializer file. JavaScript initializers execute logic before and after a Blazor app loads. There is one norm that we need to follow while adding this file: {NAME}.lib.module.js, where the {NAME} is the assembly name. You can learn more about it from ASP.NET Core Blazor startup | Microsoft Learn.

export function afterStarted(blazor) {
    blazor.registerCustomEventType('Broadcast', {
        createEventArgs: event => {
            let e = {
                data: globalData
            };
            return e;
        }
    });
}

Code snippet 1. Test.lib.module.js

The function gets triggered after the Blazor application is loaded in the browser. We are setting up a custom event type named 'Broadcast'. When this event is triggered, Blazor will use 'createEventArgs' to generate the event arguments. We're setting the 'data' to a global variable called 'globalData'.

Custom Event Handling in C#

In order to proceed, we need to define an event handler class named 'EventHandlers' and an event argument class named 'BroadcastEventArgs'. These classes will facilitate the handling and passing of events within our application.

using Microsoft.AspNetCore.Components;

namespace Test.Pages
{
    [EventHandler("onBroadcast", typeof(BroadcastEventArgs))]
    public static class EventHandlers
    {
    }

    public class BroadcastEventArgs : EventArgs
    {
        public string data { get; set; }
    }
}

Code snippet 2. EventHandler and EventArgs class

The EventHandlers class is decorated with an EventHandler attribute specifying the type of events it handles, 'onBroadcast', and the type of event arguments it expects, 'BroadcastEventArgs'. This establishes the groundwork for allowing HTML elements to have a C# custom event named 'onBroadcast'.

The BroadcastEventArgs class defines a property named 'data' of type string. This property represents the data associated with the event. In the provided code snippet (Code Snippet 1), you'll notice that the name of the event argument is 'data'.

Broadcasting.js

This JavaScript code sets up a BroadcastChannel. It defines functions responsible for sending and receiving broadcasted messages.

const channel = new BroadcastChannel("Rikam");
let globaldata = '';

// Sending a broadcast
function sendBroadcast(data) {
    console.log("Broadcast message sent -->", data);
    channel.postMessage(data);
};

// Receiving a broadcast
channel.onmessage = function (event) {
    console.log("Broadcast message received <--", event.data);
    receiveBroadcast(event.data);
};

// Handling a received broadcast
function receiveBroadcast(data){
    const element = document.getElementById("broadcastingReceiver");
    globaldata = data;
    let e = new CustomEvent("Broadcast", {
        bubbles: true,
        detail: { data: () => globaldata }
    });
    element.dispatchEvent(e);
}

Code snippet 3. Broadcasting.js

  • Setting up a BroadcastChannel: The BroadcastChannel object establishes a communication channel named "Rikam", enabling broadcasting.
  • Sending a Broadcast: The sendBroadcast function takes data as a parameter and sends it as a broadcast message through the channel using postMessage().

The channel.onmessage event handler listens for incoming broadcast messages. When a message is received, it calls the receiveBroadcast function with the received data.

Handling a Received Broadcast: The receiveBroadcast function is responsible for handling the received broadcast message. It retrieves the HTML element with the id "broadcastingReceiver", updates the globaldata variable with the received data, creates a custom event named "Broadcast" (as seen in listing 1) with the received data as the detail, and dispatches the custom event to the specified HTML element, allowing it to react to the broadcasted message.

Before moving on to the next part, attention is drawn to line number 18 in code snippet 3. An event is sent to the HTML elements with the id "broadcastingReceiver", so you need to add the following code snippet as part of the receiver(s) dashboard.

<div id="broadcastingReceiver" @onBroadcast="ReceivedMessage" />

Code snippet 4. Element with id="broadcastingReceiver"

The Programming Language

Our UI has a form that adds a programming language; let's start by adding a model for that.

public class ProgrammingLanguage
{
    public string Name { get; set; }
    public string Creator { get; set; }
    public int Year { get; set; }
}

Code snippet 5. ProgrammingLanguage.cs

The Components


1. The Sender

This component captures user input for programming language details and sends this data as a broadcast message to other components. For the UI, we have form inputs for entering the details of a programming language: name, creator, and year. There's also a button labeled Save, which triggers the SaveLanguageAsync method when clicked. This method serializes the ProgrammingLanguage object into a JSON string and then invokes a 'sendBroadcast' function from code snippet 3.

@page "/"

@using Newtonsoft.Json;
@inject IJSRuntime JS;

<div>
    <div>
        <h3>Add a Language</h3>
        <div>
            <label>Name:</label>
            <input type="text" @bind="Language.Name" id="name" />
        </div>
        <div>
            <label>Creator:</label>
            <input type="text" @bind="Language.Creator" id="creator" />
        </div>
        <div>
            <label for="year">Year:</label>
            <input type="text" @bind="Language.Year" id="year" />
        </div>
        <div>
            <button @onclick="SaveLanguageAsync">Save</button>
        </div>
    </div>
</div>

@code{
    private ProgrammingLanguage Language = new ProgrammingLanguage();

    private async void SaveLanguageAsync()
    {
        var obj = JsonConvert.SerializeObject(Language);
        await JS.InvokeVoidAsync("sendBroadcast", new object[] {obj});
    }
}

Code snippet 6. Sender.razor

2. The Receiver: DashBoard

First thing I want you to notice is line number 28 from the following code snippet. There is a div element with id="broadcastingReceiver". If you recall code snippet 4, this div element serves as a receiver for broadcasted messages. It uses the @onBroadcast event from code snippet 2, and we're handling this event by invoking the ReceivedMessage method. It deserializes the broadcasted data into a ProgrammingLanguage and then adds it to the list of ProgrammingLanguages, which is then displayed by the table used in the UI.

@page "/dashboard"

@using Newtonsoft.Json;

<h3>Programming Languages:</h3>

<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Creator</th>
            <th>Year</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var language in ProgrammingLanguages)
        {
            <tr>
                <td>@language.Name</td>
                <td>@language.Creator</td>
                <td>@language.Year</td>
            </tr>
        }
    </tbody>
</table>

<div id="broadcastingReceiver" @onBroadcast="ReceivedMessage" />

@code {

    private List<ProgrammingLanguage> ProgrammingLanguages = new();

    private void ReceivedMessage(BroadcastEventArgs args)
    {
        var receivedLanguage = JsonConvert.DeserializeObject<ProgrammingLanguage>(args.data);
        ProgrammingLanguages.Add(receivedLanguage);
    }
}

Code snippet 7. Dashboard.razor

3. The Second Receiver: DashBoardTwo

Since we need to have two receivers, let's add another Dashboard and call it Dashboardtwo.razor with the route to "/dashboardtwo"

@page "/dashboardtwo"
...
<div id="broadcastingReceiver" @onBroadcast="ReceivedMessage" />
@code {
...
}

Code snippet 8. Dashboardtwo.razor

In following snippet, I am adding a second programming language, C++, and in the console, you can see that it is being broadcasted with its data.

Image 1. Broadcasting a programming language

On the other end of the spectrum, at the receiver's end, I am receiving that object and displaying it on the screen.

Image 2. Received broadcasted a programming language

Conclusion

Broadcasting in Blazor bridges the gap between communicaiton of unrelated components, By creating components like the Sender and Receiver, setting up a BroadcastChannel, and handling events effectively, we've enabled seamless communication between different parts of the application. This approach enhances interactivity. I hope you learned something new today. See you at the next one.


Similar Articles