I have a server-side Blazor application in which a page contains nested components. I had the need for one child to communicate with another. As a simple example, imagine a page for editing a list of widgets. It could contain a component for adding a new widget, and a second for showing a list of widgets. When a new widget were added, the list component should be notified.
Whilst it’s pretty easy to use the EventHandler
What would be nice is a way to decouple the whole thing, and have a mechanism whereby a component could send out a message, not caring if any other component picks it up or not. Other components, wherever they were in the UI tree, could hook up to receive such messages, and not care where they came from. You could rearrange your hierarchy as much as you liked, and things would just carry on working as before.
If you’ve done any MVVM, this probably all sounds very familiar, as it’s basically how messaging works. Your NewWidget class could broadcast a message, and your Widgetlist class could pick it up. Neither needs to know anything about the other, and either could be moved around without breaking any coupling.
So, how do we do this in server-side Blazor? Given that the code is all running in one place (unlike Razor pages or MVC, where messages can’t be used, as each request creates new instances of the classes, and then disposes of them when the rendering is over), we should be able to implement a messaging system.
Having spent some hours trying to get someone’s Nuget package working (no names, as it’s a nice package, I just couldn’t get this feature to work), I decided to have a go at it myself. This turned out to be very simple. The main thing you need is a central class that will act as the messenger, receiving requests to send out messages and then broadcasting them.
If you are a Grown Up Programmer(TM), you would probably name this class something like MessengerFactory, or something dead good like that. If you’re a big kid like me, you’d pick a really silly name like SoupDragon! Why not? It’s as good as anything, and brings back happy childhood memories.
public class SoupDragon { public Action<Widget> NewWidget; public void RaiseNewWidget(Widget w) => NewWidget?.Invoke(w); }
This class contains code for a single message, namely a new widget. You could add as many of these as you liked of course.
In order to ensure that all components see the same instance, so all receive the messages, we need to inject this class as scoped, so in the ConfigureServices method in Startup.cs, we add the following line…
services.AddScoped<SoupDragon>();
The component that creates widgets then needs to inject the SoupDragon, and when a new widget is created, ask it to broadcast the message…
OnNewWidget.Invoke(null, _widget);
The component that shows the list would also inject the SoupDragon, and hook up to the event. One point to watch is that Blazor components often have OnInitialized called more than once, which can lead to you wiring up to the message multiple times. We’re also not unsubscribing, which can cause extra messages to be received. To avoid this, have your component implement IDisposable, and wire up as follows…
private void NewWidget(Widget w) { Widgets.Add(w); StateHasChanged(); } protected override void OnInitialized() => SoupDragon.NewWidget += NewWidget; public void Dispose() => SoupDragon.NewWidget -= NewWidget;
That’s it!
There is a sample project on GitHub that you can download and try.
SignalR
The alert amongst you may have noticed that if you change the SoupDragon from being scoped to singleton…
services.AddSingleton<SoupDragon>();
…then the messages will be broadcast to every user, not just the current one. You can see the difference by opening two browser windows and sending a message from one. If the soup dragon is scoped, then neither window will show widgets created in the other one. However, if soup dragon is a singleton, then creating a widget in one window will cause it to show up in the other.
Guess what? We just re-invented SignalR in a few lines of code! OK, so let’s be honest, we didn’t reinvent SignalR, we utilised the fact that Blazor server-side already uses it. However, the point is that we get (almost) all of the features we need very simply.
If you look at the MSDN page on using SignalR in Blazor, you’ll see that they use the Microsoft.AspNetCore.SignalR.Client Nuget package, and have you adding a hub, then wiring up hub events in your components and so on. It’s a lot of code that most people just don’t need. The vast majority of use cases for SignalR involves little more than blasting out messages, which is what we are doing here.
Now don’t get me wrong, SignalR is fantastic, it’s just that as server-side Blazor is built on it, you probably don’t need to add it again yourself. You can just use a soup dragon and save wads of code.
If you do decide to use this approach to sending out messages to all scopes, you can make the code even simpler, as you can use a static class instead of an injected dependency. For example, we can create a StaticChicken (no, I haven’t been drinking, I just have a weird sense of humour!) as follows…
public static class StaticChicken { public static Action<Widget> NewWidget; public static void RaiseNewWidget(Widget w) => NewWidget?.Invoke(w); }
This can be used exactly as before, but without the need to register it with the dependency container, and without needing to inject it into your components.
If you look at the sample code, you’ll see that the NewWidget component has two buttons, one for adding a widget using the scope-injected SoupDragon, and one for adding widgets created using the static (and so not injected at all) StaticChicken. If you open the same page in two browser windows (or tabs), then any widgets you create when clicking the soup dragon button will only be shown in that window, whereas widgets created with the static chicken button will show in both windows.
A note about naming conventions
Before I get into any trouble here, I should point out that if you work for someone else, it’s probably not a good idea to name a class SoupDragon, unless it really does represent a soup dragon of course! Hmm, that conjures up all sorts of interesting ideas for an application! Anyway, under normal circumstances, it’s not the sort of name that pointy-haired managers like to see, so maybe you should use MessengerFactory, MessageBroker or whatever, and make them think you’re dead grown up! Not that I ever managed to fool anyone though ?
Be First to Comment