I'm trying to create a .Net Core 3.1 background task/service that also has a .Net Core 3.1 MVC management UI all in the same project for easy deployment by Docker container. I need help with the overall code architecture to get started. The high level requirements are:
A single .Net Core entrypoint, not using a custom bash script as the container entrypoint that launches two separate projects/binaries. I've seen some examples of bash scripts, and I think it's sloppy and makes communication from the management UI to the service problematic. Your input?
The MVC web UI has full control of background task/service to change its parameters while running and/or restart it with configuration changes set by the user in the management UI. The more robust this control is, the better since the MVC web UI's entire role is to manage the task/service in realtime.
The background task/service should execute in perpetuity irrespective of the state of the MVC app used for management. In other words, the background task/service's lifecycle should be guaranteed even if worker threads for the MVC app get killed or restarted for any reason.
I'm very comfortable with developing .Net Core MVC web apps and APIs, and I'm fairly comfortable creating background services in individual projects. However, I'm not sure how to approach combining them in this way.
I'm pasting my first effort below that gets both tasks running so that both an MVC app and background service run as expected, but I can't control the service from the MVC app because of the scope. Can you offer any help about how I might accomplish the requirements bulleted above?
Program.cs (Works to run both simultaneously, but does not allow MVC app to control the service)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace ServiceWithMvcManagement
{
public class Program
{
public static void Main(string[] args)
{
var hostManagement = CreateHostBuilderManagement(args).Build();
var hostService = CreateHostBuilderService(args).Build();
Task[] tasks = new Task[] {
hostManagement.RunAsync(),
hostService.RunAsync()
};
Task.WaitAll(tasks);
}
// MVC Management
public static IHostBuilder CreateHostBuilderManagement(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
// Background Service
public static IHostBuilder CreateHostBuilderService(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
Worker.cs (Almost stock from Background Service template)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ServiceWithMvcManagement
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
Debug.WriteLine("Worker running at: " + DateTimeOffset.Now.ToString());
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex.Message);
}
finally
{
await Task.Delay(1000, stoppingToken);
}
}
}
}
}
Startup.cs (Completely stock from MVC Template)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ServiceWithMvcManagement
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}