Section 4 of 6

Configuration & appsettings.json

đŸŽ¯ What You'll Learn

  • Understanding ASP.NET Core configuration system
  • Working with appsettings.json
  • Environment-specific configuration files
  • Configuration sources and precedence
  • Reading configuration values
  • Strongly-typed configuration with IConfiguration
  • Connection strings and secrets management
  • Best practices for InvenTrack configuration

The Configuration System

ASP.NET Core has a flexible, hierarchical configuration system that loads settings from multiple sources:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. User secrets (Development only)
  4. Environment variables
  5. Command-line arguments
💡 Configuration Precedence

Later sources override earlier ones. For example, environment variables override appsettings.json. This lets you have default settings in JSON files and override them in production with environment variables.

appsettings.json Structure

The default appsettings.json looks like this:

appsettings.json JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

InvenTrack Configuration Example

Here's a more complete configuration for InvenTrack:

appsettings.json JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Information"
    }
  },
  "AllowedHosts": "*",
  
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=InvenTrack;Trusted_Connection=true;TrustServerCertificate=true"
  },
  
  "JwtSettings": {
    "Secret": "your-secret-key-here-min-32-chars",
    "Issuer": "InvenTrack.Api",
    "Audience": "InvenTrack.Client",
    "ExpirationMinutes": 60
  },
  
  "EmailSettings": {
    "SmtpServer": "smtp.gmail.com",
    "SmtpPort": 587,
    "SenderEmail": "noreply@inventtrack.com",
    "SenderName": "InvenTrack System",
    "EnableSsl": true
  },
  
  "InventorySettings": {
    "LowStockThreshold": 10,
    "EnableAutoReorder": false,
    "DefaultCurrency": "USD"
  },
  
  "CorsSettings": {
    "AllowedOrigins": [
      "http://localhost:3000",
      "http://localhost:4200"
    ]
  }
}
âš ī¸ Never Commit Secrets!

Don't put real passwords, API keys, or connection strings in appsettings.json if you're committing to Git. Use User Secrets for development and environment variables for production.

Environment-Specific Configuration

You can have different settings for different environments:

  • appsettings.json - Base settings (all environments)
  • appsettings.Development.json - Development overrides
  • appsettings.Production.json - Production overrides
  • appsettings.Staging.json - Staging overrides

appsettings.Development.json

appsettings.Development.json JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft.AspNetCore": "Information"
    }
  },
  
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=InvenTrack_Dev;Trusted_Connection=true;TrustServerCertificate=true"
  },
  
  "EmailSettings": {
    "SmtpServer": "localhost",
    "SmtpPort": 25,
    "EnableSsl": false
  }
}

appsettings.Production.json

appsettings.Production.json JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.AspNetCore": "Error"
    }
  },
  
  // Connection string set via environment variable
  
  "InventorySettings": {
    "EnableAutoReorder": true
  }
}
â„šī¸ How It Works

ASP.NET Core loads appsettings.json first, then loads appsettings.{Environment}.json and merges/overrides values. The environment is determined by the ASPNETCORE_ENVIRONMENT variable.

Reading Configuration Values

Method 1: Direct Access with IConfiguration

ProductsController.cs C#
public class ProductsController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public ProductsController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpGet("settings")]
    public IActionResult GetSettings()
    {
        // Simple value
        var allowedHosts = _configuration["AllowedHosts"];
        
        // Nested value
        var logLevel = _configuration["Logging:LogLevel:Default"];
        
        // Connection string
        var connString = _configuration.GetConnectionString("DefaultConnection");
        
        // With default value
        var threshold = _configuration.GetValue<int>("InventorySettings:LowStockThreshold", 5);

        return Ok(new { allowedHosts, logLevel, threshold });
    }
}

Configuration Path Syntax

JSON Structure Configuration Path
{ "Key": "Value" } "Key"
{ "Parent": { "Child": "Value" } } "Parent:Child"
{ "Array": ["A", "B"] } "Array:0", "Array:1"

Method 2: GetSection

Reading a Section C#
// Get entire section
var jwtSection = _configuration.GetSection("JwtSettings");

// Read values from section
var secret = jwtSection["Secret"];
var issuer = jwtSection["Issuer"];
var expiration = jwtSection.GetValue<int>("ExpirationMinutes");

Method 3: Bind to Object

Binding to POCO C#
// Define a class
public class JwtSettings
{
    public string Secret { get; set; } = string.Empty;
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public int ExpirationMinutes { get; set; }
}

// Bind configuration to object
var jwtSettings = new JwtSettings();
_configuration.GetSection("JwtSettings").Bind(jwtSettings);

// Or use Get<T> (creates new instance)
var jwtSettings2 = _configuration.GetSection("JwtSettings").Get<JwtSettings>();

Connection Strings

