ASP.NET Core  

Mastering Configuration Management in ASP.NET Core: Secure and Scalable Use of AppSettings.json in Real-World Applications

Introduction

Every production-grade ASP.NET Core application needs configuration values, such as database connection strings, API URLs, environment switches, feature flags, and external service credentials. These configurations evolve across environments: local development, staging, UAT, and production.

ASP.NET Core provides a flexible configuration system built around appsettings.json, environment overrides (like appsettings.Development.json), and secure secret storage mechanisms. However, many applications still misuse configuration — storing passwords in plain text, mixing environment values in a single file, hardcoding values, or not supporting runtime changes.

This article provides a real-world implementation approach to using appsettings.json effectively and securely, ensuring maintainability, scalability, and compliance.

Real-World Problem Scenario

A fintech product deployed across multiple regions faced these issues:

  • Developers tested payment API keys locally using production values.

  • Production configuration leaked accidentally into version control.

  • Scaling environments required different connection strings.

  • The application needed feature toggles but was built with hardcoded flags.

After implementing a structured configuration strategy using appsettings.json, secure storage (Secrets Manager and Azure Key Vault), and strongly-typed options patterns, the system improved in stability and governance.

This case demonstrates why configuration must be treated as a core architecture concern, not an afterthought.

What appsettings.json Is and Why It Exists

appsettings.json is a JSON-based configuration file automatically read by ASP.NET Core via its built-in configuration provider.

It supports:

  • Key-value configuration

  • Hierarchical configuration

  • Typed mapping to .NET objects

  • Environment-specific overrides

  • Secure replacement via external providers

Example Default File

{"ApplicationName": "PaymentAPI","ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=PaymentDB;Trusted_Connection=True;"},"ApiSettings": {
    "PaymentGatewayUrl": "https://dev-payments.example.com",
    "RetryCount": 3},"Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning"
    }}}

Binding Configuration to Strongly Typed Classes (Recommended Pattern)

Instead of manually reading configuration, bind it to a class:

public class ApiSettings
{
    public string PaymentGatewayUrl { get; set; }
    public int RetryCount { get; set; }
}

Register it in Program.cs:

builder.Services.Configure<ApiSettings>(
    builder.Configuration.GetSection("ApiSettings"));

Use it via DI:

public class PaymentService
{
    private readonly ApiSettings _config;

    public PaymentService(IOptions<ApiSettings> config)
    {
        _config = config.Value;
    }
}

This increases type safety and avoids runtime errors due to missing keys.

Environment-Specific Configurations

ASP.NET Core automatically loads environment files if named:

  • appsettings.Development.json

  • appsettings.Staging.json

  • appsettings.Production.json

Example environment file override

{"ConnectionStrings": {
    "DefaultConnection": "Server=prod-db;Database=PaymentDB;User Id=app;Password=secureProdPassword;"}}

This allows different values per environment without changing code.

Loading Order Priority

ASP.NET Core loads configuration in this order (later overwrites earlier):

  1. appsettings.json

  2. appsettings.{Environment}.json

  3. User secrets (Development only)

  4. Environment variables

  5. Command line arguments

  6. Cloud secret providers (Azure Key Vault, AWS Secrets Manager, etc.)

This layered structure enables override without modifying base files.

Never Store Secrets in appsettings.json

Hardcoded credentials pose a security risk.

Example of what NOT to do

"Password": "MyProductionDatabasePassword"

Instead, use:

  • Development → dotnet user-secrets

  • Production → Key Vault / AWS Secrets Manager / Kubernetes Secrets

Using User Secrets (Local Development)

Run

dotnet user-secrets init

Store a secret

dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=.;Database=DevDB;Encrypt=false"

User secrets are stored outside the project source, so they never enter Git.

Using Azure Key Vault (Production Example)

builder.Configuration.AddAzureKeyVault(new Uri(keyVaultUrl), new DefaultAzureCredential());

This replaces sensitive content securely during runtime.

Workflow Diagram

                          ┌───────────────────────┐
                          │ appsettings.json      │
                          └───────────────┬───────┘
                                          │
                          ┌───────────────▼─────────────────┐
                          │ appsettings.Environment.json     │
                          └───────────────┬─────────────────┘
                                          │
                          ┌───────────────▼─────────────────┐
                          │ User Secrets (Dev Only)          │
                          └───────────────┬─────────────────┘
                                          │
                          ┌───────────────▼─────────────────┐
                          │ Environment Variables            │
                          └───────────────┬─────────────────┘
                                          │
                          ┌───────────────▼─────────────────┐
                          │ Cloud Secret Providers           │
                          └──────────────────────────────────┘

Flowchart: Configuration Resolution Logic

           ┌──────────────────────┐
           │Application Starts    │
           └───────────┬─────────┘
                       │
                       ▼
      ┌──────────────────────────────────┐
      │ Load Base appsettings.json       │
      └───────────────────┬──────────────┘
                          │
                          ▼
     ┌────────────────────────────────────┐
     │ Load Environment-Specific File     │
     └─────────────────────┬──────────────┘
                           │
                           ▼
     ┌────────────────────────────────────┐
     │ Apply Secrets and Environment Vars │
     └─────────────────────┬──────────────┘
                           │
                           ▼
     ┌────────────────────────────────────┐
     │ Inject Final Configuration in DI   │
     └────────────────────────────────────┘

Feature Flags Example

{"FeatureFlags": {
    "EnableDiscountService": true}}

Use in code:

if (_config.EnableDiscountService)
{
    ApplyDiscount();
}

Future improvement: Connect with LaunchDarkly or Azure App Configuration.

Updating Values Without Recompiling

ASP.NET Core can reload configuration dynamically:

builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

Useful for:

  • Temporary traffic throttling

  • Emergency feature toggles

  • Dynamic cache settings

Best Practices

  1. Keep appsettings.json free of sensitive data.

  2. Use typed configuration via IOptions<T> or IOptionsSnapshot<T>.

  3. Use environment-specific configuration files.

  4. Enable runtime reload where appropriate.

  5. Use cloud secret services for production.

  6. Structure configuration cleanly using hierarchy.

  7. Version configuration changes and track environment drift.

Common Mistakes

MistakeResult
Storing secrets directlySecurity breach risk
Hardcoding values in codeNo environment flexibility
Using same config for dev and prodRisk of accidental live API use
Not using typed configurationTypo-based silent failures

Final Recommendations

  • Treat configuration as code but protect secrets separately.

  • Keep configuration modular, versioned, and environment-specific.

  • Implement automated validation for critical configuration keys.

  • Combine configuration with logging, monitoring, and feature flags for full observability.

Conclusion

Configuration management is a core engineering capability in ASP.NET Core applications. When used properly, appsettings.json enables maintainability, security, flexibility, and predictable deployment cycles. By layering configuration, protecting secrets, and using strongly typed access, teams can build reliable systems ready for enterprise-scale operations.