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.
dotnet new console --name mydaemon
.csproj
file, so the myadaemon.csproj file in a text editor.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>
</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>
.csproj file
dotnet restore
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;
Main
method’s signature to be asynchronous.
public static async Task Main(string[] args)
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();
Program.cs
file.DaemonService.cs
. This is the class file that defines your service.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....");
}
}
}
DaemonService.cs
.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; }
}
}
dotnet build
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
Ctrl + C
to stop it.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.
Cloud management is difficult to do manually, especially if you work with multiple cloud…
Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…
https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…
FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…
Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…
In the intricate landscape of modern business, compliance is both a cornerstone of operational integrity…