Azure App Configuration refresh on demand
Here’s what Azure App Configuration Service is.
Here’s how to use it.
How the web api middleware uses the App Configuration Client
As per oficial documentation, refering to
// Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAzureAppConfiguration();
“The middleware uses the refresh configuration specified in the AddAzureAppConfiguration method in Program.cs to trigger a refresh for each request received by the ASP.NET Core web app. For each request, a refresh operation is triggered and the client library checks if the cached value for the registered configuration setting has expired. If it’s expired, it’s refreshed.”
It’s worth to note that the refresh doesn’t happen synchronously, but as fire & forget (you can check so in the code of the middleware), so it actually does not impact the incomming request’s response time. Rather the next request is the one that will see the change in the value.
On demand configuration refresh
But it doesn’t make sense to have one request every 30 seconds to ask for something that changes only once in a long while (not to talk about infinite cahe and having to restart the application when changing it).
Plus it does impact in cost, because considering a cache of 30 sec (the default) and an with one request every 30 seconds, you get one request to the ‘sentinel’ key every 30 seconds to the App Config Service, PER SERVICE with this configurations. So, 10 services make over 200k calls a day, and that is actually the limit above which the service starts charging extra. Not good.
So, this is not the behaviour I want, because:
- either it makes A LOT of extra calls, if cache expiration is short (and it costs money)
- or there is a LONG time to apply config changes, if fache expiration is long
What I want is:
- Minimum calls to Config API, because I’m short in cash :P
- Short cache expiration because I need the changes are applied ASAP
So, I implement an OnDemandAzureAppConfigurationRefresher and then expose an endpoint to trigger the refresh on demand.
Implementing OnDemandAzureAppConfigurationRefresher
I don’t want the refresh to be triggered by the middleware, instead I want to call it explicitly, so:
- I won’t register the AppConfiguration middleware in Startup.Configure
- I’ll implement the refresh as it is done in AzureAppConfigurationRefreshMiddleware class, in Microsoft’s repo, like this
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;
}
}
}
Add App Configuration configuration
First add the nuget package from the console
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore
Add the App Configuration as the configuration to use, with a refreshing config (this is a basic config, to focus on the refresh part).
// 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>();
});
Register the service
// Startup.ConfigureServices
services.AddScoped<IAzureAppConfigurationRefresher, OnDemandAzureAppConfigurationRefresher>();
Expose an endpoint to trigger App Configuration refresh on demand
Inject it in a Refresh controller to expose the refresh endpoint
// RefreshAzAppConfigurationController
[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();
}
}
Talk is cheap
Here’s the full code & usage, and here’s the nuget package with the refresher.