Creating a Daemon with .NET Core (Part 1)

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.

  1. 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
    
  2. 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.
  3. 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>   
    
  4. 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>
    
  5. Save the .csproj file
  6. Back at the CLI, install the dependencies.
     dotnet restore
    
  7. 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;
    
  8. Change the Main method’s signature to be asynchronous.
     public static async Task Main(string[] args)
    
  9. 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. The ConfigureAppConfiguration 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 the IOption 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 the ILogger interface.The secret sauce for making the program run as a daemon is the RunConsoleAsync method. This method puts the app in a wait state that is looking for Ctrl + C or Ctrl + Break without consuming CPU. While the app is up and running, so is your service as defined by the ConfigureServices 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();
    
  10. Now save the Program.cs file.
  11. Create a new file called DaemonService.cs. This is the class file that defines your service.
  12. Paste in the following code into the file. This class implements IHostedService and IDisposable. The IHostedService interface is used by the builder in Program.cs to create a service that is “hosted” in the console app. It has two basic methods: StartAsync and StopAsync 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 implements IDisposable, then the Dispose method will be called. This is a nicety for any final cleanup steps needed after StopAsync is called. The constructor accepts a number of interfaces as parameters, which are resolved by the dependency injection built into the builder. The logger and config 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....");
    
             }
         }
     }
    
  13. Save DaemonService.cs.
  14. 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; } 
         }
     }
    
  15. Now, everything is ready to compile. Back in the CLI, build the app.
     dotnet build
    
  16. 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
    
  17. 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.

Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X