Client – server event aggregation with SignalR ASP.NET Core

Event aggregation is really a pattern i like, there is something elegant about firing events and subscribers can listen to them without knowing who fired them. This creates decoupled domains both in backends and frontends. Back in 2013, at my customer of that time we saw an increasing demand for pub / sub and immediate update. We fired events on the backend event bus and in the frontend we had SignalR Hubs that picked up the events and forwarded them to the clients. This caused duplicated and similar code both on the client and on the web server. I decided to create an abstraction layer between SignalR and the event aggregator. The result is a library called SignalR.EventAggregatorProxy

It has now been ported to support ASP.NET Core, this article will focus on the changes, for the orginal article go here.

Bootstrap

ASP.NET Core have the new Startup.cs that unifies all your bootstrapping needs. First from ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddSignalREventAggregator()
        .AddSignalR()
}

This will hook up all of the libraries different dependencies. Second;

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app
      .UseEventProxy()
      .UseSignalREventAggregator();
}

This will hook up the event proxy script using a custom middleware and also the SignalR hub. Notice that we have removed the generic argument. This was earlier used to identify which event types to proxy out to the clients. This is deprecated, now you instead implement IEventTypeFinder which will supply the library with all the events. We now use the built in Core DI for all custom dependencies, hook up your finder using this.

services
    .AddSignalREventAggregator()
    .AddSingleton<IEventTypeFinder, EventTypeFinder>();

 

Proxy

The event proxy that proxies your events into my library haven’t seen much change, but its now async.

public class EventAggregatorProxy : IEventAggregator, public class EventAggregatorProxy : IEventAggregator, IHandle<Contracts.Events.Event>
{
    private Action<object> handler;
 
    public EventAggregatorProxy(Caliburn.Micro.IEventAggregator eventAggregator)
    {
        eventAggregator.Subscribe(this);
    }
 
    public void Subscribe(Func<object, Task> handler)
    {
        this.handler = handler;
    }
 
    public async Task Handle(Contracts.Events.Event message)
    {
        if (handler != null) //Events can come in before the subscriber is hooked up
            await handler(message);
    }
}

Hook up your proxy using the Core DI.

services
    .AddSignalREventAggregator()
    .AddSingleton<IEventAggregator, EventAggregatorProxy>()

Client side library

As said earlier, make sure that you have registered the event proxy script

app.UseEventProxy()

Also include it client side

<script src="/eventAggregation/events" asp-append-version="true"></script>

The client side library is installed using nupgk, right click web project and select Add client side library. Select nupkg from the dropdown and in the searchbox write signalr.eventaggregatorproxy@2 and you should see all versions. Select the latest one. Chose files and choose the client libraries files. Select location and press install.

nupkg

.NET Client

The library that has seen most change is the .NET Client. First a completely new bootstrap mechanism. We use the core service collection.

serviceProvider = new ServiceCollection()
    .AddSignalREventAggregator()
    .WithHubUrl("http://localhost:60976/EventAggregatorProxyHub")
    .OnConnectionError(e => Debug.WriteLine(e.Message))
    .Build()

It also has some callbacks etc you can listen to.

serviceProvider = new ServiceCollection()
    .AddSignalREventAggregator()
    .WithHubUrl("http://localhost:60976/EventAggregatorProxyHub")
    .OnConnectionError(...)
    .OnSubscriptionError(...)
    .OnConnected(...)
    .Build() 

Just like the backend the client no longer has a generic argument to specify its events, instead implement the client side version of IEventTypeFinder and register it with the DI. Both your backend and client should share the same Assembly containing the events. This way you ensure that the fully qualified name is same in both cases.

Other than that its mostly the same. I did change the constraint hookup a bit to better support proper constructor injection. You now build the constraints like.

public MyViewModel(IProxyEventAggregator eventAggregator) 
{
   eventAggregator.Subscribe(this, builder => builder.Add<MyEvent, MyEventConstraint>(new MyEventConstraint{ Id = 100 }));
}

This overload is only available on IProxyEventAggregator former IEventAggregator<TBaseEvent>

Install using nuget

Server package
Install-Package SignalR.EventAggregatorProxyCore

Constraint part only (Good if you dont want dependency to SignalR in Backend etc)
Install-Package SignalR.EventAggregatorProxy.ConstraintCore

.NET Client
Install-Package SignalR.EventAggregatorProxy.Client.DotNetCore

Demo

You can find a Core demo here

Project site 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 )

Facebook photo

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

Connecting to %s