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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With