How to implement .net core IHostedService that runs one time only

Viewed 4523

I know a IHostedService that runs only one time sounds like a console application, but the reason I want to use it instead of a plain console application is:

  • .net core introduces Generic Host for running non-http application
  • A plain console application does not have DI, Logger, Configurations ready to use

By using the following code, I'm able to somewhat achieve this one-time behaviour, however, I could not find a way to gracefully exit the app after it finishes.

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<StartUp>(); });
    }

Where StartUp is a simple IHostedService

public class StartUp:IHostedService
    {
        private ILogger<StartUp> _logger;

        public StartUp(ILogger<StartUp> logger)
        {
            _logger = logger;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("start async");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("stop async");
            return Task.CompletedTask;
        }
    }

How can I stop the app gracefully? Or if this is completely wrong, how should I implement this one-time application?

4 Answers

Yes, you can achieve that by injecting IHostApplicationLifetime into your hosted service.

Example:

 public class StartUp:IHostedService
 {
    private readonly IHostApplicationLifetime _host;
    private ILogger<StartUp> _logger;

    public StartUp(IHostApplicationLifetime host, ILogger<StartUp> logger)
    {
        _host = host;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("start async");
        _host.StopApplication();
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("stop async");
        return Task.CompletedTask;
    }
}

You can also set exit code by setting Environment.ExitCode.For example:

Environment.ExitCode = 0;
_host.StopApplication();

If you are just using IHostedService as a workaround for the missing DI and ILogger you can also setup DI with the ILogger and IConfiguration directly without IHostedService

public class Program
{
    public static async Task Main(string[] args)
    {
        var configBuilder = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", optional: true);
        var config = configBuilder.Build();

        var sp = new ServiceCollection()
            .AddLogging(b => b.AddConsole())
            .AddSingleton<IConfiguration>(config)
            .AddSingleton<IFooService, FooService>()
            .BuildServiceProvider();

        var logger = sp.GetService<ILoggerFactory>().CreateLogger<Program>();
        logger.LogDebug("Starting");

        var bar = sp.GetService<IFooService>();
        await bar.DoAsync();
    }
}

With this setup your code is just running once, can resolve every service you register in the ServiceCollection and no need of a Host to start

Example: .netfiddle

Another implementation might be using IHostBuilder, but without calling run method.

    static Task Main(string[] args)
    {
        using IHost host = CreateHostBuilder(args).Build();

        var manager = host.Services.GetRequiredService<IManager>();
        manager.DoSomething();

        // we don't have registered hosted services therefore we don't call host.RunAsync();
        return Task.CompletedTask; // or change signature to void and remove return
    }

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((_, services) =>
                services
                .AddScoped<IFoo, Foo>()
                .AddScoped<IBar, Bar>()
                .AddSingleton<IManager, Manager>());
Related