.NET Console Application Template

Here’s what I’ll use as the starting point for any new console application that I build. It includes simple logging to the console and to a file, dependency injection, and strongly typed configuration, including user secrets that don’t go to source control.

Main.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MyApplication.Configuration;
using Serilog;
using Serilog.Debugging;
using System.Diagnostics;

// Writes Serilog errors do the debug window in case you're having trouble
// with your configuration.
SelfLog.Enable(msg => Debug.WriteLine(msg));

await Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostContext, builder) =>
    {
        // Allows you to use the secrets.json file in
        // your user directory during development, so you don't
        // risk putting sensitive information into source control
        builder.AddUserSecrets<Program>();
    })
    .ConfigureServices((hostContext, services) =>
    {
        // Pulls the configuration information for Serilog 
        // logging into the default Serilog logger
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(hostContext.Configuration)
            .CreateLogger();

        // Replaces the default logger with Serilog
        services.AddLogging(builder =>
        {
            builder.ClearProviders();
            builder.AddSerilog();
        });

        // Deserializes your config file into an object that is
        // then added to DI
        services.AddSingleton(hostContext.Configuration.Get<Config>());

        // Sets the Program class as your hosted service
        services.AddHostedService<Program>();
    })
    .Build()
    .RunAsync();

Program.cs

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MyApplication.Configuration;
using System.Text.Json;

partial class Program
    : IHostedService
{
    private readonly ILogger<Program> _logger;
    private readonly Config _config;

    // Injected logger and Config object
    public Program(ILogger<Program> logger, Config config)
    {
        _logger = logger;
        _config = config;
    }

    async Task IHostedService.StartAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogDebug("Starting...");
        _logger.LogDebug("Config data: {configJson}",
            JsonSerializer.Serialize(_config));

        // Your real work goes here, probably a call to another class
        await DoSomethingAsync();
    }

    Task IHostedService.StopAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogDebug("Ending...");
        return Task.CompletedTask;
    }

    private Task DoSomethingAsync()
    {
        _logger.LogInformation(@"
            Your item is {itemID}/{itemName}.
            You have {numWidgets} widgets in your collection
        ", _config.Item.ID, _config.Item.Name, _config.Collection.Length);
        return Task.CompletedTask;
    }
}

appsettings.json

{
  "Item": {
    "ID": 34,
    "Name": "Foo"
  },

  "Collection": [
    {
      "Color": "Blue",
      "Shape": "Circle"
    },
    {
      "Color": "Brown",
      "Shape": "Square"
    }
  ],

  "ApiPassword": "This is overridden from the secrets.json file",

  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "Serilog.Sinks.File"
    ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "restrictedToMinimumLevel": "Debug"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "C:/temp/log.txt",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] [{SourceContext}] {Message}{NewLine}{Exception}",
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}

secrets.json (in your user directory, not in the project directory)

{
  "ApiPassword": "mypassword"
}

MyApplication.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    
    <!-- Forces you to deal with compiler warnings -->
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <WarningsAsErrors />
    
    <!-- Auto-generated when you open
                     "Manage User Secrets" from the VS project -->
    <UserSecretsId>559df25f-a058-417c-b576-e9a5409a9118</UserSecretsId>

    <!-- These get injected into your generated AssemblyInfo class -->
    <Version>2.0.0</Version>
    <AssemblyVersion>2.0.0</AssemblyVersion>
    <Company>My Company Name</Company>
    <Product>My Product Name</Product>
    <AssemblyTitle>My Title</AssemblyTitle>
    
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
    <PackageReference Include="Serilog" Version="2.11.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Obviously, the below configuration classes would be specific to your application:

Configuration/Config.cs

namespace MyApplication.Configuration;

public class Config
{
    public Item Item { get; init; } = new();
    public Widget[] Collection { get; init; } = Array.Empty<Widget>();
    public string ApiPassword { get; init; } = "";
}

Configuration/Item.cs

namespace MyApplication.Configuration;

public class Item
{
    public int ID { get; init; }
    public string Name { get; init; } = "";
}

Configuration/Widget.cs

namespace MyApplication.Configuration;

public class Widget
{
    public string Color { get; init; } = "";
    public string Shape { get; init; } = "";
}

Leave a Reply

Your email address will not be published.