Application configuration
.NET .NET Core ASP.NET Core Azure C# Dependency Injection Patterns Visual Studio

How to Refresh Web App Configurations with the Azure App Configuration Service

Welcome to today’s post.

In today’s post I will show how to refresh application settings from Azure App Configuration service within a web application.

In a previous post I showed how to read application setting key-value pairs from an Azure App Configuration service then integrate the settings into a basic ASP.NET Core web application. In an even earlier post I showed how to retrieve local application settings into a .NET Core web application using the IOptions pattern. In these two posts the settings were read in once during the application startup and stayed the same throughout the running of the application until shutdown.

In many applications, settings are read in once and not refreshed throughout the execution of the application. In many applications we would not want to refresh a database connection or remote service connection while a user is actioning a request to one of these services that suddenly has its configurations changed. With other settings that do not affect critical data, such as the application theme including font colors, heading sizes etc. these can be refreshed. In a previous post I showed how to refresh changes from local application settings into a .NET Core application using the IOptionsSnapshot pattern.

In today’s post I will be building on the mentioned posts and show how to refresh settings modified from within an Azure App Configuration service.

Setting up Azure App Configuration in a .NET Core Application

In addition to the following packages:

Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.AzureAppConfiguration

that are required to retrieve settings both locally and the cloud, with an ASP.NET Core web application we will require an additional NuGet package:

Microsoft.Azure.AppConfiguration.AspNetCore 

to allow an ASP.NET Core Web application to be able to read settings from an Azure App Configuration service.

In addition, we are required to add the following middleware to our Configure() method in Startup.cs that allows our web application to process changes to configurations only during application activity, such as any web requests:

app.UseAzureAppConfiguration();

Being able to detect and refresh settings from within an Azure App Configuration service requires us to add an additional unique key-value pair. This extra key is known as a Sentinel key, which when modified triggers off a refresh within the configuration service to allow the remaining key-value pairs to be read in one request by a client connected to the configuration service. (The analogy of a sentinel in the real-world is of a watchman in a tower that alerts the rest of the soldiers with a whistle or trumpet in the fortress that the enemy is approaching.)

In the Configuration Explorer, we create a new key-value for the Sentinel key as shown:

I have not experimented with naming the sentinel key differently, so this would be a worthwhile exercise.

In the next section, I will show how to use the Sentinel key in .NET Core Applications.

Using the Sentinel Key to Trigger Configuration Changes

The sentinel key when added will show as key-value pairs within the grid in the Configuration Explorer:

In our experiment, the original key-value pairs stored in our configuration service as shown:

KeyValue
BookLoanApp:Settings:LandingPageHeadingWelcome Reader!
BookLoanApp:Settings:LandingPageMessageBrowse through our wonderful collection of Books and Media!

I will go through the code used to perform the configuration settings refresh. The code below applies to .NET Core 3.1 and 5.0. For Versions 6 and above, I will give an overview in a future post.

In Program.cs, we amend the CreateHostBuilder() method, which is originally:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

We then add code to read in the end point from local configuration settings, then connect to the Azure App Configuration service before application startup. This is shown below:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            IConfiguration settings = config.Build();
            string connectionString = 
                settings.GetValue<string>(
                    "AppSettings:AppConfigConnection"
                );

            webBuilder.ConfigureAppConfiguration(config =>
            {
                config.AddAzureAppConfiguration(connectionString);
            });
            webBuilder.UseStartup<Startup>();
    });

Recall that in the previous post, we implemented the above within the ConfigureServices() method in Startup.cs. With the refresh of remote settings, the bootstrapping of the Azure App Configuration service provider needs to be done within the CreateHostBuilder() before ConfigureServices() is called. The reason is that the overload of the services.AddAzureAppConfiguration(..) extension method is not available in ConfigureServices().

We next add some additional code to configure options within config.AddAzureAppConfiguration(..) to select (using Select(..)) of the key-values (which we did last time) and register the key that will be used as the sentinel key for refreshing values from the other key-value pairs (using ConfigureRefresh(..).

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
..
public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.ConfigureAppConfiguration(config =>
                {
                    IConfiguration settings = config.Build();
                    string connectionString = 
                        settings.GetValue<string>(
                            "AppSettings:AppConfigConnection"
                        );
                    config.AddAzureAppConfiguration(options =>
                    {
                        options.Connect(connectionString)
                            // Load selected keys starting with `BookLoanApp:` and have no label
                            .Select("BookLoanApp:*", LabelFilter.Null)
                            // Configure to reload configuration if the registered sentinel key is modified
                            .ConfigureRefresh(refreshOptions =>
                                refreshOptions.Register(
                                    "BookLoanApp:Settings:Sentinel", 
                                    refreshAll: true
                                )
                            );
                    });
                });
                webBuilder.UseStartup<Startup>();
            });

