Does anyone know how to set up a custom DI provider (such as Castle Windsor) in a DotNetCore Worker Service? I've seen info on how to do it for ASP.NET apps, but all the examples talk about modifying Startup.cs, which does not exist in the Worker Service template.
Castle Windsor doesn't support recent .NET Core versions, so I'll use Autofac as an example.
When you start a new worker
project, you'll have a Program class like this
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
Host.CreateDefaultBuilder
gives you an IHostBuilder
, which has necessary methods to extend or replace the DI container. We're using Autofac, so let's install it:
dotnet add package Autofac.Extensions.DependencyInjection
Then introduce it to our host. Here ContainerBuilder
comes from Autofac assembly.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// use autofac
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
// configure autofac di container
.ConfigureContainer<ContainerBuilder>((hostContext, container) =>
{
// hostcontext gives you access to config & environment
// hostContext.Configuration.GetSection("ApiKeys");
// hostContext.HostingEnvironment.IsDevelopment()
// auto register all classes in the assembly
container
.RegisterAssemblyTypes(typeof(Program).Assembly)
.AsImplementedInterfaces();
})
// configure microsoft di container
.ConfigureServices((hostContext, services) =>
{
// let autofac register this service
// services.AddHostedService<Worker>();
});
Now let's create a service. IJob
interface here is unnecessary, it's just there to show Autofac's capabilities:
internal interface IJob
{
Task ExecuteAsync(CancellationToken cancellationToken = default);
}
internal class WarmUpCaches : IJob
{
private readonly ILogger<WarmUpCaches> _logger;
public WarmUpCaches(ILogger<WarmUpCaches> logger)
{
_logger = logger;
}
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("Warming up caches");
var steps = 5;
while (!cancellationToken.IsCancellationRequested && --steps > 0)
{
_logger.LogInformation("working...");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
_logger.LogInformation("Done");
}
}
Now let's use this service inside the background service:
class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IJob _job;
public Worker(ILogger<Worker> logger, IJob job)
{
_logger = logger;
_job = job;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _job.ExecuteAsync(stoppingToken);
}
}
When we run the app, it logs:
info: CustomDiExample.WarmUpCaches[0]
Warming up caches
info: CustomDiExample.WarmUpCaches[0]
working...
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\...\CustomDiExample
info: CustomDiExample.WarmUpCaches[0]
working...
info: CustomDiExample.WarmUpCaches[0]
working...
info: CustomDiExample.WarmUpCaches[0]
working...
info: CustomDiExample.WarmUpCaches[0]
Done
If the application doesn't need to keep working, we can do without a BackgroundService
.
We need to modify the Main
method and expose the IHost
instance. We can resolve any service from the container using IHost.Services
.
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
// stop the application with Ctrl+C, SIGINT, SIGTERM
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
var cancellationToken = lifetime.ApplicationStopping;
var job = host.Services.GetRequiredService<IJob>();
await job.ExecuteAsync(cancellationToken);
}