SignalR event aggregation with Blazor WASM

As you probably know by now I love event aggregation and being able to seamless forward server side events to clients. My library SignalR.EventAggregatorProxy have been around for a while now and it enables seamless event aggregation between server and client. The .NET core client library just got updated to .NET 5. And as it turns out it works without any effort in Blazor WASM.

In your WASM client project add nuget package SignalR.EventAggregatorProxy.Client.DotNetCore. Next up we need to config the event aggregator client and this is done using the standard service pipeline from the WASM client app Program.cs.

builder.Services.AddSignalREventAggregator()
    .WithHubUrl($"{builder.HostEnvironment.BaseAddress}EventAggregatorProxyHub")
    .OnConnectionError(e => Debug.WriteLine(e.Message))
    .Build()
    .AddSingleton<IEventAggregator>(p => p.GetService<IProxyEventAggregator>())
    .AddSingleton<IEventTypeFinder, EventTypeFinder>()
    .AddTransient<EventsViewModel>()
    .AddTransient<SendMessageViewModel>();

This code is pretty much identical to the standalone .NET 5 desktop configuration, which on its own is pretty awesome. First I point out the url to the SignalR hub. I also add IEventAggregator so that I can publish and subscribe to events from the viewmodels. We also point out the custom IEventTypeFinder which tells the library which events are available to it. What’s cool is that we can share the same events contracts assembly between Blazor WASM and the server, this means with zero effort we get a strongly typed contract between server and client, something that’s not as easy with classic javascript/typescript web solutions. Finally I register my viewmodels for the demo application.

Subscribing to events are done exactly like the vanilla desktop library

public class EventsViewModel : IHandle<StandardEvent>, IHandle<GenericEvent<string>>, IHandle<ConstrainedEvent>, IHandle<ClientSideEvent>
{
    public EventsViewModel(IProxyEventAggregator eventAggregator)
    {
        Events = new List<IMessageEvent<string>>();
        eventAggregator.Subscribe(this, builder =>
            builder.For<ConstrainedEvent>()
                .Add(new ConstrainedEventConstraint {Message = "HelloWorld"}));
    }


    public List<IMessageEvent<string>> Events { get; }
    public Action StateHasChanged { get; set; }

    public void Handle(StandardEvent message)
    {
        Add(message);
    }

    public void Handle(GenericEvent<string> message)
    {
        Add(message);
    }

    public void Handle(ConstrainedEvent message)
    {
        Add(message);
    }

    public void Handle(ClientSideEvent message)
    {
        Add(message);
    }

    private void Add(IMessageEvent<string> message)
    {
        Events.Add(message);
        StateHasChanged();
    }
}

We have come far in web development when we can use conventions like these. You implement interface IHandle<TEvent> and it will get called when server publishes such a message. From the constructor you subscribe to events. We also have a mechanic to constraint events, in this case the client will only receive events of type ConstrainedEvent when property Message = HelloWorld.

Finally we inject the view models into the razor view using DI.

@inject SendMessageViewModel SendMessage
@inject EventsViewModel Events

Used like

@foreach (var message in Events.Events)
{
<div>@message.Message</div>
}

Since events can come in at any time we need to call StateHasChanged to ensure that UI is updated correctly. Since we update the event collection from a injected service we need to hook up a reference to the razor view StateHasChanged.

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();

        Events.StateHasChanged = StateHasChanged;
    }
}

Not very elegant, I have started a feature request ticket for adding mechanics to inject component context and be able to call StateHasChanged from here.

Full demo code found here

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s