🔷 Configuration Management in C# TuskLang
Configuration Management in C# TuskLang
Overview
Configuration management is the backbone of any TuskLang application. This guide covers advanced configuration patterns, hierarchical structures, hot reloading, validation, and best practices for C# applications.
🏗️ Hierarchical Configuration
Multi-Level Configuration Structure
// Configuration hierarchy from most specific to least specific
public class ConfigurationHierarchy
{
public string Environment { get; set; } = "development";
public string ConfigPath { get; set; } = "config";
public async Task<TSKConfig> LoadHierarchicalConfig()
{
var config = new TSKConfig();
// Load in order of specificity
await config.LoadFileAsync("config/default.tsk");
await config.LoadFileAsync($"config/{Environment}.tsk");
await config.LoadFileAsync("config/local.tsk"); // Override for local development
return config;
}
}
Environment-Specific Overrides
// peanu.tsk - Base configuration
[database]
host = "localhost"
port = 5432
name = "myapp"[api]
base_url = "https://api.example.com"
timeout = 30
// peanu.production.tsk - Production overrides
[database]
host = @env("DB_HOST", "prod-db.example.com")
port = @env("DB_PORT", "5432")
[api]
base_url = @env("API_BASE_URL", "https://api.production.com")
timeout = 60
// peanu.staging.tsk - Staging overrides
[database]
host = @env("DB_HOST", "staging-db.example.com")
[api]
base_url = @env("API_BASE_URL", "https://api.staging.com")
Configuration Inheritance
public class InheritedConfiguration
{
public async Task<TSKConfig> LoadWithInheritance()
{
var config = new TSKConfig();
// Load base configuration
await config.LoadFileAsync("config/base.tsk");
// Load environment-specific overrides
var environment = Environment.GetEnvironmentVariable("APP_ENV") ?? "development";
await config.LoadFileAsync($"config/{environment}.tsk");
// Load feature-specific configurations
await config.LoadFileAsync("config/features/database.tsk");
await config.LoadFileAsync("config/features/api.tsk");
await config.LoadFileAsync("config/features/cache.tsk");
return config;
}
}
🔄 Hot Reloading
File System Watcher
public class HotReloadConfiguration
{
private readonly TSKConfig _config;
private readonly FileSystemWatcher _watcher;
private readonly ILogger<HotReloadConfiguration> _logger;
public HotReloadConfiguration(TSKConfig config, ILogger<HotReloadConfiguration> logger)
{
_config = config;
_logger = logger;
_watcher = new FileSystemWatcher("config")
{
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
Filter = "*.tsk",
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
_watcher.Changed += OnConfigFileChanged;
_watcher.Created += OnConfigFileChanged;
_watcher.Deleted += OnConfigFileChanged;
}
private async void OnConfigFileChanged(object sender, FileSystemEventArgs e)
{
try
{
_logger.LogInformation("Configuration file changed: {FilePath}", e.FullPath);
// Debounce rapid changes
await Task.Delay(100);
// Reload configuration
await _config.ReloadAsync();
_logger.LogInformation("Configuration reloaded successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to reload configuration");
}
}
public void Dispose()
{
_watcher?.Dispose();
}
}
Configuration Change Notifications
public class ConfigurationChangeNotifier
{
private readonly ILogger<ConfigurationChangeNotifier> _logger;
private readonly List<IConfigurationChangeHandler> _handlers = new();
public event EventHandler<ConfigurationChangedEventArgs> ConfigurationChanged;
public ConfigurationChangeNotifier(ILogger<ConfigurationChangeNotifier> logger)
{
_logger = logger;
}
public void RegisterHandler(IConfigurationChangeHandler handler)
{
_handlers.Add(handler);
}
public async Task NotifyConfigurationChanged(TSKConfig oldConfig, TSKConfig newConfig)
{
var args = new ConfigurationChangedEventArgs(oldConfig, newConfig);
// Notify event subscribers
ConfigurationChanged?.Invoke(this, args);
// Notify registered handlers
foreach (var handler in _handlers)
{
try
{
await handler.OnConfigurationChangedAsync(args);
}
catch (Exception ex)
{
_logger.LogError(ex, "Handler {HandlerType} failed to process configuration change",
handler.GetType().Name);
}
}
}
}public interface IConfigurationChangeHandler
{
Task OnConfigurationChangedAsync(ConfigurationChangedEventArgs args);
}
public class ConfigurationChangedEventArgs : EventArgs
{
public TSKConfig OldConfig { get; }
public TSKConfig NewConfig { get; }
public ConfigurationChangedEventArgs(TSKConfig oldConfig, TSKConfig newConfig)
{
OldConfig = oldConfig;
NewConfig = newConfig;
}
}
Database-Driven Configuration
public class DatabaseConfigurationProvider
{
private readonly IDbConnection _connection;
private readonly ILogger<DatabaseConfigurationProvider> _logger;
private readonly Timer _refreshTimer;
public DatabaseConfigurationProvider(IDbConnection connection, ILogger<DatabaseConfigurationProvider> logger)
{
_connection = connection;
_logger = logger;
// Refresh configuration every 5 minutes
_refreshTimer = new Timer(RefreshConfiguration, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
}
private async void RefreshConfiguration(object state)
{
try
{
var query = @"
SELECT key, value, environment
FROM configuration
WHERE environment = @environment OR environment = 'global'
ORDER BY environment DESC, updated_at DESC";
var parameters = new { environment = Environment.GetEnvironmentVariable("APP_ENV") };
var configData = await _connection.QueryAsync<ConfigEntry>(query, parameters);
// Update configuration
await UpdateConfigurationFromDatabase(configData);
_logger.LogInformation("Configuration refreshed from database");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh configuration from database");
}
}
private async Task UpdateConfigurationFromDatabase(IEnumerable<ConfigEntry> configData)
{
var config = new TSKConfig();
foreach (var entry in configData)
{
config.Set(entry.Key, entry.Value);
}
// Notify configuration change
// Implementation depends on your notification system
}
}public class ConfigEntry
{
public string Key { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public string Environment { get; set; } = string.Empty;
public DateTime UpdatedAt { get; set; }
}
✅ Configuration Validation
Schema Validation
public class ConfigurationValidator
{
private readonly ILogger<ConfigurationValidator> _logger;
public ConfigurationValidator(ILogger<ConfigurationValidator> logger)
{
_logger = logger;
}
public async Task<ValidationResult> ValidateConfigurationAsync(TSKConfig config)
{
var result = new ValidationResult();
// Validate required sections
await ValidateRequiredSectionsAsync(config, result);
// Validate database configuration
await ValidateDatabaseConfigAsync(config, result);
// Validate API configuration
await ValidateApiConfigAsync(config, result);
// Validate security configuration
await ValidateSecurityConfigAsync(config, result);
return result;
}
private async Task ValidateRequiredSectionsAsync(TSKConfig config, ValidationResult result)
{
var requiredSections = new[] { "database", "api", "security", "logging" };
foreach (var section in requiredSections)
{
if (!config.HasSection(section))
{
result.AddError($"Missing required section: {section}");
}
}
}
private async Task ValidateDatabaseConfigAsync(TSKConfig config, ValidationResult result)
{
var dbConfig = config.GetSection("database");
if (dbConfig == null) return;
// Validate connection string
var connectionString = dbConfig.Get<string>("connection_string");
if (string.IsNullOrEmpty(connectionString))
{
result.AddError("Database connection string is required");
}
else
{
// Test database connection
try
{
using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync();
await connection.CloseAsync();
}
catch (Exception ex)
{
result.AddError($"Database connection failed: {ex.Message}");
}
}
// Validate timeout values
var timeout = dbConfig.Get<int>("timeout", 30);
if (timeout <= 0 || timeout > 300)
{
result.AddError("Database timeout must be between 1 and 300 seconds");
}
}
private async Task ValidateApiConfigAsync(TSKConfig config, ValidationResult result)
{
var apiConfig = config.GetSection("api");
if (apiConfig == null) return;
// Validate base URL
var baseUrl = apiConfig.Get<string>("base_url");
if (string.IsNullOrEmpty(baseUrl))
{
result.AddError("API base URL is required");
}
else if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out _))
{
result.AddError("API base URL must be a valid absolute URL");
}
// Validate timeout
var timeout = apiConfig.Get<int>("timeout", 30);
if (timeout <= 0 || timeout > 300)
{
result.AddError("API timeout must be between 1 and 300 seconds");
}
}
private async Task ValidateSecurityConfigAsync(TSKConfig config, ValidationResult result)
{
var securityConfig = config.GetSection("security");
if (securityConfig == null) return;
// Validate JWT secret
var jwtSecret = securityConfig.Get<string>("jwt_secret");
if (string.IsNullOrEmpty(jwtSecret))
{
result.AddError("JWT secret is required");
}
else if (jwtSecret.Length < 32)
{
result.AddError("JWT secret must be at least 32 characters long");
}
// Validate encryption key
var encryptionKey = securityConfig.Get<string>("encryption_key");
if (string.IsNullOrEmpty(encryptionKey))
{
result.AddError("Encryption key is required");
}
else if (encryptionKey.Length < 32)
{
result.AddError("Encryption key must be at least 32 characters long");
}
}
}public class ValidationResult
{
public List<string> Errors { get; } = new();
public List<string> Warnings { get; } = new();
public bool IsValid => Errors.Count == 0;
public void AddError(string error)
{
Errors.Add(error);
}
public void AddWarning(string warning)
{
Warnings.Add(warning);
}
}
Custom Validation Attributes
[AttributeUsage(AttributeTargets.Property)]
public class ConfigurationValidationAttribute : Attribute
{
public string ValidationRule { get; }
public string ErrorMessage { get; }
public ConfigurationValidationAttribute(string validationRule, string errorMessage)
{
ValidationRule = validationRule;
ErrorMessage = errorMessage;
}
}public class DatabaseConfiguration
{
[ConfigurationValidation("required", "Connection string is required")]
public string ConnectionString { get; set; } = string.Empty;
[ConfigurationValidation("range:1-300", "Timeout must be between 1 and 300 seconds")]
public int Timeout { get; set; } = 30;
[ConfigurationValidation("range:1-100", "Max connections must be between 1 and 100")]
public int MaxConnections { get; set; } = 20;
}
public class ApiConfiguration
{
[ConfigurationValidation("url", "Base URL must be a valid URL")]
public string BaseUrl { get; set; } = string.Empty;
[ConfigurationValidation("range:1-300", "Timeout must be between 1 and 300 seconds")]
public int Timeout { get; set; } = 30;
[ConfigurationValidation("range:1-10", "Retry count must be between 1 and 10")]
public int RetryCount { get; set; } = 3;
}
🔐 Secure Configuration
Environment Variable Integration
public class SecureConfigurationProvider
{
private readonly ILogger<SecureConfigurationProvider> _logger;
public SecureConfigurationProvider(ILogger<SecureConfigurationProvider> logger)
{
_logger = logger;
}
public async Task<TSKConfig> LoadSecureConfigurationAsync()
{
var config = new TSKConfig();
// Load base configuration
await config.LoadFileAsync("config/secure.tsk");
// Replace sensitive values with environment variables
await ReplaceSensitiveValuesAsync(config);
return config;
}
private async Task ReplaceSensitiveValuesAsync(TSKConfig config)
{
// Database credentials
var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD");
if (!string.IsNullOrEmpty(dbPassword))
{
config.Set("database.password", dbPassword);
}
// API keys
var apiKey = Environment.GetEnvironmentVariable("API_KEY");
if (!string.IsNullOrEmpty(apiKey))
{
config.Set("api.key", apiKey);
}
// JWT secret
var jwtSecret = Environment.GetEnvironmentVariable("JWT_SECRET");
if (!string.IsNullOrEmpty(jwtSecret))
{
config.Set("security.jwt_secret", jwtSecret);
}
// Encryption key
var encryptionKey = Environment.GetEnvironmentVariable("ENCRYPTION_KEY");
if (!string.IsNullOrEmpty(encryptionKey))
{
config.Set("security.encryption_key", encryptionKey);
}
}
}
Encrypted Configuration Values
public class EncryptedConfigurationProvider
{
private readonly string _encryptionKey;
private readonly ILogger<EncryptedConfigurationProvider> _logger;
public EncryptedConfigurationProvider(string encryptionKey, ILogger<EncryptedConfigurationProvider> logger)
{
_encryptionKey = encryptionKey;
_logger = logger;
}
public async Task<TSKConfig> LoadEncryptedConfigurationAsync()
{
var config = new TSKConfig();
// Load encrypted configuration
await config.LoadFileAsync("config/encrypted.tsk");
// Decrypt sensitive values
await DecryptSensitiveValuesAsync(config);
return config;
}
private async Task DecryptSensitiveValuesAsync(TSKConfig config)
{
var sensitiveKeys = new[] { "database.password", "api.key", "security.jwt_secret" };
foreach (var key in sensitiveKeys)
{
var encryptedValue = config.Get<string>(key);
if (!string.IsNullOrEmpty(encryptedValue) && encryptedValue.StartsWith("ENC:"))
{
try
{
var decryptedValue = await DecryptValueAsync(encryptedValue.Substring(4));
config.Set(key, decryptedValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to decrypt configuration value for key: {Key}", key);
}
}
}
}
private async Task<string> DecryptValueAsync(string encryptedValue)
{
// Implementation depends on your encryption library
// Example using AES encryption
using var aes = Aes.Create();
aes.Key = Convert.FromBase64String(_encryptionKey);
var encryptedBytes = Convert.FromBase64String(encryptedValue);
var iv = new byte[16];
var ciphertext = new byte[encryptedBytes.Length - 16];
Array.Copy(encryptedBytes, 0, iv, 0, 16);
Array.Copy(encryptedBytes, 16, ciphertext, 0, ciphertext.Length);
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
using var ms = new MemoryStream(ciphertext);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var reader = new StreamReader(cs);
return await reader.ReadToEndAsync();
}
}
📊 Configuration Analytics
Usage Tracking
public class ConfigurationAnalytics
{
private readonly ILogger<ConfigurationAnalytics> _logger;
private readonly Dictionary<string, int> _accessCounts = new();
private readonly Dictionary<string, DateTime> _lastAccess = new();
public ConfigurationAnalytics(ILogger<ConfigurationAnalytics> logger)
{
_logger = logger;
}
public void TrackAccess(string key)
{
lock (_accessCounts)
{
_accessCounts[key] = _accessCounts.GetValueOrDefault(key, 0) + 1;
_lastAccess[key] = DateTime.UtcNow;
}
}
public async Task<ConfigurationUsageReport> GenerateReportAsync()
{
lock (_accessCounts)
{
var report = new ConfigurationUsageReport
{
TotalAccesses = _accessCounts.Values.Sum(),
MostAccessedKeys = _accessCounts
.OrderByDescending(x => x.Value)
.Take(10)
.ToDictionary(x => x.Key, x => x.Value),
LastAccessTimes = new Dictionary<string, DateTime>(_lastAccess)
};
return report;
}
}
public async Task LogUnusedConfigurationAsync(TSKConfig config)
{
var allKeys = config.GetAllKeys();
var unusedKeys = allKeys.Where(key => !_accessCounts.ContainsKey(key)).ToList();
if (unusedKeys.Any())
{
_logger.LogWarning("Unused configuration keys detected: {UnusedKeys}",
string.Join(", ", unusedKeys));
}
}
}public class ConfigurationUsageReport
{
public int TotalAccesses { get; set; }
public Dictionary<string, int> MostAccessedKeys { get; set; } = new();
public Dictionary<string, DateTime> LastAccessTimes { get; set; } = new();
}
🚀 Best Practices
Configuration Organization
// Recommended configuration structure
config/
├── base.tsk # Base configuration
├── development.tsk # Development overrides
├── staging.tsk # Staging overrides
├── production.tsk # Production overrides
├── local.tsk # Local development overrides (git ignored)
├── features/
│ ├── database.tsk # Database-specific configuration
│ ├── api.tsk # API-specific configuration
│ ├── cache.tsk # Cache-specific configuration
│ └── security.tsk # Security-specific configuration
└── secrets/
└── encrypted.tsk # Encrypted sensitive values
Performance Optimization
public class OptimizedConfigurationProvider
{
private readonly MemoryCache _cache = new(new MemoryCacheOptions());
private readonly ILogger<OptimizedConfigurationProvider> _logger;
public OptimizedConfigurationProvider(ILogger<OptimizedConfigurationProvider> logger)
{
_logger = logger;
}
public async Task<TSKConfig> LoadOptimizedConfigurationAsync()
{
var cacheKey = "configuration_cache";
if (_cache.TryGetValue(cacheKey, out TSKConfig cachedConfig))
{
return cachedConfig;
}
var config = await LoadConfigurationAsync();
// Cache for 5 minutes
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.SetSlidingExpiration(TimeSpan.FromMinutes(2));
_cache.Set(cacheKey, config, cacheOptions);
return config;
}
private async Task<TSKConfig> LoadConfigurationAsync()
{
var config = new TSKConfig();
// Load configuration files in parallel
var tasks = new[]
{
config.LoadFileAsync("config/base.tsk"),
config.LoadFileAsync("config/features/database.tsk"),
config.LoadFileAsync("config/features/api.tsk"),
config.LoadFileAsync("config/features/cache.tsk")
};
await Task.WhenAll(tasks);
return config;
}
}
Error Handling
public class ResilientConfigurationProvider
{
private readonly ILogger<ResilientConfigurationProvider> _logger;
private TSKConfig _fallbackConfig;
public ResilientConfigurationProvider(ILogger<ResilientConfigurationProvider> logger)
{
_logger = logger;
_fallbackConfig = CreateFallbackConfiguration();
}
public async Task<TSKConfig> LoadResilientConfigurationAsync()
{
try
{
var config = new TSKConfig();
// Try to load configuration files
await LoadConfigurationFilesAsync(config);
// Validate configuration
var validator = new ConfigurationValidator(_logger);
var validationResult = await validator.ValidateConfigurationAsync(config);
if (!validationResult.IsValid)
{
_logger.LogWarning("Configuration validation failed, using fallback: {Errors}",
string.Join(", ", validationResult.Errors));
return _fallbackConfig;
}
return config;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load configuration, using fallback");
return _fallbackConfig;
}
}
private async Task LoadConfigurationFilesAsync(TSKConfig config)
{
var configFiles = new[]
{
"config/base.tsk",
"config/features/database.tsk",
"config/features/api.tsk"
};
foreach (var file in configFiles)
{
try
{
await config.LoadFileAsync(file);
}
catch (FileNotFoundException)
{
_logger.LogWarning("Configuration file not found: {File}", file);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load configuration file: {File}", file);
}
}
}
private TSKConfig CreateFallbackConfiguration()
{
var config = new TSKConfig();
// Set safe default values
config.Set("database.connection_string", "Server=localhost;Database=myapp;");
config.Set("database.timeout", 30);
config.Set("api.base_url", "https://api.example.com");
config.Set("api.timeout", 30);
config.Set("logging.level", "Warning");
return config;
}
}
📝 Summary
This guide covered advanced configuration management patterns for C# TuskLang applications:
- Hierarchical Configuration: Multi-level configuration with environment-specific overrides - Hot Reloading: Real-time configuration updates with file system watchers - Configuration Validation: Comprehensive validation with custom rules and schemas - Secure Configuration: Environment variable integration and encrypted values - Configuration Analytics: Usage tracking and performance monitoring - Best Practices: Organization, optimization, and error handling strategies
These patterns ensure your C# TuskLang applications have robust, secure, and maintainable configuration management systems.