When building a .net worker application with a hosted service based on the BackgroundService
class, it's some times it's required to set the application exit code based on the outcomes of the execution of the hosted service.
One trivial way to do this is to to set the Environment.ExitCode
property from the hosted service:
public class Worker : BackgroundService
{
public Worker()
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
throw new Exception("Something bad happened");
}
catch
{
Environment.ExitCode = 1;
}
}
}
This works, however consider these unit tests:
[Test]
public async Task Test1()
{
Worker sut = new Worker();
await sut.StartAsync(new CancellationToken());
Assert.That(Environment.ExitCode, Is.EqualTo(1));
}
[Test]
public void Test2()
{
// another test
Assert.That(Environment.ExitCode, Is.EqualTo(0));
}
Test1
passes, however Test2
fails as Environment.ExitCode
is a static variable. You can reset back to zero it after the test, but this is error-prone. So what is the alternative?
One simple solution is to use a status code-holding class as a singleton and inject it into the background service:
public interface IStatusHolder
{
public int Status { get; set; }
}
public class StatusHolder : IStatusHolder
{
public int Status { get; set; }
}
public class Worker : BackgroundService
{
private readonly IStatusHolder _statusHolder;
public Worker(IStatusHolder statusHolder)
{
_statusHolder = statusHolder;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
throw new Exception("Something bad happened");
}
catch
{
_statusHolder.Status = 1;
}
}
}
As simple Program.cs
would look like:
using EnvironmentExit;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddSingleton<IStatusHolder, StatusHolder>();
})
.Build();
host.Start();
var statusHolder = host.Services.GetRequiredService<IStatusHolder>();
Environment.ExitCode = statusHolder.Status;
Note that line number 8 registers IStatusHolder
as a singleton, which is important to maintain its state.
Now all tests pass. Additionally, when the application runs, the exit code is 1.