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?
The IHostedService interface provides a convenient way to start background tasks in an ASP.NET Core web application (in . NET Core 2.0 and later versions) or in any process/host (starting in . NET Core 2.1 with IHost ).
Create a new project To create a new Worker Service project with Visual Studio, you'd select File > New > Project.... From the Create a new project dialog search for "Worker Service", and select Worker Service template. If you'd rather use the . NET CLI, open your favorite terminal in a working directory.
The TcpClient class provides simple methods for connecting, sending, and receiving stream data over a network in synchronous blocking mode. In order for TcpClient to connect and exchange data, a TcpListener or Socket created with the TCP ProtocolType must be listening for incoming connection requests.
CreateDefaultBuilder(args) . ConfigureWebHostDefaults(webBuilder => { webBuilder. UseStartup<Startup>(); }); } A background service is a specific type that runs asynchronously within a console application host with the idea it does not interfere with the primary process.
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();
}
}
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