
Previous article: Module 16 of 40 : ASP.NET Core Middleware Mastery: Custom Logging, Auth & Pipeline Control
Table of Contents
Introduction to Configuration in ASP.NET Core
Understanding the Configuration System
AppSettings.json Deep Dive
Environment-Specific Configuration
User Secrets for Development
Environment Variables Configuration
Command-Line Configuration
Custom Configuration Providers
Azure Key Vault Integration
AWS Secrets Manager
Database Configuration Provider
Configuration Best Practices
Security Considerations
Troubleshooting Common Issues
Real-World Implementation Examples
1. Introduction to Configuration in ASP.NET Core
Configuration management is a critical aspect of modern web application development. In ASP.NET Core, the configuration system has been completely redesigned to be more flexible, extensible, and cloud-ready. Let's explore why proper configuration management is essential:
Why Configuration Management Matters
Real-Life Scenario : Imagine you're developing an e-commerce application that needs to connect to different databases in development, staging, and production environments. Without proper configuration management, you'd be constantly changing connection strings, risking security breaches and deployment failures.
// Problematic approach - hardcoded configuration
public class DatabaseService
{
private string _connectionString = "Server=prod-db;Database=Shop;User=admin;Password=secret123";
public void Connect()
{
// This will fail in development and staging!
}
}
Evolution of Configuration in ASP.NET
From Web.config to Modern Approach :
ASP.NET Web Forms: XML-based Web.config with complex sections
ASP.NET MVC: Still Web.config with some improvements
ASP.NET Core: JSON-based, environment-aware, cloud-native
2. Understanding the Configuration System
Configuration Building Blocks
The ASP.NET Core configuration system is built around several key concepts:
IConfiguration Interface
public interface IConfiguration
{
string this[string key] { get; set; }
IConfigurationSection GetSection(string key);
IEnumerable<IConfigurationSection> GetChildren();
IChangeToken GetReloadToken();
}
Configuration Sources Hierarchy
ASP.NET Core loads configuration from multiple sources in a specific order:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
// Configuration sources are loaded in this order:
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. User Secrets (Development environment only)
// 4. Environment Variables
// 5. Command-line arguments
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Configuration Providers Deep Dive
// Detailed configuration setup with multiple providers
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var env = context.HostingEnvironment;
// Base appsettings
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
// Environment-specific appsettings
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: true);
// User Secrets for development
if (env.IsDevelopment())
{
config.AddUserSecrets<Program>();
}
// Environment variables
config.AddEnvironmentVariables();
// Command-line arguments
if (args != null)
{
config.AddCommandLine(args);
}
// Last provider wins - command line has highest priority
});
3. AppSettings.json Deep Dive
Structured Configuration with JSON
Real-Life Example : E-commerce application configuration
{
"StoreSettings": {
"StoreName": "TechMart Online",
"MaxProductsPerPage": 50,
"EnableWishlist": true,
"Currency": "USD",
"SupportedCountries": ["US", "CA", "UK", "AU"]
},
"Database": {
"ConnectionString": "Server=localhost;Database=TechMart;Trusted_Connection=true;",
"Timeout": 30,
"RetryCount": 3
},
"PaymentGateway": {
"ApiKey": "pk_test_123456789",
"BaseUrl": "https://api.payments.com/v1",
"WebhookSecret": "whsec_987654321",
"Settings": {
"Timeout": 10000,
"RetryPolicy": "ExponentialBackoff"
}
},
"EmailSettings": {
"SmtpServer": "smtp.techmart.com",
"Port": 587,
"Username": "[email protected]",
"FromAddress": "[email protected]",
"Templates": {
"OrderConfirmation": "templates/order-confirmation.html",
"PasswordReset": "templates/password-reset.html"
}
},
"CacheSettings": {
"RedisConnection": "localhost:6379",
"DefaultExpiration": 3600,
"Enabled": true
},
"Security": {
"Jwt": {
"SecretKey": "your-super-secret-key-here",
"Issuer": "TechMart",
"Audience": "TechMartUsers",
"ExpirationMinutes": 60
},
"Cors": {
"AllowedOrigins": ["https://techmart.com", "https://admin.techmart.com"]
}
}
}
Strongly-Typed Configuration Classes
// Configuration classes for strongly-typed access
public class StoreSettings
{
public string StoreName { get; set; }
public int MaxProductsPerPage { get; set; }
public bool EnableWishlist { get; set; }
public string Currency { get; set; }
public string[] SupportedCountries { get; set; }
}
public class DatabaseSettings
{
public string ConnectionString { get; set; }
public int Timeout { get; set; }
public int RetryCount { get; set; }
}
public class PaymentGatewaySettings
{
public string ApiKey { get; set; }
public string BaseUrl { get; set; }
public string WebhookSecret { get; set; }
public PaymentSettings Settings { get; set; }
}
public class PaymentSettings
{
public int Timeout { get; set; }
public string RetryPolicy { get; set; }
}
public class EmailSettings
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string FromAddress { get; set; }
public EmailTemplates Templates { get; set; }
}
public class EmailTemplates
{
public string OrderConfirmation { get; set; }
public string PasswordReset { get; set; }
}
public class CacheSettings
{
public string RedisConnection { get; set; }
public int DefaultExpiration { get; set; }
public bool Enabled { get; set; }
}
public class SecuritySettings
{
public JwtSettings Jwt { get; set; }
public CorsSettings Cors { get; set; }
}
public class JwtSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpirationMinutes { get; set; }
}
public class CorsSettings
{
public string[] AllowedOrigins { get; set; }
}
Configuration Binding and Validation
// Startup configuration with validation
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Bind configuration sections to strongly-typed classes
services.Configure<StoreSettings>(_configuration.GetSection("StoreSettings"));
services.Configure<DatabaseSettings>(_configuration.GetSection("Database"));
services.Configure<PaymentGatewaySettings>(_configuration.GetSection("PaymentGateway"));
services.Configure<EmailSettings>(_configuration.GetSection("EmailSettings"));
services.Configure<CacheSettings>(_configuration.GetSection("CacheSettings"));
services.Configure<SecuritySettings>(_configuration.GetSection("Security"));
// Advanced binding with validation
var storeSettings = new StoreSettings();
_configuration.GetSection("StoreSettings").Bind(storeSettings);
// Validate critical settings
if (string.IsNullOrEmpty(storeSettings.StoreName))
{
throw new InvalidOperationException("StoreName is required in configuration");
}
// Register services that use configuration
services.AddSingleton(storeSettings);
// Database configuration with validation
var dbSettings = _configuration.GetSection("Database").Get<DatabaseSettings>();
if (string.IsNullOrEmpty(dbSettings?.ConnectionString))
{
throw new InvalidOperationException("Database connection string is required");
}
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(dbSettings.ConnectionString));
}
}
4. Environment-Specific Configuration
Environment-Based Configuration Strategy
Real-Life Scenario : Different configurations for Development, Staging, and Production environments.
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Debug"
}
},
"Database": {
"ConnectionString": "Server=localhost;Database=TechMart_Dev;Trusted_Connection=true;",
"Timeout": 30,
"RetryCount": 3
},
"PaymentGateway": {
"BaseUrl": "https://api.sandbox.payments.com/v1",
"ApiKey": "pk_test_development_key"
},
"Features": {
"EnableSwagger": true,
"EnableDetailedErrors": true,
"UseInMemoryCache": true
}
}
// appsettings.Staging.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Database": {
"ConnectionString": "Server=staging-db.techmart.com;Database=TechMart_Staging;User Id=appuser;Password=staging_password;",
"Timeout": 30,
"RetryCount": 5
},
"PaymentGateway": {
"BaseUrl": "https://api.staging.payments.com/v1",
"ApiKey": "pk_test_staging_key"
},
"Features": {
"EnableSwagger": false,
"EnableDetailedErrors": true,
"UseInMemoryCache": false
}
}
// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning"
}
},
"Database": {
"ConnectionString": "Server=prod-db.techmart.com;Database=TechMart_Prod;User Id=appuser;Password=production_password;",
"Timeout": 30,
"RetryCount": 5
},
"PaymentGateway": {
"BaseUrl": "https://api.payments.com/v1",
"ApiKey": "pk_live_production_key"
},
"Features": {
"EnableSwagger": false,
"EnableDetailedErrors": false,
"UseInMemoryCache": false
}
}
Environment Detection and Configuration
// Program.cs with environment-specific setup
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var env = context.HostingEnvironment;
Console.WriteLine($"Environment: {env.EnvironmentName}");
Console.WriteLine($"Content Root: {env.ContentRootPath}");
Console.WriteLine($"Web Root: {env.WebRootPath}");
// Load environment-specific configuration
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChange: true);
// Additional configuration based on environment
if (env.IsDevelopment())
{
// Development-specific configuration
config.AddUserSecrets<Program>();
}
else if (env.IsStaging())
{
// Staging-specific configuration
config.AddJsonFile("appsettings.Staging.Secrets.json",
optional: true, reloadOnChange: true);
}
else if (env.IsProduction())
{
// Production-specific configuration
// Rely on environment variables and secure vaults
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Environment-Based Service Registration
// Environment-specific service configuration
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
// Common services for all environments
services.AddControllers();
services.AddHttpClient();
// Environment-specific services
if (Environment.IsDevelopment())
{
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TechMart API", Version = "v1" });
});
}
// Database configuration based on environment
if (Environment.IsDevelopment())
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
.EnableSensitiveDataLogging());
}
else
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
// Cache configuration
if (Configuration.GetValue<bool>("Features:UseInMemoryCache"))
{
services.AddMemoryCache();
}
else
{
var redisConnection = Configuration.GetValue<string>("CacheSettings:RedisConnection");
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnection;
});
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TechMart API v1"));
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
5. User Secrets for Development
Why Use User Secrets?
Real-Life Problem : Developers committing sensitive data to source control
// Before User Secrets - sensitive data in appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyApp;User Id=sa;Password=MySuperSecretPassword123!"
},
"ApiKeys": {
"SendGrid": "SG.abc123def456ghi789",
"Stripe": "sk_test_1234567890abcdef"
}
}
Setting Up User Secrets
Step 1. Initialize User Secrets
# In the project directory
dotnet user-secrets init
Step 2. Add Secrets
# Add connection string
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=MyApp;User Id=sa;Password=DevPassword123!"
# Add API keys
dotnet user-secrets set "ApiKeys:SendGrid" "SG.devkey123456789"
dotnet user-secrets set "ApiKeys:Stripe" "sk_test_devkey123456789"
# Add JWT secret
dotnet user-secrets set "Security:Jwt:SecretKey" "dev-super-secret-jwt-key-2024"
Step 3. secrets.json File Structure
{
"ConnectionStrings:DefaultConnection": "Server=localhost;Database=MyApp_Dev;User Id=sa;Password=DevPassword123!",
"ApiKeys": {
"SendGrid": "SG.devkey123456789",
"Stripe": "sk_test_devkey123456789"
},
"Security:Jwt:SecretKey": "dev-super-secret-jwt-key-2024",
"PaymentGateway": {
"ApiKey": "pk_test_dev_123456789",
"WebhookSecret": "whsec_dev_987654321"
}
}
Programmatic Access to User Secrets
// Advanced User Secrets management
public class DevelopmentSecretsManager
{
private readonly IConfiguration _configuration;
public DevelopmentSecretsManager(IConfiguration configuration)
{
_configuration = configuration;
}
public void ValidateDevelopmentSecrets()
{
var requiredSecrets = new[]
{
"ConnectionStrings:DefaultConnection",
"ApiKeys:SendGrid",
"Security:Jwt:SecretKey"
};
var missingSecrets = requiredSecrets
.Where(secret => string.IsNullOrEmpty(_configuration[secret]))
.ToList();
if (missingSecrets.Any())
{
throw new InvalidOperationException(
$"Missing required development secrets: {string.Join(", ", missingSecrets)}. " +
"Run 'dotnet user-secrets set [key] [value]' to set them.");
}
}
public string GetSecret(string key)
{
var value = _configuration[key];
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException($"Secret '{key}' not found in user secrets");
}
return value;
}
public T GetSecret<T>(string key)
{
var value = GetSecret(key);
return (T)Convert.ChangeType(value, typeof(T));
}
}
// Integration in Startup
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
// Validate secrets in development
if (Environment.IsDevelopment())
{
var secretsManager = new DevelopmentSecretsManager(Configuration);
secretsManager.ValidateDevelopmentSecrets();
}
// Use secrets in services
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
}
}
User Secrets Best Practices
// Secure development practices
public static class SecretsBestPractices
{
public static void EnsureSecretsAreNotInAppSettings(IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
var appSettingsPath = Path.Combine(env.ContentRootPath, "appsettings.json");
var appSettingsContent = File.ReadAllText(appSettingsPath);
var sensitivePatterns = new[]
{
@"password=([^;""']+)",
@"pwd=([^;""']+)",
@"api[_-]?key=([^;""']+)",
@"secret=([^;""']+)"
};
foreach (var pattern in sensitivePatterns)
{
var matches = System.Text.RegularExpressions.Regex.Matches(appSettingsContent, pattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (matches.Count > 0)
{
throw new InvalidOperationException(
"Sensitive data found in appsettings.json. Move to user secrets.");
}
}
}
}
}
6. Environment Variables Configuration
Environment Variables in Different Environments
Real-Life Scenario : Deployment to various environments (Docker, Azure, AWS, Kubernetes).
// Environment variable configuration provider
public static class EnvironmentVariablesConfig
{
public static void ConfigureEnvironmentVariables(this IConfigurationBuilder config)
{
config.AddEnvironmentVariables();
// Add prefix for application-specific variables
config.AddEnvironmentVariables("TECHMART_");
}
}
// Usage in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json",
optional: true)
.AddEnvironmentVariables("TECHMART_") // Application-specific
.AddEnvironmentVariables(); // All environment variables
});
Environment Variable Naming Conventions
// Environment variable mapping examples
public static class EnvVarConstants
{
// Database
public const string DB_CONNECTION = "TECHMART_DB_CONNECTION";
public const string DB_TIMEOUT = "TECHMART_DB_TIMEOUT";
// API Keys
public const string SENDGRID_API_KEY = "TECHMART_SENDGRID_API_KEY";
public const string STRIPE_API_KEY = "TECHMART_STRIPE_API_KEY";
// Application Settings
public const string ENVIRONMENT = "ASPNETCORE_ENVIRONMENT";
public const string URLs = "ASPNETCORE_URLS";
// Feature Flags
public const string FEATURE_SWAGGER = "TECHMART_FEATURE_SWAGGER";
public const string FEATURE_CACHE = "TECHMART_FEATURE_CACHE";
}
// Environment variable helper class
public class EnvironmentConfig
{
private readonly IConfiguration _configuration;
public EnvironmentConfig(IConfiguration configuration)
{
_configuration = configuration;
}
public string DatabaseConnection =>
GetRequiredValue(EnvVarConstants.DB_CONNECTION);
public string SendGridApiKey =>
GetRequiredValue(EnvVarConstants.SENDGRID_API_KEY);
public bool EnableSwagger =>
GetValue(EnvVarConstants.FEATURE_SWAGGER, false);
public int DatabaseTimeout =>
GetValue(EnvVarConstants.DB_TIMEOUT, 30);
private string GetRequiredValue(string key)
{
var value = _configuration[key];
if (string.IsNullOrEmpty(value))
{
throw new InvalidOperationException($"Required environment variable '{key}' is missing");
}
return value;
}
private T GetValue<T>(string key, T defaultValue)
{
var value = _configuration[key];
if (string.IsNullOrEmpty(value))
{
return defaultValue;
}
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Unable to convert environment variable '{key}' value '{value}' to type {typeof(T).Name}", ex);
}
}
}
Docker and Container Configuration
# Dockerfile with environment variables
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["TechMart.Web/TechMart.Web.csproj", "TechMart.Web/"]
RUN dotnet restore "TechMart.Web/TechMart.Web.csproj"
COPY . .
WORKDIR "/src/TechMart.Web"
RUN dotnet build "TechMart.Web.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TechMart.Web.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# Environment variables for container
ENV ASPNETCORE_ENVIRONMENT=Production
ENV ASPNETCORE_URLS=http://+:80
ENV TECHMART_DB_TIMEOUT=30
ENV TECHMART_FEATURE_SWAGGER=false
ENTRYPOINT ["dotnet", "TechMart.Web.dll"]
# docker-compose.yml with environment variables
version: '3.8'
services:
techmart.web:
image: techmart/web:latest
build:
context: .
dockerfile: TechMart.Web/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
- TECHMART_DB_CONNECTION=Server=techmart.db;Database=TechMart_Dev;User Id=sa;Password=DevPassword123!
- TECHMART_SENDGRID_API_KEY=SG.docker_dev_key
- TECHMART_STRIPE_API_KEY=sk_test_docker_key
- TECHMART_FEATURE_SWAGGER=true
ports:
- "5000:80"
depends_on:
- techmart.db
techmart.db:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- SA_PASSWORD=DevPassword123!
- ACCEPT_EULA=Y
ports:
- "1433:1433"
7. Command-Line Configuration
Command-Line Arguments in ASP.NET Core
Real-Life Scenario : Overriding configuration for specific deployments or debugging.
// Command-line configuration setup
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// Configure command-line arguments with custom mappings
var switchMappings = new Dictionary<string, string>
{
{ "--urls", "urls" },
{ "--environment", "environment" },
{ "--db-connection", "ConnectionStrings:DefaultConnection" },
{ "--enable-swagger", "Features:EnableSwagger" },
{ "--cache-timeout", "CacheSettings:DefaultExpiration" },
{ "--log-level", "Logging:LogLevel:Default" }
};
config.AddCommandLine(args, switchMappings);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Advanced Command-Line Processing
// Custom command-line argument processor
public class CommandLineConfig
{
private readonly string[] _args;
public CommandLineConfig(string[] args)
{
_args = args;
}
public Dictionary<string, string> ParseArguments()
{
var config = new Dictionary<string, string>();
for (int i = 0; i < _args.Length; i++)
{
var arg = _args[i];
if (arg.StartsWith("--"))
{
var key = arg.Substring(2);
string value = "true"; // Default for flags
// Check if next argument is a value (not another flag)
if (i + 1 < _args.Length && !_args[i + 1].StartsWith("--"))
{
value = _args[i + 1];
i++; // Skip the value in next iteration
}
config[key] = value;
}
else if (arg.StartsWith("/"))
{
// Windows-style arguments: /key value or /key:value
var parts = arg.Substring(1).Split(':');
var key = parts[0];
var value = parts.Length > 1 ? parts[1] : "true";
config[key] = value;
}
else if (arg.Contains("="))
{
// Key=value format
var parts = arg.Split('=');
if (parts.Length == 2)
{
config[parts[0]] = parts[1];
}
}
}
return config;
}
public void ValidateRequiredArguments(Dictionary<string, string> arguments)
{
var requiredArgs = new[] { "db-connection", "environment" };
var missingArgs = requiredArgs.Where(arg => !arguments.ContainsKey(arg)).ToList();
if (missingArgs.Any())
{
throw new ArgumentException(
$"Missing required command-line arguments: {string.Join(", ", missingArgs)}");
}
}
}
// Integration with configuration
public static class CommandLineConfigExtensions
{
public static IConfigurationBuilder AddCustomCommandLine(
this IConfigurationBuilder builder,
string[] args)
{
var commandLineConfig = new CommandLineConfig(args);
var arguments = commandLineConfig.ParseArguments();
// Validate required arguments
commandLineConfig.ValidateRequiredArguments(arguments);
// Add to configuration
foreach (var (key, value) in arguments)
{
Environment.SetEnvironmentVariable($"CMDLINE_{key}", value);
}
return builder.AddInMemoryCollection(arguments);
}
}
Real-World Command-Line Usage Examples
# Development with specific settings
dotnet run --urls "https://localhost:5001" --environment "Development" --db-connection "Server=localhost;Database=DevDB;Trusted_Connection=true" --enable-swagger true --log-level "Debug"
# Production with minimal logging
dotnet run --urls "http://*:80" --environment "Production" --db-connection "Server=prod-db;Database=ProdDB;User Id=appuser;Password=ProdPass123!" --enable-swagger false --log-level "Warning"
# Docker container with environment overrides
docker run -p 8080:80 techmart/app --db-connection "Server=sql-server;Database=AppDB;User Id=sa;Password=DockerPass123!" --cache-timeout 3600
8. Custom Configuration Providers
Building Custom Configuration Providers
Real-Life Scenario : Reading configuration from a custom XML file or external service.
// Custom XML configuration provider
public class XmlConfigurationProvider : ConfigurationProvider
{
private readonly string _filePath;
public XmlConfigurationProvider(string filePath)
{
_filePath = filePath;
}
public override void Load()
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try
{
if (File.Exists(_filePath))
{
var doc = XDocument.Load(_filePath);
ParseXmlElement(doc.Root, "", data);
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to load XML configuration from {_filePath}", ex);
}
Data = data;
}
private void ParseXmlElement(XElement element, string prefix, Dictionary<string, string> data)
{
foreach (var child in element.Elements())
{
var currentKey = string.IsNullOrEmpty(prefix)
? child.Name.LocalName
: $"{prefix}:{child.Name.LocalName}";
if (child.HasElements)
{
ParseXmlElement(child, currentKey, data);
}
else
{
data[currentKey] = child.Value;
}
}
}
}
public class XmlConfigurationSource : IConfigurationSource
{
public string FilePath { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new XmlConfigurationProvider(FilePath);
}
}
public static class XmlConfigurationExtensions
{
public static IConfigurationBuilder AddXmlFile(
this IConfigurationBuilder builder,
string path,
bool optional = false)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("File path must not be null or empty", nameof(path));
}
var source = new XmlConfigurationSource
{
FilePath = path,
Optional = optional
};
builder.Add(source);
return builder;
}
}
Database Configuration Provider
// Database configuration provider for dynamic configuration
public class DatabaseConfigurationProvider : ConfigurationProvider, IDisposable
{
private readonly DatabaseConfigurationSource _source;
private readonly Timer _refreshTimer;
private bool _disposed = false;
public DatabaseConfigurationProvider(DatabaseConfigurationSource source)
{
_source = source;
if (_source.ReloadOnChange && _source.ReloadInterval > 0)
{
_refreshTimer = new Timer(RefreshConfiguration, null,
TimeSpan.FromSeconds(_source.ReloadInterval),
TimeSpan.FromSeconds(_source.ReloadInterval));
}
}
public override void Load()
{
RefreshConfiguration();
}
private void RefreshConfiguration(object state = null)
{
try
{
var newData = LoadConfigurationFromDatabase();
// Only update if configuration changed
if (!Data.SequenceEqual(newData))
{
Data = newData;
OnReload(); // Notify about configuration changes
}
}
catch (Exception ex)
{
// Log error but don't crash the application
Console.WriteLine($"Failed to refresh configuration from database: {ex.Message}");
}
}
private Dictionary<string, string> LoadConfigurationFromDatabase()
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using var connection = new SqlConnection(_source.ConnectionString);
connection.Open();
var command = new SqlCommand(
"SELECT [Key], [Value], [Environment] FROM Configuration WHERE [Environment] = @Environment OR [Environment] IS NULL",
connection);
command.Parameters.AddWithValue("@Environment", _source.Environment);
using var reader = command.ExecuteReader();
while (reader.Read())
{
var key = reader.GetString(0);
var value = reader.GetString(1);
data[key] = value;
}
return data;
}
public void Dispose()
{
if (!_disposed)
{
_refreshTimer?.Dispose();
_disposed = true;
}
}
}
public class DatabaseConfigurationSource : IConfigurationSource
{
public string ConnectionString { get; set; }
public string Environment { get; set; }
public bool ReloadOnChange { get; set; }
public int ReloadInterval { get; set; } = 30; // seconds
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new DatabaseConfigurationProvider(this);
}
}
public static class DatabaseConfigurationExtensions
{
public static IConfigurationBuilder AddDatabaseConfiguration(
this IConfigurationBuilder builder,
string connectionString,
string environment,
bool reloadOnChange = false,
int reloadInterval = 30)
{
return builder.Add(new DatabaseConfigurationSource
{
ConnectionString = connectionString,
Environment = environment,
ReloadOnChange = reloadOnChange,
ReloadInterval = reloadInterval
});
}
}
9. Azure Key Vault Integration
Securing Secrets with Azure Key Vault
Real-Life Scenario : Enterprise application with multiple environments and compliance requirements.
// Azure Key Vault configuration provider
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
// Validate Key Vault connectivity
await ValidateKeyVaultConnection(host.Services);
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
// Add Azure Key Vault
var keyVaultEndpoint = builtConfig["AzureKeyVault:Endpoint"];
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
keyVaultEndpoint,
keyVaultClient,
new DefaultKeyVaultSecretManager());
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
private static async Task ValidateKeyVaultConnection(IServiceProvider services)
{
try
{
var configuration = services.GetRequiredService<IConfiguration>();
var keyVaultEndpoint = configuration["AzureKeyVault:Endpoint"];
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
var secretClient = new SecretClient(new Uri(keyVaultEndpoint),
new DefaultAzureCredential());
// Test connection by trying to list secrets
await secretClient.GetPropertiesOfSecretsAsync().FirstAsync();
Console.WriteLine("✅ Azure Key Vault connection successful");
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Azure Key Vault connection failed: {ex.Message}");
throw;
}
}
}
Advanced Key Vault Management
// Key Vault secret management service
public class KeyVaultSecretManager
{
private readonly SecretClient _secretClient;
private readonly ILogger<KeyVaultSecretManager> _logger;
public KeyVaultSecretManager(IConfiguration configuration, ILogger<KeyVaultSecretManager> logger)
{
var keyVaultEndpoint = configuration["AzureKeyVault:Endpoint"];
if (string.IsNullOrEmpty(keyVaultEndpoint))
{
throw new InvalidOperationException("Azure Key Vault endpoint is not configured");
}
_secretClient = new SecretClient(new Uri(keyVaultEndpoint), new DefaultAzureCredential());
_logger = logger;
}
public async Task<string> GetSecretAsync(string secretName)
{
try
{
var secret = await _secretClient.GetSecretAsync(secretName);
return secret.Value.Value;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
_logger.LogWarning("Secret '{SecretName}' not found in Key Vault", secretName);
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve secret '{SecretName}' from Key Vault", secretName);
throw;
}
}
public async Task SetSecretAsync(string secretName, string secretValue)
{
try
{
await _secretClient.SetSecretAsync(secretName, secretValue);
_logger.LogInformation("Secret '{SecretName}' updated in Key Vault", secretName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to set secret '{SecretName}' in Key Vault", secretName);
throw;
}
}
public async Task<IEnumerable<string>> ListSecretsAsync()
{
var secrets = new List<string>();
await foreach (var secretProperties in _secretClient.GetPropertiesOfSecretsAsync())
{
secrets.Add(secretProperties.Name);
}
return secrets;
}
public async Task<bool> SecretExistsAsync(string secretName)
{
try
{
await _secretClient.GetSecretAsync(secretName);
return true;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
return false;
}
}
}
// Startup configuration for Key Vault
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
// Register Key Vault service
services.AddSingleton<KeyVaultSecretManager>();
// Configure database using Key Vault
services.AddDbContext<ApplicationDbContext>(options =>
{
var connectionString = Configuration.GetConnectionString("DefaultConnection");
options.UseSqlServer(connectionString);
});
// Configure other services with secrets from Key Vault
services.Configure<PaymentGatewaySettings>(options =>
{
options.ApiKey = Configuration["PaymentGateway--ApiKey"];
options.WebhookSecret = Configuration["PaymentGateway--WebhookSecret"];
});
}
}
Key Vault Secret Rotation
// Secret rotation service
public class SecretRotationService : BackgroundService
{
private readonly KeyVaultSecretManager _secretManager;
private readonly IConfiguration _configuration;
private readonly ILogger<SecretRotationService> _logger;
public SecretRotationService(
KeyVaultSecretManager secretManager,
IConfiguration configuration,
ILogger<SecretRotationService> logger)
{
_secretManager = secretManager;
_configuration = configuration;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await RotateSecretsIfNeeded();
await Task.Delay(TimeSpan.FromHours(24), stoppingToken); // Check daily
}
catch (Exception ex)
{
_logger.LogError(ex, "Secret rotation failed");
await Task.Delay(TimeSpan.FromHours(1), stoppingToken); // Retry after 1 hour
}
}
}
private async Task RotateSecretsIfNeeded()
{
var secretsToRotate = new[] { "DatabasePassword", "ApiKey--Primary", "JwtSecret" };
foreach (var secretName in secretsToRotate)
{
if (await ShouldRotateSecret(secretName))
{
await RotateSecret(secretName);
}
}
}
private async Task<bool> ShouldRotateSecret(string secretName)
{
// Check secret age or other rotation criteria
var secret = await _secretManager.GetSecretAsync(secretName);
if (secret == null) return false;
// Implement rotation logic based on your requirements
return false; // Placeholder
}
private async Task RotateSecret(string secretName)
{
_logger.LogInformation("Rotating secret: {SecretName}", secretName);
// Generate new secret value
var newSecretValue = GenerateNewSecret(secretName);
// Set new version in Key Vault
await _secretManager.SetSecretAsync(secretName, newSecretValue);
_logger.LogInformation("Successfully rotated secret: {SecretName}", secretName);
}
private string GenerateNewSecret(string secretName)
{
return secretName switch
{
"DatabasePassword" => GenerateSecurePassword(),
"ApiKey--Primary" => GenerateApiKey(),
"JwtSecret" => GenerateJwtSecret(),
_ => Guid.NewGuid().ToString()
};
}
private string GenerateSecurePassword()
{
const string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*";
var random = new Random();
return new string(Enumerable.Repeat(validChars, 32)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
private string GenerateApiKey() => $"key_{Guid.NewGuid():N}";
private string GenerateJwtSecret() => Convert.ToBase64String(Guid.NewGuid().ToByteArray());
}

Join the conversation! Your thoughts help the community grow.