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
andSettins.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
.