Wim Deblauwe
Wim Deblauwe

Reputation: 26858

How to combine an EventHandler with a SignalR hub with .NET core?

Using .NET Core 2.1, where I have a service like this:

public class OrderService : IOrderService
{
    public event EventHandler<OrderUpdatedEvent> OrderUpdatedEventHandler; 

    ...
}

I also created a SignalR hub like this:

public class OrderHub : Hub
{
    private OrderService _orderService;
    private EventHandler<OrderUpdatedEvent> _eventHandler;

    public OrderHub(OrderService orderService)
    {
        Console.WriteLine("OrderHub created...");
        _orderService = orderService;
        _eventHandler = (sender, updateEvent) => { SendUpdateOverWebsocket(updateEvent); };
        _orderService.OrderEventHandler += _eventHandler;
        Console.WriteLine("Event handler added!");
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _orderService.OrderUpdatedEventHandler -= _eventHandler;
    }

In the OrderService I have this line:

OrderUpdatedEventHandler?.Invoke(this, new OrderUpdatedEvent(orderId, ...));

The problem is that OrderUpdatedEventHandler is always null because the constructor of OrderHub does not seem to be created at application startup.

The OrderService is registered as singleton:

services.AddSingleton<IOrderService, OrderService>();

I did find in the docs that .NET will only create singletons when it is "used". Which I understood as "a controller that has a dependency on this singleton is hit for the first time via a HTTP REST call".

What is also not good in my code is that OrderHub declares the constructor DI parameter as OrderService instead of IOrderService. I could probably get around it by directly injecting the hub into my OrderService, but I want to use events to have a loose coupling between the service and putting stuff on a websocket when something changes.

Upvotes: 4

Views: 2567

Answers (1)

Daniel Beckmann
Daniel Beckmann

Reputation: 286

As you already feared, the OrderHub is not created at application startup. The Hub will be created, when a client connects or invokes a method on the Hub. Another problem is, that the Hub gets directly disposed after the client has connected or a hub method has been invoked. So in your case, the Hub will immediately unsubscribe from the EventHandler. In addition the Hub instance is no singleton.

I would suggest the following solution, when your primary goal is loose coupling:

Create a IOrderBroadcaster interface, to be able to change the implementation from SignalR to another technology later:

public interface IOrderBroadcaster
{
    Task SendUpdate(string order);
}

The implementation for the SignalR version would look like that:

public class WebSocketOrderBroadcaster : IOrderBroadcaster
{
    private IHubContext<OrderHub> orderHubContext;

    public WebSocketOrderBroadcaster(IHubContext<OrderHub> orderHubContext)
    {
        this.orderHubContext = orderHubContext;
    }

    public Task SendUpdate(string order)
    {
        return this.orderHubContext.Clients.All.SendAsync("Update", order);
    }
}

Also take care, that the new broadcaster is registered for DI in Startup.cs:

services.AddSingleton<IOrderBroadcaster, WebSocketOrderBroadcaster>();

For this solution your Hub can remain empty:

public class OrderHub : Hub
{
}

In your OrderService you then can inject the IOrderBroadcaster and call your update method:

public class OrderService : IOrderService
{
    private IOrderBroadcaster broadcaster;

    public OrderService(IOrderBroadcaster broadcaster)
    {
        this.broadcaster = broadcaster;
    }

    public void UpdateOrder(string order)
    {
        this.broadcaster.SendUpdate(order);
    }
}

With this solution you have achieved a loose coupling and also don't need EventHandlers, which should be avoided anyway in my opinion, when it's possible.

Happy coding!

Upvotes: 3

Related Questions