Date:

Share:

How to automatically refresh configurations with Azure App Configuration in .NET | Code4IT

Related Articles

ASP.NET allows you to poll Azure App Configuration to always get the most up-to-date values ​​without restarting your applications. It’s simple, but you have to think carefully.

Table of Contents

In a previous article we learned how to centralize configurations using Azure App Configuration, a service provided by Azure for sharing configurations securely. With Azure App Configuration, you can store your most critical configurations in one place and apply them to one or more environments or projects.

We used a very simple example with a limitation: you must restart your applications to make the changes effective. In fact, ASP.NET connects to Azure App Config, loads the configurations in memory, and serves those configurations until the next application restart.

In this article, we will learn How to make configurations dynamic: By the end of this article, we will be able to see our settings changes reflected in our applications without restarting them.

Since this article is kind of an improvement of the previous one, you should read it first.

Let me summarize here the code presented in the previous article. We have a .NET API application whose sole purpose is to return the configurations stored in an object, whose form is this:

{
  "MyNiceConfig": {
    "PageSize": 6,
    "Host": {
      "BaseUrl": "https://www.mydummysite.com",
      "Password": "123-go"
    }
  }
}

In the constructor of the API controller, I injected an IOptions<MyConfig> An instance that holds the current data stored in the application.

 public ConfigDemoController(IOptions<MyConfig> config)
        => _config = config;

HTTP’s only endpoint is GET: it simply accesses that value and returns it to the client.

[HttpGet()]
public IActionResult Get()
{
    return Ok(_config.Value);
}

Finally, I created a new instance of Azure App Configuration, and used a connection string to combine Azure App Configuration with the existing configurations by calling:

builder.Configuration.AddAzureAppConfiguration(ConnectionString);

Now we can move on and make configurations dynamic.

Sentinel values: A save value to track configuration changes

In Azure App Configuration, you must update the configurations manually one by one. Unfortunately, there is no way to update them in one batch. You can Import them in bulkBut you have to update them individually.

Imagine you have a service that accesses an external API whose BaseUrl key and API key are stored in the Az App configuration. Now we need to switch to another API: Next we need to update both the BaseUrl and the API key. The application is running, and we want to update the information about the external API. If we were to update the app configurations every time something is updated in Az App Configuration, we’d end up with an invalid state – for example, we’d have the new BaseUrl and the old API key.

Therefore, we need to define a configuration value that acts as a sort of version key for the entire list of configurations. In Azure App Configuration jargon, this is called sentry.

Sentinel is nothing but a version key: This is a string value used by the application to figure out if it needs to reload the entire configuration list. Since it’s just a string, you can set any value, as long as it changes over time. My suggestion is to use the UTC date value from the moment you updated the value, such as 202306051522. This way, in case of errors you can understand when was the last time one of these values ​​changed (but you won’t know which values ​​changed), and, Depending on the pricing tier you are using, you can compare the current values ​​with the previous values.

So, go back to Configuration Explorer Page and add a new entry: I called it sentry.

As I said, you can use any value. For the sake of this article, I’m going to use a simple number (just for simplicity).

Configure how to refresh configurations by initializing an ASP.NET Core application

We can finally move to code!

If you remember, in the previous article we added a NuGet package, Microsoft.Azure.AppConfiguration.AspNetCoreAnd then we added Azure App Configuration as a configuration source by calling

builder.Configuration.AddAzureAppConfiguration(ConnectionString);

This command is used to load all configurations, without polling and updating. Therefore we must remove it.

Instead of this instruction, add this other instruction:

builder.Configuration.AddAzureAppConfiguration(options =>
{
    options
    .Connect(ConnectionString)
    .Select(KeyFilter.Any, LabelFilter.Null)
    // Configure to reload configuration if the registered sentinel key is modified
    .ConfigureRefresh(refreshOptions =>
              refreshOptions.Register("Sentinel", label: LabelFilter.Null, refreshAll: true)
        .SetCacheExpiration(TimeSpan.FromSeconds(3))
      );
});

Let’s dive deep into each part:

options.Connect(ConnectionString) Just tells ASP.NET to load the configurations from that particular connection string.

.Select(KeyFilter.Any, LabelFilter.Null) Loads all unlabeled keys;

And finally, the most important part:

.ConfigureRefresh(refreshOptions =>
            refreshOptions.Register(key: "Sentinel", label: LabelFilter.Null, refreshAll: true)
      .SetCacheExpiration(TimeSpan.FromSeconds(3))
    );

Here we note that all values ​​must be refreshed (refreshAll: true) where the key with value=”Sentinel” (key: "Sentinel") it is updated. Then, store these values ​​for 3 seconds (SetCacheExpiration(TimeSpan.FromSeconds(3)).

Here I used 3 seconds as refresh time. This means that if the app is in continuous use, the app will poll Azure App Configuration every 3 seconds – clearly a bad idea! So choose the right value according to the change expectations. God The default cache timeout value is 30 seconds.

Note that the previous instruction adds the Azure App configuration to Configuration object, and not as a service used by .NET. In fact, the method is builder.Configuration.AddAzureAppConfiguration. We need two more steps.

First of all, add the Azure App configuration to IServiceCollection resist:

builder.Services.AddAzureAppConfiguration();

Finally, we need to add it to our existing middleware by calling

app.UseAzureAppConfiguration();

The end result is this:

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    const string ConnectionString = "......";

    // Load configuration from Azure App Configuration
    builder.Configuration.AddAzureAppConfiguration(options =>
    {
        options.Connect(ConnectionString)
                .Select(KeyFilter.Any, LabelFilter.Null)
                // Configure to reload configuration if the registered sentinel key is modified
                .ConfigureRefresh(refreshOptions =>
                    refreshOptions.Register(key: "Sentinel", label: LabelFilter.Null, refreshAll: true)
                    .SetCacheExpiration(TimeSpan.FromSeconds(3)));
    });

    // Add the service to IServiceCollection
    builder.Services.AddAzureAppConfiguration();

    builder.Services.AddControllers();
    builder.Services.Configure<MyConfig>(builder.Configuration.GetSection("MyNiceConfig"));

    var app = builder.Build();

    // Add the middleware
    app.UseAzureAppConfiguration();

    app.UseHttpsRedirection();

    app.MapControllers();

    app.Run();
}

