Azure App Configuration in ConnectSoft Microservice Template¶
Purpose & Overview¶
Azure App Configuration is a managed service that provides centralized configuration management for microservices. In the ConnectSoft Microservice Template, Azure App Configuration serves as an optional configuration provider that enables dynamic configuration updates, feature flag management, and centralized configuration storage without requiring application redeployment.
Why Azure App Configuration?¶
Azure App Configuration offers several key benefits:
- Centralized Management: Single source of truth for configuration across multiple services
- Dynamic Updates: Change configuration without redeploying applications
- Environment Support: Use labels to manage environment-specific configurations
- Feature Flags: Built-in feature flag management and rollout
- Security: Integration with Azure Key Vault for secrets management
- Governance: Role-based access control, audit logs, and versioning
- Performance: Efficient polling and caching with minimal overhead
- Scalability: Supports thousands of configuration keys and values
Azure App Configuration Philosophy
Azure App Configuration provides a centralized, secure, and scalable way to manage application configuration and feature flags. It enables teams to update configuration in real-time without code changes, supports multi-environment deployments, and integrates seamlessly with Azure Key Vault for secure secret management.
Architecture Overview¶
Azure App Configuration Position in Configuration Hierarchy¶
Command-Line Arguments (Highest Priority)
↓
Environment Variables
↓
Azure App Configuration (if enabled)
├── Configuration Keys
├── Feature Flags
└── Key Vault References
↓
appsettings.{Environment}.json
↓
appsettings.json (Base)
Configuration Flow¶
Azure App Configuration Store
├── Configuration Keys/Values
├── Feature Flags
└── Key Vault References
↓
AddAzureAppConfiguration()
├── Key Selection (.Select)
├── Refresh Configuration (.ConfigureRefresh)
├── Feature Flags (.UseFeatureFlags)
└── Client Options (.ConfigureClientOptions)
↓
IConfiguration
↓
Options Pattern
├── IOptions<T> (Immutable)
├── IOptionsSnapshot<T> (Scoped, Reloadable)
└── IOptionsMonitor<T> (Singleton, Reloadable)
↓
UseAzureAppConfiguration() Middleware
└── Dynamic Refresh Polling
Service Registration¶
Configuration Setup¶
Azure App Configuration is registered in Program.cs:
// Program.cs
private static void DefineConfiguration(
string[] args,
HostBuilderContext hostBuilderContext,
IConfigurationBuilder configurationBuilder)
{
configurationBuilder.Sources.Clear();
configurationBuilder
.SetBasePath(hostBuilderContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile(
$"appsettings.{hostBuilderContext.HostingEnvironment.EnvironmentName}.json",
optional: true,
reloadOnChange: true);
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
// Build configuration to get connection string
IConfigurationRoot configurationRoot = configurationBuilder.Build();
// Use dynamic configuration in an ASP.NET Core app
// https://learn.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-aspnet-core
configurationBuilder.AddAzureAppConfiguration(options =>
{
string? azureAppConfigurationConnectionString =
configurationRoot.GetConnectionString("AzureAppConfiguration");
options.Connect(azureAppConfigurationConnectionString)
// Load all keys that start with ConnectSoft.MicroserviceTemplate: and have no label
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
// Configure to reload configuration if the registered sentinel key is modified
.ConfigureRefresh(refresh =>
{
refresh.Register(
"ConnectSoft.MicroserviceTemplate:Settings:Sentinel",
refreshAll: true);
refresh.SetRefreshInterval(TimeSpan.FromMinutes(30));
})
// Configure client retry options
.ConfigureClientOptions(options =>
{
options.Retry.MaxRetries = 1;
});
#if UseAzureAppConfigurationAsFeatureFlagsProvider
// Configure feature flags
options.UseFeatureFlags(featureFlagsOptions =>
{
featureFlagsOptions.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null);
featureFlagsOptions.SetRefreshInterval(TimeSpan.FromMinutes(30));
});
#endif
});
#endif
configurationBuilder
.AddEnvironmentVariables()
.AddCommandLine(args);
}
Service Registration Extension¶
// AzureAppConfigurationExtensions.cs
internal static IServiceCollection AddMicroserviceAzureAppConfiguration(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddAzureAppConfiguration();
return services;
}
Registration:
// MicroserviceRegistrationExtensions.cs
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
services.AddMicroserviceAzureAppConfiguration();
#endif
Middleware Registration¶
// AzureAppConfigurationExtensions.cs
internal static IApplicationBuilder UseMicroserviceAzureAppConfiguration(
this IApplicationBuilder app)
{
ArgumentNullException.ThrowIfNull(app);
app.UseAzureAppConfiguration();
return app;
}
Middleware Pipeline:
// MicroserviceRegistrationExtensions.cs
#if UseAzureAppConfigurationAsAdditionalConfigurationProvider
application.UseMicroserviceAzureAppConfiguration();
#endif
Configuration¶
Connection String¶
appsettings.json:
{
"ConnectionStrings": {
"AzureAppConfiguration": "Endpoint=https://{store}.azconfig.io;Id={id};Secret={secret}"
}
}
Connection String Format:
Connection String Components:
- Endpoint: The Azure App Configuration store endpoint URL
- Id: Client ID (for connection string authentication)
- Secret: Client secret (for connection string authentication)
Managed Identity Authentication¶
For production, use managed identity instead of connection strings:
Benefits: - No secrets in configuration - Automatic credential rotation - Better security posture - Supports Azure AD authentication
Key Vault References¶
Use Key Vault references for connection strings:
{
"ConnectionStrings": {
"AzureAppConfiguration": "@Microsoft.KeyVault(SecretUri=https://{vault}.vault.azure.net/secrets/{secret-name})"
}
}
Benefits: - Centralized secret management - Automatic secret rotation - Audit logging - Access control
Key Selection¶
Key Pattern Matching¶
Select Keys by Pattern:
Pattern Components:
- ConnectSoft.MicroserviceTemplate:*: Matches all keys starting with this prefix
- LabelFilter.Null: Only keys without labels (Production)
Examples:
// Select all keys with prefix
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
// Select specific keys
.Select("ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName", LabelFilter.Null)
// Select multiple patterns
.Select("ConnectSoft.MicroserviceTemplate:Microservice:*", LabelFilter.Null)
.Select("ConnectSoft.MicroserviceTemplate:Validation:*", LabelFilter.Null)
Environment Labels¶
Multi-Environment Support:
options.Connect(connectionString)
// Production keys (no label)
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
// Development overrides
.Select("ConnectSoft.MicroserviceTemplate:*", "Development")
// Staging overrides
.Select("ConnectSoft.MicroserviceTemplate:*", "Staging");
Label Priority: 1. Environment-specific label (Development, Staging, etc.) 2. Null label (Production) 3. Base configuration (appsettings.json)
Example:
Production (Label: null):
ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName = "ProductionService"
Development (Label: Development):
ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName = "DevelopmentService"
Result in Development environment: "DevelopmentService" (overrides Production)
Dynamic Configuration Refresh¶
Sentinel Key Strategy¶
Purpose: Use a single sentinel key to trigger full configuration refresh.
Configuration:
.ConfigureRefresh(refresh =>
{
refresh.Register(
"ConnectSoft.MicroserviceTemplate:Settings:Sentinel",
refreshAll: true);
refresh.SetRefreshInterval(TimeSpan.FromMinutes(30));
})
How It Works:
1. Middleware polls the sentinel key at configured interval
2. If sentinel key value changes, all configuration is refreshed
3. IOptionsMonitor<T> instances are updated
4. Change notifications are triggered
Benefits: - Efficient: Only one key needs to be checked - Atomic: All configuration refreshes together - Predictable: Clear trigger for refresh - Performance: Minimal overhead
Refresh Interval¶
Configure Polling Interval:
Considerations: - Shorter intervals: More responsive but higher API calls - Longer intervals: Fewer API calls but slower updates - Default: 30 minutes is a good balance
Recommended Intervals: - Development: 1-5 minutes (faster feedback) - Staging: 5-15 minutes (balance) - Production: 15-30 minutes (stability)
Manual Refresh Keys¶
Register Specific Keys for Refresh:
.ConfigureRefresh(refresh =>
{
// Sentinel key (triggers full refresh)
refresh.Register("ConnectSoft.MicroserviceTemplate:Settings:Sentinel", refreshAll: true);
// Specific keys (refresh only these)
refresh.Register("ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName");
refresh.Register("ConnectSoft.MicroserviceTemplate:Validation:EnableClassLevelFailFast");
refresh.SetRefreshInterval(TimeSpan.FromMinutes(30));
})
When to Use: - Specific keys that change frequently - Keys that need immediate updates - Fine-grained refresh control
Dynamic Configuration Usage¶
IOptionsMonitor¶
Use IOptionsMonitor<T> for Dynamic Configuration:
public class ConfigurableService
{
private readonly IOptionsMonitor<MicroserviceOptions> optionsMonitor;
private readonly ILogger<ConfigurableService> logger;
public ConfigurableService(
IOptionsMonitor<MicroserviceOptions> optionsMonitor,
ILogger<ConfigurableService> logger)
{
this.optionsMonitor = optionsMonitor;
this.logger = logger;
// Subscribe to configuration changes
this.optionsMonitor.OnChange(options =>
{
this.logger.LogInformation(
"Configuration updated: {MicroserviceName}",
options.MicroserviceName);
});
}
public void Process()
{
// Always gets latest value (even after refresh)
var currentConfig = this.optionsMonitor.CurrentValue;
// Use configuration
var serviceName = currentConfig.MicroserviceName;
}
}
Benefits: - Always Current: Gets latest value even after refresh - Change Notifications: Subscribe to configuration changes - Singleton: One instance shared across application - Thread-Safe: Safe to use in concurrent scenarios
IOptionsSnapshot¶
Use IOptionsSnapshot<T> for Scoped Configuration:
public class ScopedService
{
private readonly IOptionsSnapshot<MicroserviceOptions> optionsSnapshot;
public ScopedService(IOptionsSnapshot<MicroserviceOptions> optionsSnapshot)
{
this.optionsSnapshot = optionsSnapshot;
}
public void Process()
{
// Gets configuration at request start (scoped)
var config = this.optionsSnapshot.Value;
}
}
Characteristics: - Scoped Lifetime: One instance per request - Snapshot: Captures configuration at request start - Reloadable: Gets updated values on refresh - Per-Request: Each request gets its own snapshot
IOptions¶
Use IOptions<T> for Immutable Configuration:
public class ImmutableService
{
private readonly MicroserviceOptions options;
public ImmutableService(IOptions<MicroserviceOptions> options)
{
this.options = options.Value; // Loaded once at startup
}
public void Process()
{
// Uses configuration loaded at startup (immutable)
var serviceName = this.options.MicroserviceName;
}
}
Characteristics: - Immutable: Loaded once at startup - Singleton: Same instance throughout application lifetime - Not Reloadable: Does not update on refresh - Performance: No overhead for configuration access
Feature Flags Integration¶
Enable Feature Flags¶
Configuration:
#if UseAzureAppConfigurationAsFeatureFlagsProvider
options.UseFeatureFlags(featureFlagsOptions =>
{
featureFlagsOptions.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null);
featureFlagsOptions.SetRefreshInterval(TimeSpan.FromMinutes(30));
});
#endif
Features: - Dynamic Updates: Change flags without redeployment - Environment Labels: Different flags per environment - Centralized Management: Manage flags in Azure portal - Automatic Refresh: Flags refresh with configuration
See Feature Flags for detailed feature flag documentation.
Client Options¶
Retry Configuration¶
Configure Retry Policy:
Retry Options:
- MaxRetries: Maximum number of retry attempts
- Mode: Retry mode (Exponential, Fixed, NoRetry)
- Delay: Delay between retries
- MaxDelay: Maximum delay between retries
Recommended Settings:
- Development: MaxRetries = 1 (fail fast for debugging)
- Production: MaxRetries = 3 (resilience for transient failures)
Timeout Configuration¶
Configure Timeout:
.ConfigureClientOptions(options =>
{
options.Retry.MaxRetries = 1;
options.Timeout = TimeSpan.FromSeconds(30);
})
Key Naming Conventions¶
Recommended Patterns¶
Use Hierarchical Keys:
ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName
ConnectSoft.MicroserviceTemplate:Validation:EnableClassLevelFailFast
ConnectSoft.MicroserviceTemplate:Orleans:Connection:ConnectionString
Pattern Structure:
- {Organization}:{Service}:{Category}:{Property}
- Use colons (:) as separators
- Use PascalCase for segments
Benefits: - Organization: Clear hierarchy and grouping - Filtering: Easy to select related keys - Clarity: Self-documenting key structure - Consistency: Standardized naming across services
Key Examples¶
Microservice Configuration:
ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName
ConnectSoft.MicroserviceTemplate:Microservice:StartupWarmupSeconds
Validation Configuration:
ConnectSoft.MicroserviceTemplate:Validation:EnableClassLevelFailFast
ConnectSoft.MicroserviceTemplate:Validation:EnableRuleLevelFailFast
Orleans Configuration:
ConnectSoft.MicroserviceTemplate:Orleans:OrleansEndpoints:SiloPort
ConnectSoft.MicroserviceTemplate:Orleans:OrleansEndpoints:GatewayPort
Azure Portal Configuration¶
Creating Configuration Keys¶
Steps:
1. Navigate to Azure App Configuration store
2. Go to "Configuration explorer"
3. Click "Create" → "Key-value"
4. Enter key name (e.g., ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName)
5. Enter value
6. Optionally add label (e.g., "Development", "Staging")
7. Click "Apply"
Creating Sentinel Key¶
Sentinel Key:
- Key: ConnectSoft.MicroserviceTemplate:Settings:Sentinel
- Value: Timestamp (e.g., 2025-01-15T10:30:00Z)
- Purpose: Trigger configuration refresh
Update Sentinel Key: 1. Navigate to sentinel key in Azure portal 2. Update value to current timestamp 3. Save changes 4. Configuration refreshes within refresh interval
Feature Flags in Portal¶
Create Feature Flag: 1. Navigate to "Feature manager" 2. Click "Create" 3. Enter feature flag name 4. Configure filters (percentage, time window, targeting) 5. Set enabled/disabled state 6. Optionally add label for environment-specific flags
Best Practices¶
Do's¶
-
Use Hierarchical Key Names
-
Use Sentinel Key for Refresh
-
Use IOptionsMonitor for Dynamic Config
-
Use Environment Labels
-
Use Managed Identity in Production
Don'ts¶
-
Don't Store Secrets Directly
-
Don't Poll Too Frequently
-
Don't Use IOptions for Dynamic Config
-
Don't Ignore Refresh Failures
Monitoring and Observability¶
Configuration Refresh Events¶
Log Configuration Changes:
this.optionsMonitor.OnChange(options =>
{
this.logger.LogInformation(
"Configuration refreshed at {Time}. MicroserviceName: {Name}",
DateTime.UtcNow,
options.MicroserviceName);
});
Metrics¶
Track Configuration Refresh:
private readonly Counter<int> configurationRefreshCounter;
public ConfigurableService(IOptionsMonitor<MyOptions> optionsMonitor)
{
this.optionsMonitor.OnChange(options =>
{
this.configurationRefreshCounter.Add(1,
new KeyValuePair<string, object?>("EventType", "ConfigurationRefresh"));
});
}
Health Checks¶
Monitor Azure App Configuration Health:
services.AddHealthChecks()
.AddAzureAppConfiguration(
options =>
{
options.ConnectionString = configuration.GetConnectionString("AzureAppConfiguration");
},
name: "azure-app-configuration",
failureStatus: HealthStatus.Degraded);
Troubleshooting¶
Issue: Configuration Not Refreshing¶
Symptom: Changes in Azure App Configuration don't reflect in application.
Solutions:
1. Verify UseMicroserviceAzureAppConfiguration() middleware is registered
2. Check sentinel key is being updated
3. Verify refresh interval is appropriate
4. Check connection string is valid
5. Review application logs for refresh errors
6. Verify keys match selection pattern
7. Check label filters match key labels
Issue: Connection String Authentication Fails¶
Symptom: UnauthorizedAccessException or authentication errors.
Solutions: 1. Verify connection string format is correct 2. Check connection string is not expired 3. Verify access keys in Azure portal 4. Consider using managed identity instead 5. Check network connectivity to Azure App Configuration
Issue: Keys Not Loading¶
Symptom: Configuration keys from Azure App Configuration not available.
Solutions: 1. Verify key selection pattern matches keys 2. Check label filters (Null vs environment labels) 3. Verify connection string is correct 4. Check keys exist in Azure portal 5. Verify key names match exactly (case-sensitive)
Issue: Performance Issues¶
Symptom: Slow application startup or high latency.
Solutions: 1. Reduce number of keys selected 2. Increase refresh interval 3. Use specific key patterns instead of wildcards 4. Check network latency to Azure App Configuration 5. Consider caching configuration locally
Integration with Other Patterns¶
Azure App Configuration + Options Pattern¶
Seamless Integration:
// Configuration from Azure App Configuration
{
"ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName": "MyService"
}
// Options class
public sealed class MicroserviceOptions
{
public const string SectionName = "Microservice";
[Required]
required public string MicroserviceName { get; set; }
}
// Usage
public MyService(IOptionsMonitor<MicroserviceOptions> optionsMonitor)
{
// Automatically bound from Azure App Configuration
var name = optionsMonitor.CurrentValue.MicroserviceName;
}
Azure App Configuration + Feature Flags¶
Unified Management:
options.Connect(connectionString)
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
.ConfigureRefresh(...)
.UseFeatureFlags(featureFlagsOptions =>
{
featureFlagsOptions.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null);
});
Benefits: - Single Source: Configuration and feature flags in one place - Consistent Labels: Same environment labels for both - Unified Refresh: Both refresh together - Centralized Management: Manage in Azure portal
Azure App Configuration + Key Vault¶
Secure Secret Management:
{
"ConnectionStrings": {
"AzureAppConfiguration": "@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/AppConfigConnectionString)"
}
}
Key Vault References in Configuration:
ConnectSoft.MicroserviceTemplate:Database:ConnectionString
Value: @Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)
Benefits: - Centralized Secrets: All secrets in Key Vault - Automatic Rotation: Key Vault handles secret rotation - Access Control: Role-based access to secrets - Audit Logging: Track secret access
Security Considerations¶
Connection String Security¶
Development: - Use connection strings in User Secrets or environment variables - Never commit connection strings to source control
Production: - Use managed identity authentication - Use Key Vault references for connection strings - Rotate access keys regularly - Use read-only access keys when possible
Access Control¶
Role-Based Access Control (RBAC): - App Configuration Data Reader: Read-only access - App Configuration Data Owner: Full access - App Configuration Data Contributor: Read and write access
Recommended:
- Application: App Configuration Data Reader
- Administrators: App Configuration Data Owner
- CI/CD: App Configuration Data Contributor
Key Vault Integration¶
Reference Secrets:
Key: ConnectSoft.MicroserviceTemplate:Database:ConnectionString
Value: @Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/DbConnectionString)
Benefits: - Secrets never stored in App Configuration - Automatic secret rotation - Centralized secret management - Audit logging
Cost Optimization¶
Key Selection¶
Optimize Key Selection:
// ✅ GOOD - Specific keys
.Select("ConnectSoft.MicroserviceTemplate:Microservice:MicroserviceName", LabelFilter.Null)
.Select("ConnectSoft.MicroserviceTemplate:Validation:EnableClassLevelFailFast", LabelFilter.Null)
// ❌ BAD - Too broad, loads unnecessary keys
.Select("ConnectSoft.MicroserviceTemplate:*", LabelFilter.Null)
Refresh Interval¶
Balance Responsiveness and Cost:
// ✅ GOOD - Balanced interval
refresh.SetRefreshInterval(TimeSpan.FromMinutes(30));
// ❌ BAD - Too frequent, high API costs
refresh.SetRefreshInterval(TimeSpan.FromSeconds(10));
Caching¶
Leverage Built-in Caching: - Azure App Configuration client caches configuration - Refresh only checks for changes - Minimal API calls when configuration is stable
Summary¶
Azure App Configuration in the ConnectSoft Microservice Template provides:
- ✅ Centralized Management: Single source of truth for configuration
- ✅ Dynamic Updates: Change configuration without redeployment
- ✅ Environment Support: Labels for multi-environment configurations
- ✅ Feature Flags: Built-in feature flag management
- ✅ Security: Key Vault integration and managed identity support
- ✅ Performance: Efficient polling and caching
- ✅ Scalability: Supports thousands of configuration keys
- ✅ Governance: RBAC, audit logs, and versioning
By following these patterns, teams can:
- Centralize Configuration: Manage configuration across multiple services
- Update Dynamically: Change configuration without redeployment
- Support Multi-Environment: Use labels for environment-specific configs
- Secure Secrets: Integrate with Key Vault for secret management
- Monitor Changes: Track configuration updates and refresh events
- Optimize Costs: Balance refresh frequency and API usage
Azure App Configuration enables teams to manage configuration at scale, update settings dynamically, and maintain consistency across microservices while providing security, observability, and performance optimizations.