Reputation: 26858
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
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