Reputation: 1891
I am trying to build a small tcp server/daemon with asp.net core as a web frontend to interact with the server. I have found IHostedService/BackgroundService which seems to provide a low effort alternative to bundle the server and the frontend together.
The code looks basically like this at the moment (echo server for testing purposes):
public class Netcat : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8899);
listener.Start();
while(!stoppingToken.IsCancellationRequested)
{
TcpClient client = await listener.AcceptTcpClientAsync();
NetworkStream stream = client.GetStream();
while (!stoppingToken.IsCancellationRequested)
{
byte[] data = new byte[1024];
int read = await stream.ReadAsync(data, 0, 1024, stoppingToken);
await stream.WriteAsync(data, 0, read, stoppingToken);
}
}
}
}
And is initialized in Startup.cs like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<Netcat>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?
How would I interact with the running service itself from a Controller?
Is IHostedService even usable for this purpose or is it a better way that fully decouples the Asp.Net frontend and the service/server, e.g. by running the daemon and asp.net as seperate processes with some sort of IPC mechanism?
Upvotes: 9
Views: 14528
Reputation: 303
My suggestion is similar to @itminus
Depending on your desired scenario:
Do not create TCP Listener. Use the background queue for requests and background service for processing requests, invoked from the code as explained in the docs
Implement the separate processing service (logic server) as in point 1. You can inject it and invoke from both your TCP listener background service and controllers.
Of course you can access your own service via HttpClient
from the same app, but it would seem strange to use the whole TCP stack for internal calls.
If the TCP processing is totally independent from the web application, then cut the TCP service out to separate server application. See docs on how to create "pure" service without asp/kestrel overhead in dotnet core 2.1.
Upvotes: 0
Reputation: 25380
Is there a common pattern for how modern Asp.Net core applications and daemons should cooperate?
Actually , the hosted service is not that powerful for the present . So people usually use a third product . However , it's possible to communicate with hosted service and controller . I'll use your code as an example to achieve these goals :
TcpServer
is able to receive two commands so that we can switch the state of hosted service from a TcpClient
.WebServer
can invoke method of TcpServer
indirectly (through a mediator ), and render it as html It's not a good idea to couple controller with hosted service . To invoke method from hosted service , we can introduce a Mediator . A mediator is no more than a service that serves as a singleton (because it will referenced by hosted service) :
public interface IMediator{
event ExecHandler ExecHandler ;
string Exec1(string status);
string Exec2(int status);
// ...
}
public class Mediator: IMediator{
public event ExecHandler ExecHandler ;
public string Exec1(string status)
{
if(this.ExecHandler==null)
return null;
return this.ExecHandler(status);
}
public string Exec2(int status)
{
throw new System.NotImplementedException();
}
}
A Hosted Service needs to realize the existence of IMediator
and expose his method to IMediator
in some way :
public class Netcat : BackgroundService
{
private IMediator Mediator ;
public Netcat(IMediator mediator){
this.Mediator=mediator;
}
// method that you want to be invoke from somewhere else
public string Hello(string status){
return $"{status}:returned from service";
}
// method required by `BackgroundService`
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8899);
listener.Start();
while(!stoppingToken.IsCancellationRequested)
{
// ...
}
}
}
To allow control the status from the NetCat TcpServer
, I make it able to receive two commands from clients to switch the state of background service :
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8899);
listener.Start();
while(!stoppingToken.IsCancellationRequested)
{
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("a new client connected");
NetworkStream stream = client.GetStream();
while (!stoppingToken.IsCancellationRequested)
{
byte[] data = new byte[1024];
int read = await stream.ReadAsync(data, 0, 1024, stoppingToken);
var cmd= Encoding.UTF8.GetString(data,0,read);
Console.WriteLine($"[+] received : {cmd}");
if(cmd=="attach") {
this.Mediator.ExecHandler+=this.Hello;
Console.WriteLine($"[-] exec : attached");
continue;
}
if(cmd=="detach") {
Console.WriteLine($"[-] exec : detached");
this.Mediator.ExecHandler-=this.Hello;
continue;
}
await stream.WriteAsync(data, 0, read, stoppingToken);
stream.Flush();
}
}
}
If you want to invoke the method of background service within a controller, simply inject
the IMediator
:
public class HomeController : Controller
{
private IMediator Mediator{ get; }
public HomeController(IMediator mediator){
this.Mediator= mediator;
}
public IActionResult About()
{
ViewData["Message"] = this.Mediator.Exec1("hello world from controller")??"nothing from hosted service";
return View();
}
}
Upvotes: 8