Azure App Configuration. On-Demand Refresh
The Problem
Azure App Configuration’s default middleware triggers refresh checks on every HTTP request. With a 30-second cache:
- Short cache = High costs: One request every 30 seconds = 200K+ calls/day across 10 services (beyond free tier)
- Long cache = Slow updates: Configuration changes take longer to propagate
Goal
- Minimal API calls to App Configuration
- Fast configuration updates when needed
- Manual control over refresh timing
Implementation
OnDemandAzureAppConfigurationRefresher
public class OnDemandAzureAppConfigurationRefresher : IAzureAppConfigurationRefresher
{
private readonly List<IConfigurationRefresher> _configurationRefreshers = new List<IConfigurationRefresher>();
public OnDemandAzureAppConfigurationRefresher(IConfiguration configuration)
{
var configurationRoot = configuration as IConfigurationRoot;
if (configurationRoot == null)
{
throw new InvalidOperationException("The 'IConfiguration' injected in OnDemantConfigurationRefresher is not an 'IConfigurationRoot', and needs to be as well.");
}
foreach (var provider in configurationRoot.Providers)
{
if (provider is IConfigurationRefresher refresher)
{
_configurationRefreshers.Add(refresher);
}
}
}
public async Task RefreshAllRegisteredKeysAsync()
{
Task compositeTask = null;
var refreshersTasks = new List<Task>();
try
{
_configurationRefreshers.ForEach(r => refreshersTasks.Add(r.RefreshAsync()));
compositeTask = Task.WhenAll(refreshersTasks);
await compositeTask;
}
catch (Exception)
{
throw compositeTask.Exception;
}
}
}
Install Package
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore
Configure App Configuration
// Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config
.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AzAppConfigurationConnectionString"])
.ConfigureRefresh(refreshOptions =>
{
refreshOptions.Register("Bustroker.AppConfiguration.WebApi:sentinel", refreshAll: true)
.SetCacheExpiration(TimeSpan.FromSeconds(30));
});
});
})
.UseStartup<Startup>();
});
Note: Do NOT register app.UseAzureAppConfiguration() middleware in Startup.Configure.
Register Service
// Startup.ConfigureServices
services.AddScoped<IAzureAppConfigurationRefresher, OnDemandAzureAppConfigurationRefresher>();
Expose Refresh Endpoint
[ApiController]
[Route("[controller]")]
public class RefreshAzAppConfigurationController : ControllerBase
{
private readonly IAzureAppConfigurationRefresher _azureAppConfigurationRefresher;
public RefreshAzAppConfigurationController(IAzureAppConfigurationRefresher onDemandConfigurationRefresher)
{
_azureAppConfigurationRefresher = onDemandConfigurationRefresher;
}
[HttpPost]
public async Task<IActionResult> Post()
{
await _azureAppConfigurationRefresher.RefreshAllRegisteredKeysAsync();
return Ok();
}
}