Using multiple appsettings.json to release to different platforms in .NET
Tags: dotnet, csharp, programming, tutorial
Published: 2023-02-18 22:28:54

## Challenges

You need to release the same application to different platforms with different settings, for example, different log configurations. And you also want to ease up the release process instead of configuring by managing that complexity from the app itself.

There is also a request from the user to be able to overwrite these settings, but you don't want them messing up the big `appsettings.json` you have set up. You only want them to focus on the two or three properties that they are interested in.

## Solutions

You can load different settings files depending on the build configuration using preprocessor directives.

Taking advantage of the `IConfiguration` file load order, we can add support for a user settings file that will only contain specific properties.

## Settings files

You have an application with five different settings files:
- `appsettings.json`: contains the global settings for the application to work correctly.
- `appsettings.debug.json`: in this file, we have the correct settings to run the application in debug mode on your dev environment.
- `appsettings.azure.json`: we override properties for the app to work on Azure.
- `appsettings.linux.json`: we override properties for the app to work on Linux.
- `appsettings.user.json`: in this file, we only have the properties that the user may want to override when they deploy the application.

`appsettings.json` has the following properties:
```json
{
  "GlobalSetting": "this is a global setting",
  "PlatformSetting": "default for platform setting",
  "UserSetting": "default for user setting"
}
```

The properties mean:
- `GlobalSetting`: this setting is shared between all the possible deployment platforms.
- `PlatformSetting`: this setting is platform specific, it needs to be different in Linux, Azure, and debug.
- `UserSetting`: this setting will be overridden by the user if they need it.

The debug, Linux, and Azure files will only configure the `PlatformSetting` and the user file will only configure the `UserSetting`.

## Why do we have a specific `appsettings.user.json`?

Sometimes, the user needs to override certain settings from your application, but you have a very complex `appsettings.json`. Using a specific file for the user solves the following problems:
- The user can only on the properties they care about, they don't need to handle your massive global settings file. 
- We have the option to not include the user file in our project. If we chose to not include it, we can safely update our application without overwriting the user settings file in the process.

## Project build configurations

To support different platforms, we need to set up our project with different configurations:

For this example, our `.csproj` file has the three different configurations we support:
```xml
<PropertyGroup>
	...
	<Configurations>Debug;ReleaseAzure;ReleaseLinux</Configurations>
</PropertyGroup>
```

This is how the `.sln` is configured:
```
GlobalSection(SolutionConfigurationPlatforms) = preSolution
	Debug|Any CPU = Debug|Any CPU
	ReleaseAzure|Any CPU = ReleaseAzure|Any CPU
	ReleaseLinux|Any CPU = ReleaseLinux|Any CPU
EndGlobalSection
```

## Defining constants

Next, we need to define some constants we can use in the preprocessor directives.

If you want to know more about preprocessor directives, please visit https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives.

We can define them in the `.csproj`:
```xml
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseAzure' ">
	<DefineConstants>AZURE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseLinux' ">
	<DefineConstants>LINUX</DefineConstants>
</PropertyGroup>
```

## Loading settings files

First, we need to include these three NuGet packages in our project:
```xml
<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
	<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
	<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
</ItemGroup>
```

Next, we will be loading the settings files using `IConfiguration.AddJsonFile`, and use the `#if` preprocessor directive to select which platform file to load.

This example uses a console application in dotnet 6. `Program.cs` looks like this:
```csharp
using Microsoft.Extensions.Configuration;

var configurationBuilder = new ConfigurationBuilder();

configurationBuilder.AddJsonFile("appsettings.json");

#if DEBUG
configurationBuilder.AddJsonFile("appsettings.debug.json");
#elif AZURE
configurationBuilder.AddJsonFile("appsettings.azure.json");
#elif LINUX
configurationBuilder.AddJsonFile("appsettings.linux.json");
#endif

configurationBuilder.AddJsonFile("appsettings.user.json");

var configuration = configurationBuilder.Build();

Console.WriteLine(configuration.GetValue<string>("GlobalSetting"));
Console.WriteLine(configuration.GetValue<string>("PlatformSetting"));
Console.WriteLine(configuration.GetValue<string>("UserSetting"));
```

## Testing the configurations

Now, if we run our application using the debug configuration, it will take the configuration of the debug settings file:

```shell
> dotnet run --configuration Debug
this is a global setting
this is debug
overwritten by appsettings.user.json
```

We can also do the same for other configurations:

```shell
> dotnet run --configuration ReleaseAzure
this is a global setting
this is azure
overwritten by appsettings.user.json
```

```shell
> dotnet run --configuration ReleaseLinux
this is a global setting
this is linux
overwritten by appsettings.user.json
```

## Final thoughts

This was a very simple tutorial, but I hope you can see the advantages of this approach. 

For example, in your build pipeline, you can publish for different configurations by simply changing the configuration in the command. The application already handles the complexity of different platforms within itself.

Another advantage is that you can have as many "users" settings files as you want. Your team can handle the global and platform settings files, and different teams in your org can handle their files with the properties they care about. Just don't forget to have all of them properly documented in your org knowledge base to avoid any confusion!

The code used for this post can be found at: https://github.com/rogeliogamez92/blog-multiple-apsettings.