Reputation: 1121
In my ASP.NET Core 2.2 web application I have these scoped services in my Startup.ConfigureServices(IServiceCollection services) method:
public void ConfigureServices(IServiceCollection services)
{
services.AddDirectoryBrowser();
services.AddScoped<WebApplication.Services.DoThingAlpha>();
services.AddScoped<WebApplication.Services.DoThingBravo>();
services.AddScoped<WebApplication.Services.DoThingCharlie>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
These scoped services do the dirty work for my controllers.
However, I have a need to constantly listen on a UDP port in the background and then notify the appropriate service (DoThingsAlpha, DoThingsBravo, or DoThingsCharlie) when certain UDP messages are received on the listener.
I think I can implement the background service with BackgroundService (https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/background-tasks-with-ihostedservice) and then add to the services collection in ConfigureServices. Lets call it ListenOnUDP : BackgroundService
The rub is, how do I go about signaling to those other scoped services that the UDP message was received in ListenOnUDP? What is the recommended pattern to solve this kind of problem? Surely there is one.
Upvotes: 4
Views: 2945
Reputation: 1121
I have worked out a solution and I think the problem stemmed from my fundamental lack of understanding of ASP.NET Core services. I will explain in case someone has the same issue (or perhaps validate or invalidate my answer).
When you add a service class/interface to your IServiceCollection that service becomes accessible to other services via the interface. You simply include the interface in the constructor of the other service you want to call in your constructor, save it and you can then call the interface (what I take to understand is a manifestation of dependency injection). Like so:
private readonly InterfaceRCResponseQueue _rcResponseQueue;
private readonly ILogger _logger;
public UdpListenerBackgroundService(
InterfaceRCResponseQueue rcResponseQueue,
ILogger<UdpListenerBackgroundService> logger
)
{
_rcResponseQueue = rcResponseQueue;
_logger = logger;
}
This, effectively lets one service signal another by calling methods on the interface.
The catch:
A singleton service cannot be dependent on a scoped or transient service. Presumably because it hangs up the scoped or transient service. The reverse is OK though.
https://blog.markvincze.com/two-gotchas-with-scoped-and-singleton-dependencies-in-asp-net-core/
In my case, I have a UdpClient class that needs to listen for messages and then signal other services.
To do so, I put my UdpClient class in class that inherits from Microsoft.Extensions.Hosting.BackgroundService and therefore gets created and torn down with the life of the application. I also created a singleton class that holds a queue.
When my UdpClient class in my BackgroundService class gets a UDP datagram, I call a method on the singleton class that enqueues it in the singleton class' queue.
A scoped, singleton or transient class can then depend on the singleton class and look for data in that queue (dequeue) at it's leisure. You just can't do the reverse (make a singleton depend on a scoped or transitive service).
That is the form of signalling I came up with, to indirectly notify a lesser service from a singleton or background service.
Upvotes: 4