.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();
        });

        services.Configure<Config>(hostContext.Configuration.GetSection("ConfigStuff"));

        services.AddSingleton<Program>();
    })
    .Build()
    .Services.GetRequiredService<Program>()
    .ExecuteAsync();

Program.cs

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

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

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

    public async Task ExecuteAsync()
    {
        _logger.LogDebug("Starting...");
        _logger.LogDebug("Config data: {configJson}",
            JsonSerializer.Serialize(_config));

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

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

appsettings.json

{
  "ConfigStuff": {
    "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)

{
  "ConfigStuff": {
    "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>a6732cb3-823b-4d05-818e-53a24fb12bb8</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.