# A Practical Guide to ASP.NET Core Configuration Best Practices.

## What is application configuration?

In a .NET application, application configuration refers to the process of loading settings—typically key-value pairs—into your **application or hosting environment**. These settings can come from multiple sources, known as configuration providers.

### ⚙️ Host and Application Configuration in ASP.NET Core

ASP.NET Core separates configuration into two main categories:

#### 1\. **Host Configuration**

* Controls **how the application is hosted and started** (e.g., server URLs, environment).
    
* Loaded early—before the app is built.
    
* Examples:
    
    * `ASPNETCORE_ENVIRONMENT` – sets the environment (e.g., Development, Production)
        
    * `DOTNET_URLS` – sets the URLs the app will listen on
        

#### 2\. **Application Configuration**

* Governs **application behavior and feature settings** used at runtime.
    
* Loaded after the host is built.
    
* Examples:
    
    * `ConnectionStrings:Default` – database connection string
        
    * `JwtSettings:Secret` – authentication secret
        
    * `MyCustomFeature:Enabled` – feature flags
        

Both types of configuration are layered from multiple sources (JSON files, env vars, CLI args, etc.), but **their timing and purpose differ**.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip:</strong> To control which environment-specific settings are loaded, set the <code>ASPNETCORE_ENVIRONMENT</code> or <code>DOTNET_ENVIRONMENT</code> variable to the desired environment (e.g., <code>Development</code>, <code>Staging</code>, <code>Production</code>). This determines which <code>appsettings.{Environment}.json</code> file will be used at runtime.</div>
</div>

### Configuration priority.

Even though **host configuration** is loaded first and used to build the application host, **application configuration has higher effective priority at runtime**.

🔑 Here's how ASP.NET Core resolves conflicts when the same key exists in multiple sources:

| Priority | Source | Type |
| --- | --- | --- |
| 1️⃣ | Command-line arguments | App + Host |
| 2️⃣ | Environment variables (no prefix) | Application |
| 3️⃣ | User secrets (Development only) | Application |
| 4️⃣ | `appsettings.{Environment}.json` | Application |
| 5️⃣ | `appsettings.json` | Application |
| 6️⃣ | `DOTNET_`, `ASPNETCORE_` environment variables | **Host only** |

✅ In short: **application configuration overrides host configuration** during runtime. This means your feature flags, secrets, and connection strings won’t be unintentionally overridden by host-level settings.

### 🏗️ WebApplicationBuilder in ASP.NET Core Application.

When you initialize an ASP.NET Core application, you'll commonly see the following line of code:

```csharp
var builder = WebApplication.CreateBuilder(args);
```

This single line does a lot under the hood. One of its key responsibilities is setting up a **default configuration system** with a built-in order of precedence. Here's how configuration sources are loaded—from **lowest** to **highest** priority:

1. `appsettings.json`
    
2. `appsettings.{Environment}.json` (e.g., `appsettings.Development.json`)
    
3. **User secrets** (only in the `Development` environment)
    
4. **Environment variables**
    
5. **Command-line arguments**
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This means values defined in later sources override those from earlier ones.</div>
</div>

Internally, this is roughly equivalent to the following:

```csharp
Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        var env = hostingContext.HostingEnvironment;

        config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

        if (env.IsDevelopment())
        {
            config.AddUserSecrets<Program>();
        }

        config.AddEnvironmentVariables();

        if (args != null)
        {
            config.AddCommandLine(args);
        }
    });
```

This layered configuration model gives you flexibility to manage settings in a way that’s appropriate for different environments and deployment scenarios.

### 🦀 Common Mistake: Re-adding Configuration Sources

A common pitfall in ASP.NET Core is manually re-adding configuration providers **after** this line:

```csharp
var builder = WebApplication.CreateBuilder(args);
```

For example:

```csharp
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                     .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
```

At first glance, this might seem harmless—but it's usually **unnecessary and potentially harmful**.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Avoid re-adding default configuration providers unless you have a specific reason to change their behavior (e.g., setting <code>optional: false</code>, changing file paths, or customizing the order). Leverage the default configuration setup and only extend it when necessary.</div>
</div>

By manually adding configuration files again, you risk:

* **Overriding existing values unintentionally**
    
* **Redundant file reads**
    
* **Unexpected configuration merge behavior.**
    

### 🤝 Understanding Merged Configuration

The configuration system in ASP.NET Core **merges** settings from all providers. If multiple sources define values for the same key, the **last one added wins**.

#### 🔹 Adding New Keys

Suppose you have the following in your `appsettings.json`:

```json
"JwtSetting": {
  "Issuer": "yourIssuer",
  "Audience": "yourAudience",
  "AccessTokenExpMinute": 10,
  "RefreshTokenExpiration": 30
}
```

And you define this in your **user secrets** (Development only):

```json
"JwtSetting": {
  "Secret": "your-secret-key"
}
```

The final merged configuration at runtime will be:

```json
"JwtSetting": {
  "Issuer": "yourIssuer",
  "Audience": "yourAudience",
  "AccessTokenExpMinute": 10,
  "RefreshTokenExpiration": 30,
  "Secret": "your-secret-key"
}
```

Each source contributes to the overall object unless a full override is performed.

#### 🔹 Overriding Existing Keys

If the same key is defined in multiple places, the **source added last** takes precedence. For example:

```json
// appsettings.json
"ApiKey": "ValueFromAppSettings"
```

```json
// appsettings.Development.json
"ApiKey": "ValueFromDevSettings"
```

Then you set an environment variable:

```csharp
set ApiKey=ValueFromEnv
```

And run the application with a command-line override:

```csharp
dotnet run --ApiKey=ValueFromCommandLine
```

The final value of `ApiKey` will be:

```json
"ApiKey": "ValueFromCommandLine"
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Each configuration provider contributes keys, and later providers can override or augment earlier values. This layered model gives you powerful flexibility to manage config cleanly across environments.</div>
</div>

### Custom Configuration Sources

In addition to the built-in configuration providers, ASP.NET Core makes it easy to add **custom configuration sources**. This is especially useful for keeping sensitive data like secrets or credentials out of your codebase.

For example, instead of storing everything in `appsettings.json` you could store some settings in the **Azure App Configuration** or securely store secrets in the **Azure Key Vault**.

> 🔗 I’ve written a detailed guide on integrating Azure Key Vault with ASP.NET Core. You can read it here: [Azure Key Vault: Secure .NET App Secrets](https://kristiadhy.hashnode.dev/how-to-secure-your-net-application-secrets-with-azure-key-vault-free-code-repository-included)

## Conclusion

Using configuration properly in ASP.NET Core enables your application to be **environment-aware**, **secure**, and **maintainable**. By understanding how the configuration system merges values across sources—and by using the right source for the right purpose—you can avoid common pitfalls and keep your app flexible for development, testing, and production.

For best results:

* Let `WebApplication.CreateBuilder` handle the default setup.
    
* Use additional providers (like Azure Key Vault or custom JSON files) only when necessary.
    
* Understand and leverage the **configuration precedence order** to avoid surprises at runtime.