IOptionsMonitor: access and monitor configuration values

It’s time to run the project and look at the result: some values ​​come from Azure App Configuration.

The default configuration comes from Azure App Configuration

Now we can update them: without restarting the application, update the PageSize value, and don’t forget to update the Sentinel as well. Call the endpoint again, and… nothing happens! 😯

This is because in our controller we use IOptions<T> instead of IOptionsMonitor<T>. As we learned in a previous article, IOptionsMonitor<T> is a singleton instance that always gets the most updated configuration values. It also emits an event when the configurations have been refreshed.

So, go back to ConfigDemoControllerand replace the way we retrieve the configuration:

[ApiController]
[Route("[controller]")]
public class ConfigDemoController : ControllerBase
{
    private readonly IOptionsMonitor<MyConfig> _config;

    public ConfigDemoController(IOptionsMonitor<MyConfig> config)
    {
        _config = config;
        _config.OnChange(Update);
    }

    [HttpGet()]
    public IActionResult Get()
    {
        return Ok(_config.CurrentValue);
    }

    private void Update(MyConfig arg1, string? arg2)
    {
      Console.WriteLine($"Configs have been updated! PageSize is {arg1.PageSize}, " +
                $" Password is {arg1.Host.Password}");
    }
}

when using IOptionsMonitor<T>You can retrieve the current values ​​of the configuration object by accessing CurrentValue attribute. It is also possible to define a listener for events that must be attached to OnChange case;

Finally we can run the app and update the values ​​in Azure App Configuration.

Again, update one of the values, update the sentinel and wait. After 3 seconds, you will see a pop-up message in the console: This is the text defined in Update method.

Then, call the app again (again, without restarting it), and admire the updated values!

You can see a live demo here:

Demonstration of dynamic refresh configurations

As you can see, the first time after updating the Sentinel value, the values ​​are still old. But, in the meantime, the values ​​have been updated, and the cache has expired, so next time the values ​​will be returned from Azure.

My 2 cents on timing

As we learned, the configuration values ​​are stored in a memory cache, with an expiration time. Whenever the cache expires, we need to retrieve the configurations from Azure App Configuration again (in particular, by checking if the Sentinel entry has been updated in the meantime). Don’t underestimate the value of the cacheBecause there are advantages and disadvantages of each type of value:

  • A A short time range keeps the values ​​always up to date, making your app more responsive to changes. But that also means you are Surveys too often Azure App Configuration endpoints, which makes your app busier and hits request count limits;
  • A A long time span keeps your application performing longer Because there are fewer requests to the Configuration endpoints, but it also forces you to update the configurations after some time from the update applied to Azure.

There is also another problem with long timescales: if the same configurations are used by different services, you may end up with dirty condition. say you have UserService and payment service, and both use certain configurations stored in Azure that have a cache expiration of 10 minutes. Now, the following actions occur:

  1. UserService starts
  2. The payment service starts
  3. Someone is updating the values ​​in Azure
  4. UserService is restarted, while PaymentService is not.

Eventually we will reach a situation where the UserService has the most updated values, while the PaymentService does not. There will be a window of time (in our example, up to 10 minutes) where the configurations are misaligned.

also, take costs and limitations In account: with the free tier you have 1000 requests per dayWhereas with the Standard tier, you have 30,000 per hour per copy. Using the default cache expiration (30 seconds) in an application with a continuous flow of users means you’re going to call the endpoint 2880 times per day (2 times per minute * (minutes per day = 1440)). Much more than the value available in the free tier.

so, Think carefully before choosing an expiration time!

Additional readings

This article is a continuation of a previous one, and I suggest you read the second one to understand how to configure Azure App Configuration and how to integrate it into a .NET API application in case you don’t want to use dynamic configuration.

🔗 Azure App Configuration and .NET API: a smart and secure way to manage configurations | Code4IT

This article first appeared on Code4IT 🐧

Also, we learned that using IOptions We don’t get the most up-to-date values: in fact, we have to use IOptionsMonitor. Check out this article to understand the other differences in IOptions Family.

🔗 Understanding IOptions, IOptionsMonitor and IOptionsSnapshot in .NET 7 | Code4IT

Finally, I talked briefly about pricing. As of July 2023, there are only 2 pricing tiers, with different limitations.

🔗 App Configuration Pricing | Microsoft Learn

finishing

In my opinion, smart configuration handling is essential for those difficult times when you need to understand why an error only occurs in a specific environment.

Centralizing configurations is a good idea, as it allows developers to simulate an entire environment just by changing a few values ​​in the application.

Creating real-time configurations without manually restarting your applications can be a good idea, but you should analyze it thoroughly.

I hope you enjoyed this article! Let’s keep in touch Twitter or LinkedIn! 🤜🤛

Happy coding!

🐧

.

Source

Popular Articles