In Startup.cs. we implement the ConfigureService() and Configure() methods as follows:

Within ConfigureService() we add the AddAzureAppConfiguration() service to the services collection and bind the BookLoanApp:Settings key from the configuration provider instance to the AppConfiguration object as shown:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
…
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages().AddRazorRuntimeCompilation();

    services.AddAzureAppConfiguration();

    services.Configure<AppConfiguration>(Configuration.GetSection("BookLoanApp:Settings"));
}

Within the Configure() method we add the UseAzureAppConfiguration() middleware to the application builder pipeline. As mentioned earlier, this allows an activity-driven refresh when requests are running within the web application.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    } 

    app.UseAzureAppConfiguration();
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

In the next section, I will show how to apply changes to classes using IOptionsSnapshot to reflect Azure App Configuration changes.

Refreshing Configuration Changes in Classes with IOptionsSnapshot

In the HomeController, we have changed the IOptions interface to IOptionsSnapshot to allow for the refreshing of values from the refreshed configurations from the startup.

namespace BookLoanWebApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IOptionsSnapshot<AppConfiguration> _appConfiguration;

        public HomeController(ILogger<HomeController> logger, 
            IOptionsSnapshot<AppConfiguration> options)
        {
            _logger = logger;
            _appConfiguration = options;
        }

        public IActionResult Index()
        {
            IndexModel indexModel = new IndexModel() {
                HeadingMessage = _appConfiguration.Value.LandingPageHeading, 
                WelcomeMessage = _appConfiguration.Value.LandingPageMessage
            };
            return View(indexModel);
        }
        …

After running the application with a breakpoint in the ConfigureServices() method, we can see the key-values loaded from the app configuration as shown:

An additional breakpoint in the HomeController constructor shows the values read in at startup injected to the IOptionsSnapshot object as shown:

The main landing page shows the original values from the key-value pairs stored within the Azure App configuration service:

In the next section, I will show how to test refreshed application configurations within a web application by changing a configuration value from the remote Azure App Configuration.

Testing Refreshing Azure App Configurations

We can test the refresh by applying the following amendments to the key-value pairs to the Azure App Configuration within the Configuration Explorer (in the Azure Portal). This is done through the Edit action in the ellipsis menu for each key-value we wish to amend.

Below we amend the first value of the first key-value:

And the second value of the second key-value:

Finally, to trigger off the refresh we amend the value of the sentinel key as shown:

The keys will have been amended as shown:

KeysValue
BookLoanApp:Settings:LandingPageHeadingWelcome Book Reader!
BookLoanApp:Settings:LandingPageMessageBrowse through our wonderful collection of Books and Electronic Media!
BookLoanApp:Settings:Sentinel2

The updated key-value pairs (partially shown) as shown below from the Configuration Explorer:

Switch back to the application. You notice that you will have to wait a little for the changes to take effect and be read by the application configuration provider. The reason for this delay in getting the refreshed values to be available from the configuration provider is that the default behaviour is to cache any refreshed values by 30 seconds. There is another refresh configuration method, SetCacheExpiration() that is called from the ConfigureRefresh() configuration option that allows us to control the caching duration of modified keys that are refreshed by the configuration provider.

The example below shows a configuration of the duration of key caching to 5 minutes:

.ConfigureRefresh(refreshOptions => refreshOptions
    .Register("BookLoanApp:Settings:Sentinel", refreshAll: true)
    .SetCacheExpiration(new System.TimeSpan(0,5,0))
);

After the default caching of the refreshed keys expires, the values within the IOptionsSnapshot object will update to the values shown:

The landing page will also have the heading and message updated as shown:

What we have seen is how to refresh modified settings from an Azure App Configuration service into an ASP.NET Core application. We can also implement settings refresh for a .NET Core console application or a .NET Core Web API application utilising similar techniques.  

In a future post I will show how to use Labels and Features from Azure App Configuration key-values to control visibility of application features and partitioning application environments.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial