Daemons are as essential to servers as caffeine and pizza are to developers. The executables run as services on a server waiting to receive input usually from a network call, and respond to it by providing something back to the the user who called it. .NET Core is the venerable cross-platform development stack for Linux, Mac, and Windows. Up to now, .NET Core really hasn’t had a good story for writing daemons, but with the introduction asynchronous Main
methods, GenericHosts, and RunConsoleAsync
this is not only possible, but incredibly elegant in its implementation. It follows the name patterns that ASP.NET Core developers have come to love. The main difference though is that in ASP.NET Core, the Web Host service is provided to you as a service and you write controllers to handle requests. With a Generic Host, it falls to the you to implement the host.
In this little tutorial, we’ll go over the basics of writing a daemon using .NET Core and a Generic Host.
Download the Full Project here.
- Create an app. The basic console app for the .NET Core template is little more than a Hello World app. However, it doesn’t take much to transform this into a daemon style application.
dotnet new console --name mydaemon
- To make this app run like a daemon, there’s a few things that we need to change in the
.csproj
file, so the myadaemon.csproj file in a text editor. - This project file is an XML file that contains a basic set of sections. We need to add a section to tell the compiler to use C# 7.1. This allows your
Main
method to be asynchronous which is not allowed in the default version of C#, 7.0. Add the following code snippet before the closing</Project>
tag.<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <LangVersion>7.1</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <LangVersion>7.1</LangVersion> </PropertyGroup>
- Now, we need to add some dependencies to make the app work. These are all extensions that can be used in console apps but can also be used in web apps as well. Add the following code snippet before the closing
</Project>
tag. Here, you’re adding dependencies for command line and Environment configuration, the ability to log to the console, and adding dependencies to do dependency injection for the Generic Host.<ItemGroup> <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" /> </ItemGroup>
- Save the
.csproj file
- Back at the CLI, install the dependencies.
dotnet restore
- Now, edit the
Program.cs
file. Add the following name spaces to the top of the file:using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging;
- Change the
Main
method’s signature to be asynchronous.public static async Task Main(string[] args)
- Replace the code in the main method with the following code snippet. This code is wiring up the configuration for the daemon using the
HostBuilder
class. TheConfigureAppConfiguration
tells the builder where to get configuration information from, and in this case we’re using the command line and the environment.ConfigureServices
tells the builder what services to use and how to instantiate them. In the case of a daemon, it’s most likely that your service will be a Singleton, meaning there is only one instance of the service for the entire duration of the app. It also adds the configuration POCO object to the the services for dependency injection. This implements theIOption
interface in .NET Core, which can take a type that it will attempt to bind CLI parameters and environment variables to based on the field name.ConfigureLogging
wires up console logging using theILogger
interface.The secret sauce for making the program run as a daemon is theRunConsoleAsync
method. This method puts the app in a wait state that is looking forCtrl + C
orCtrl + Break
without consuming CPU. While the app is up and running, so is your service as defined by theConfigureServices
method.var builder = new HostBuilder() .ConfigureAppConfiguration((hostingContext, config) => { config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureServices((hostContext, services) => { services.AddOptions(); services.Configure<DaemonConfig>(hostContext.Configuration.GetSection("Daemon")); services.AddSingleton<IHostedService, DaemonService>(); }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); }); await builder.RunConsoleAsync();
- Now save the
Program.cs
file. - Create a new file called
DaemonService.cs
. This is the class file that defines your service. - Paste in the following code into the file. This class implements
IHostedService
andIDisposable
. TheIHostedService
interface is used by the builder inProgram.cs
to create a service that is “hosted” in the console app. It has two basic methods:StartAsync
andStopAsync
that get called when the service is started and stopped respectively. These methods allow for a graceful startup and shutdown of the service. If a service implementsIDisposable
, then theDispose
method will be called. This is a nicety for any final cleanup steps needed afterStopAsync
is called. The constructor accepts a number of interfaces as parameters, which are resolved by the dependency injection built into the builder. Thelogger
andconfig
are some of the standard kinds of dependencies that many apps have.using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace mydaemon { public class DaemonService : IHostedService, IDisposable { private readonly ILogger _logger; private readonly IOptions<DaemonConfig> _config; public DaemonService(ILogger<DaemonService> logger, IOptions<DaemonConfig> config) { _logger = logger; _config = config; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting daemon: " + _config.Value.DaemonName); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Stopping daemon."); return Task.CompletedTask; } public void Dispose() { _logger.LogInformation("Disposing...."); } } }
- Save
DaemonService.cs
. - Now, create a file called
DaemonConfig.cs
and paste in the following code. This is just a POCO class that the configuration will attempt to bind properties to. Save the file when finished.using System; namespace mydaemon { public class DaemonConfig { public string DaemonName { get; set; } } }
- Now, everything is ready to compile. Back in the CLI, build the app.
dotnet build
- Now you can run the app.
dotnet run --Daemon:DaemonName="Cool Daemon"
The output will look like the following:
Application started. Press Ctrl+C to shut down. infoHosting environment: Production Content root path: D:sourcedotnet-daemondaemonbinDebugnetcoreapp2.1 : daemon.DaemonService[0] Starting daemon: Cool Daemon
- The app is now running and you can use
Ctrl + C
to stop it.
Conclusion
This tutorial demonstrates how to frame up a .NET core daemon using some of the newer features available in .NET such as the Generic Host, asynchronous Main
methods, and RunConsoleAsync
to keep the console app running. Also, this structure makes the code testable and maintainable using patterns that are familiar to those using ASP.NET Core.
In Part 2, we’ll use this to actually make a useful daemon: a .NET Core MQTT Broker.