Connection strings have a special section and helper method:

appsettings.json JSON
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=InvenTrack;...",
    "ReportingDb": "Server=localhost;Database=InvenTrack_Reports;..."
  }
}
Reading Connection Strings C#
// In Program.cs
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<InvenTrackDbContext>(options =>
    options.UseSqlServer(connectionString));

User Secrets (Development)

For development, use User Secrets to store sensitive data outside your project folder:

Initialize User Secrets

Terminal Shell
# Initialize user secrets for your project
dotnet user-secrets init

# Set a secret
dotnet user-secrets set "JwtSettings:Secret" "my-super-secret-key-min-32-chars"

# Set connection string
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=InvenTrack;User Id=sa;Password=MyPassword123"

# List all secrets
dotnet user-secrets list

# Remove a secret
dotnet user-secrets remove "JwtSettings:Secret"

# Clear all secrets
dotnet user-secrets clear
â„šī¸ Where Are Secrets Stored?

User secrets are stored in:
Windows: %APPDATA%\Microsoft\UserSecrets\{id}\secrets.json
macOS/Linux: ~/.microsoft/usersecrets/{id}/secrets.json
They're not encrypted, just stored outside your project.

Environment Variables

In production, use environment variables for secrets:

Setting Environment Variables

Windows (PowerShell) PowerShell
# Set for current session
$env:ConnectionStrings__DefaultConnection = "Server=prod-server;..."

# Set permanently (system-wide)
[System.Environment]::SetEnvironmentVariable("ConnectionStrings__DefaultConnection", "Server=prod-server;...", "Machine")
Linux/macOS (Bash) Bash
# Set for current session
export ConnectionStrings__DefaultConnection="Server=prod-server;..."

# Add to ~/.bashrc or ~/.zshrc for persistence
echo 'export ConnectionStrings__DefaultConnection="Server=prod-server;..."' >> ~/.bashrc
âš ī¸ Environment Variable Syntax

Use double underscores (__) to represent hierarchy in environment variables:
ConnectionStrings:DefaultConnection → ConnectionStrings__DefaultConnection
JwtSettings:Secret → JwtSettings__Secret

Configuration in Program.cs

You can customize configuration sources:

Program.cs C#
var builder = WebApplication.CreateBuilder(args);

// Add custom configuration sources
builder.Configuration
    .AddJsonFile("appsettings.Custom.json", optional: true)
    .AddXmlFile("config.xml", optional: true)
    .AddIniFile("config.ini", optional: true)
    .AddEnvironmentVariables("INVENTTRACK_")
    .AddCommandLine(args);

Best Practices

1. Never Commit Secrets

  • Use User Secrets for development
  • Use environment variables for production
  • Add appsettings.*.json to .gitignore if it contains secrets

2. Use Strongly-Typed Configuration

Instead of reading strings everywhere, use the Options pattern (covered in next section):

Good Practice C#
// Register options
builder.Services.Configure<JwtSettings>(
    builder.Configuration.GetSection("JwtSettings"));

// Inject IOptions<JwtSettings> instead of IConfiguration

3. Organize Configuration Logically

Good Structure JSON
{
  // Framework settings
  "Logging": { ... },
  "AllowedHosts": "*",
  
  // Infrastructure
  "ConnectionStrings": { ... },
  
  // Application settings (grouped by feature)
  "JwtSettings": { ... },
  "EmailSettings": { ... },
  "InventorySettings": { ... }
}

4. Use Environment-Specific Files

  • appsettings.json - Shared defaults
  • appsettings.Development.json - Dev overrides (commit this)
  • appsettings.Production.json - Prod overrides (minimal, no secrets)

5. Validate Configuration

Validation C#
// In Program.cs, after building
var jwtSecret = builder.Configuration["JwtSettings:Secret"];
if (string.IsNullOrEmpty(jwtSecret) || jwtSecret.Length < 32)
{
    throw new InvalidOperationException("JWT Secret must be at least 32 characters");
}

Key Takeaways

  • ASP.NET Core loads configuration from multiple sources
  • Configuration precedence: JSON → User Secrets → Env Vars → Command Line
  • appsettings.json contains base settings
  • appsettings.{Environment}.json overrides for specific environments
  • Access configuration via IConfiguration
  • Use colon (:) for hierarchy: "Parent:Child"
  • Use double underscore (__) in environment variables
  • User Secrets for development (never commit secrets!)
  • Environment variables for production
  • GetConnectionString() helper for connection strings
  • Bind() or Get<T>() for strongly-typed configuration
  • Validate critical configuration at startup
đŸŽ¯ Next Steps

You now understand how to manage configuration in ASP.NET Core! In the next section, we'll explore the Options Pattern—a better way to work with configuration using strongly-typed classes, validation, and dependency injection.