Hosting services in .NET Core console application

Building .NET Core console applications with IHost and HostBuild to take advantage of IHostedService, graceful shutdown, dependency injection, logging, hostbuilder, configuration and more.

When building services for data processing you do not always need a user interface. An IHost is very capable of hosting such an application in a console application as headless service. The IHost does give you a number of advantages like graceful shut down, dependency injection, logging, and configuration. When running long processing tasks in Docker containers, graceful shut down helps you to keep the state of your application consistent. This post explains how making use of the generic IHost in .NET Core for headless services.

Creating a console application based on IHost

A newly created console application seems to be very empty compared to a new web application. You do only get a Console.WriteLine("Hello World") for free. There is no set up for configuration, logging or dependency injection. A web application gives you an IWebHost (built with the WebHostBuilder) which provides services to your application. The IHost and HostBuilder give you a similar experience in a console application.

To get started IHost and HostBuilder you must add the Nuget package Microsoft.Extensions.Hosting. This package contains all the building blocks to create an IHost in which you can host your application. When using the RunConsoleAsync, you do not have to create the IHost itself locally. Everything to run the service is done in the extension method RunConsoleAsync. The code running a hosted service (MyService is of type IHostedService):

class Program
{
static async Task Main(string[] args)
{
await new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService();
})
.RunConsoleAsync();
}
}

The HostingHostBuilderExtensions gives you an easy way of configuring your host services. The RunConsoleAsync will start the services and waits on an exit signal in the application. There are the following extension available:

  • ConfigureAppConfiguration – Sets up the configuration
  • ConfigureContainer – Enables configuring the instantiated dependency container
  • ConfigureLogging – Configures logging
  • ConfigureServices – Adds services to the dependency container
  • RunConsoleAsync – Enables console support
  • UseConsoleLifetime – Listens for shutdown signals
  • UseContentRoot – Specify the content root directory
  • UseEnvironment – Specify the environment

Most extension can be called multiple times where the result is additive.

The following code is an example of a very simple implementation of an IHostedService. More on IHostedServices: ASP.NET Core background processing with IHostedService

public class MyService : IHostedService, IDisposable
{
private Timer _timer;

public MyService()
{
}

public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Timed Background Service is starting.");

_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));

return Task.CompletedTask;
}

private void DoWork(object state)
{
Console.WriteLine("Timed Background Service is working.");
}

public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Timed Background Service is stopping.2");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}

public void Dispose()
{
_timer?.Dispose();
}
}

The MyService runs as a IHostedService in the Host. You can run multiple IHostedService in one host.

Logging
Logging is very useful in your hosted services. It can be added with the extension method ConfigureLogging. Adding logging to a IHost is the same as in a WebHost:

.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
(...)
})
.ConfigureLogging((hostContext, configLogging) =>
{
configLogging.AddConsole();
configLogging.AddDebug();
}

The usage of logging is then as follows:

public class MyService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;

public MyService(ILogger logger)
{
_logger = logger;
}

Graceful shutdown
When the console application gets a shutdown signal the StopAsync methods in the hosted services are called. In the StopAsync method, you can wait till the processing is ready. When you implement IDisposible, the Dispose method is called to release any unmanaged resources. This is done after the StopAsync method is finished.

Related posts
Background processing
Schedule services
Using scoped services
Using HttpClientFactory

Final thoughts
When you have parts of your application or service that do mostly data processing and do not need a user interface, a console application with one or more IHostedServices can be very useful. A console application running an IHost can be ideal within Docker containers. When the container is shut down, the processing can stop gracefully. Besides that, the Dependency Injection gives you many options to test your code. Many reasonsto add the IHost and HostBuilder in your console application.

7 thoughts on “Hosting services in .NET Core console application”

  1. Nice! It’s pretty sweet that we can do this kinda stuff with .Net Core. I’m loving the super flexible ways we can build stuff out now. I remember reading somewhere that you can even launch WCF services from a console using the HostBuilder (not that I would ever want to do that though).
    Just as an extra note, the .net core docs say:
    “If the app shuts down unexpectedly (for example, the app’s process fails), StopAsync might not be called.”
    That’s a pretty important “side-note”. You might want to just put all your “shutdown” logic in the Dispose method, which will always be called, even when the process craps out because of some error (docs again):
    “If an error is thrown during background task execution, Dispose should be called even if StopAsync isn’t called.”
    Thanks Peter! Keep it up!

    Like

    1. Thank you James for the comment, I’m not totally sure how to read the part of the unexpectedly shut down related to the word might. The same goes for Dispose should be called. In that case, who is responsible, the host or the process itself. For graceful shut down I would use the StopAsync and cleaning up the Dispose method. If you have a processing loop that can ‘crash’, it seems a good idea to dispose your disposable resources at the end of the error handling.

      Like

      1. There are some scenarios I have in mind:
        – What if you had a hosted service that needed to inform another system (for whatever reason) that it is going offline (maybe through an api call)?
        – Or perhaps your app is using the hosted service to poll and send updates via SignalR to a UI admin screen that tells you if your app is up and running? If the system is going down (via shutdown or error) you want to send a specific message via SignalR to a client, etc.

        I’m just trying to point out that in a situation like that, developers should be mindful of the fact that using StopAsync will not be as reliable as Dispose. Under certain circumstances (like above) will not have that final necessary action.

        I think developers are going to be using Hosted Services for some very inventive use cases (even though asp had something like it -it’s much easier to use in .net core!) so it’s just something to be mindful of.

        Does that make more sense lol?

        Liked by 1 person

      2. Yes, it does idd make sense. I think it is very ‘implementation’ specific, in most cases where it is important to know the system is running (watchdog), I would prefer to monitor it from outside the process. If there is no response, do the needed actions. However, the difference between stop and disposing can indeed be important in an implementation. Thx for pointing out.

        Liked by 1 person

  2. I’m always looking for a way to abstract the hosting from small “tasks” that I’m building and using IHostedService definitely feels like a step forward, but I’m struggling to determine how to manage a task that is called by an external scheduler (like Azure Functions) and then exits once the task is completed. Perhaps using IHostedService & RunConsoleAsync are not the right fit for this but is there an alternative for tasks where the completion of all hosted service startups (and not Ctrl-C) ends the process? Undoubtedly a shiny new hammer + not really a nail issue but it’s so attractive to have some pattern for configuring & starting tasks independent of what they actually do.

    Like

  3. I am trying to implement this, but some part is very confusing. How RunConsoleAsync() translates to “run as a service”? My problem comes from a requirement to optionally run a long process as Service or Console App. I was thinking in something like RunServiceAsync() and RunConsoleAsync() controlled by a parameter… but as RunAsConsoleAsync() means “run as service”, I’m confused.
    Thanks for any suggestion

    Like

    1. “RunConsoleAsync()” doesn’t run the application as a service – it runs it as a console executable.
      Within that executable, you can create an IHostedService. Quoting the article:
      “You can run multiple IHostedService in one host.”
      So the host could be a console app (like in this case), a web app or even a windows service if you want. IHostedService isn’t a Windows service – it’s a background service specifically within an existing application.
      So these are two different things. IHostedServices are just background jobs that can be kept alive for the entirety of your application and can be “told” when the app is shutting down.
      Hopefully that helps 🙂

      Liked by 1 person

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.