1 minute read

Program.cs is usually one of the files that get quite messy after a while in every .NET project. From then, developers will add code on top of all that mess or even worse create a new class and reference that in.

Here’s a structure I found over years that helps me to keep app startup code readable and clean.

Generally there are 3 things to setup in Main function:

  1. Ingress appsettings and options (via appsettings files, environment variables or command line)
  2. Setup logging
  3. Setup ServiceProvider (dotnet core dependency injection)

I add Microsoft.Extensions.Hosting nuget package and use this in Main:

var hostBuilder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration(AddSettings) .ConfigureServices(AddOptions) .ConfigureServices(AddServices); await builder.RunConsoleAsync();

CreateDefaultBuilder() creates and sets up all the 3 things above and return an IHostBuilder instance. IHostBuilder has couple of nice methods that accept lambda functions:

  • ConfigureAppConfiguration: to add configurations related to the app
  • ConfigureHostConfiguration: to add configurations related to the host itself (such as Kestrel, etc.)
  • ConfigureServices: add classes to DI

I leverage those and divide my app startup code in these three nice methods:

private static void AddSettings(IConfigurationBuilder configurationBuilder) { configurationBuilder .AddJsonFile(“appsettings.json”, optional: false, reloadOnChange: true) .AddEnvironmentVariables(); }

private static void AddServices(HostBuilderContext context, IServiceCollection serviceCollection) { serviceCollection .AddTransient<,>() .AddSingleton<>();

        serviceCollection.AddHostedService<>();
    }

private static void AddOptions(HostBuilderContext context, IServiceCollection serviceProvider) { var configuration = context.Configuration;

        serviceProvider.AddOptions<MyOptions>()
            .Bind(configuration.GetSection(nameof(MyOptions)))
            .ValidateDataAnnotations();
    }

So what benefits do I get?

  • Main method is clean and simple and shows the setup steps in high-level view
  • It uses Options pattern to have configurations as objects. This provides all compile time checks and intelisense features.
  • The amount of usage of stings is minimum. Therefore classes can be moved/renamed without worrying about app setup code.

What do you think?