C#
Sam Lau  

Migrate App.config to appsettings.json with dependency injection and Option pattern

One thing I realized when I upgraded my application from .NET Framework to .NET 8 using the Visual Studio Upgrade Assistance was that it did not migrate App.config to appsettings.json. In fact, it just added a reference to System.Configuration.ConfigurationManager and the old way of using App.config and Setting.settings just worked. This is great if all you want is just upgrade and change as little code as possible but I am interested in modernizing my application, including modernizing configuration. As a result, I have to migrate App.config to appsettings.json by hand and I will show you how to do it following best practices, namely dependency injection and Option pattern.

Pre-migration

Let’s start by looking at how things are set up before migration. This is the sample on GitHub. I am setting up this demo as a console app running generic host and using App.config and Setting.settings. The reason I go with generic host is because it is the modern .NET way of handling application lifetime management and come with dependency injection and configuration management, which will come in handy. If your app does not already use host building pattern, I suggest looking into migrating to it or using the new worker service template.

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e080" >
            <section name="AppConfigSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e080" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <applicationSettings>
        <AppConfigSample.Properties.Settings>
            <setting name="ApplicationName" serializeAs="String">
                <value>MyApplication</value>
            </setting>
        </AppConfigSample.Properties.Settings>
    </applicationSettings>
</configuration>

This is your typical bulky App.config. I have one setting ApplicationName set as MyApplication. Not much else to say.

Program.cs

using AppConfigSample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<MyService>();

IHost host = builder.Build();
await host.RunAsync();

The Program.cs most generic host builder, only injecting MyService, which host the main logic of the app.

MyService.cs

using AppConfigSample.Properties;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace AppConfigSample
{
    internal class MyService : IHostedService
    {
        private readonly string applicationName = Settings.Default.ApplicationName;

        public Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine($"{applicationName} starts");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine($"{applicationName} stops");
            return Task.CompletedTask;
        }
    }
}

Let’s focus on how setting is being retrieve. If you are familiar with .NET App.config, this is nothing new. You get settings by calling the static Settings.Default.ApplicationName.

Migration

This is the after migration sample on GitHub and I will walk you through how I get there.

appsettings.json

{
  "MyConfiguration": {
    "ApplicationName": "MyApplication"
  }
}

We will create an appsettings.json. This is already looking a lot cleaner then App.Config! I am putting all app configuration in to MyConfiguration section and you will see why later.

MyConfiguration.cs

namespace AppsettingsSample
{
    internal record MyConfiguration
    {
        public string ApplicationName { get; init; }
    }
}

We will create a configuration class. Think of it like your Settings.settings and Settins.Designer.cs in the old day. Instead of putting setting in Settings.settings and auto-generate Settins.Designer.cs, you manage your own configuration class, which give you some flexibility. I am setting this as an internal record and members be { get; init; }, meaning this is not accessible outside of the project and members are immutable. You can set it up however you want.

Program.cs

using AppConfigSample;
using AppsettingsSample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.Configure<MyConfiguration>(builder.Configuration.GetSection("MyConfiguration"));
builder.Services.AddHostedService<MyService>();

IHost host = builder.Build();
await host.RunAsync();

The one line we add is builder.Services.Configure<MyConfiguration>(builder.Configuration.GetSection("MyConfiguration")); This reads appsettings.json MyConfiguration section and use it to populate and inject the configuration class MyConfiguration.

MyService.cs

using AppsettingsSample;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace AppConfigSample
{
    internal class MyService(IOptions<MyConfiguration> options) : IHostedService
    {
        private readonly string applicationName = options.Value.ApplicationName;

        public Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine($"{applicationName} starts");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine($"{applicationName} stops");
            return Task.CompletedTask;
        }
    }
}

We are using Option pattern here. IOptions<MyConfiguration> is being injected and you call options.Value.ApplicationName to access the setting. Another common way of doing this when you have more settings is to store MyConfiguration as readonly member. You will access individual setting by calling into MyConfiguration like myConfiguration.ApplicationName.

Clean up

Finally, we will delete all unnecessary stuff.

  • Package reference System.Configuration.ConfigurationManager
  • Settings.settings and Settins.Designer.cs
  • App.config

Conclusion

There you go! You can find the full sample code in my GitHub repo. If you are not interested in the dependency injection and Option pattern way but still want to migrate, you can follow this to do it with ConfigurationBuilder. You can read this to dive deeper into how .NET handle configuration. I have a another post talking about the use of user secrets and appsettings.Development.json, which are common development setup with appsettings.json.