Tutorial: Use dynamic configuration using push refresh in a .NET Core app

The App Configuration .NET Core client library supports updating configuration on demand without causing an application to restart. An application can be configured to detect changes in App Configuration using one or both of the following two approaches.

  1. Poll Model: This is the default behavior that uses polling to detect changes in configuration. Once the cached value of a setting expires, the next call to TryRefreshAsync or RefreshAsync sends a request to the server to check if the configuration has changed, and pulls the updated configuration if needed.

  2. Push Model: This uses App Configuration events to detect changes in configuration. Once App Configuration is set up to send key value change events to Azure Event Grid, the application can use these events to optimize the total number of requests needed to keep the configuration updated. Applications can choose to subscribe to these either directly from Event Grid, or though one of the supported event handlers such as a webhook, an Azure function or a Service Bus topic.

Applications can choose to subscribe to these events either directly from Event Grid, or through a web hook, or by forwarding events to Azure Service Bus. The Azure Service Bus SDK provides an API to register a message handler that simplifies this process for applications that either do not have an HTTP endpoint or do not wish to poll the event grid for changes continuously.

This tutorial shows how you can implement dynamic configuration updates in your code using push refresh. It builds on the app introduced in the quickstarts. Before you continue, finish Create a .NET Core app with App Configuration first.

You can use any code editor to do the steps in this tutorial. Visual Studio Code is an excellent option that's available on the Windows, macOS, and Linux platforms.

In this tutorial, you learn how to:

  • Set up a subscription to send configuration change events from App Configuration to a Service Bus topic
  • Set up your .NET Core app to update its configuration in response to changes in App Configuration.
  • Consume the latest configuration in your application.

Prerequisites

To do this tutorial, install the .NET Core SDK.

If you don't have an Azure subscription, create a free account before you begin.

Set up Azure Service Bus topic and subscription

This tutorial uses the Service Bus integration for Event Grid to simplify the detection of configuration changes for applications that do not wish to poll App Configuration for changes continuously. The Azure Service Bus SDK provides an API to register a message handler that can be used to update configuration when changes are detected in App Configuration. Follow steps in the Quickstart: Use the Azure portal to create a Service Bus topic and subscription to create a service bus namespace, topic and subscription.

Once the resources are created, add the following environment variables. These will be used to register an event handler for configuration changes in the application code.

Key Value
ServiceBusConnectionString Connection string for the service bus namespace
ServiceBusTopic Name of the Service Bus topic
ServiceBusSubscription Name of the service bus subscription

Set up Event subscription

  1. Open the App Configuration resource in the Azure portal, then click on + Event Subscription in the Events pane.

    App Configuration Events

  2. Enter a name for the Event Subscription and the System Topic.

    Create event subscription

  3. Select the Endpoint Type as Service Bus Topic, elect the Service Bus topic, then click on Confirm Selection.

    Event subscription service bus endpoint

  4. Click on Create to create the event subscription.

  5. Click on Event Subscriptions in the Events pane to validate that the subscription was created successfully.

    App Configuration event subscriptions

Note

When subscribing for configuration changes, one or more filters can be used to reduce the number of events sent to your application. These can be configured either as Event Grid subscription filters or Service Bus subscription filters. For example, a subscription filter can be used to only subscribe to events for changes in a key that starts with a specific string.

Register event handler to reload data from App Configuration

Open Program.cs and update the file with the following code.

using Microsoft.Azure.ServiceBus;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using System;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        private const string AppConfigurationConnectionStringEnvVarName = "AppConfigurationConnectionString"; // e.g. Endpoint=https://{store_name}.azconfig.io;Id={id};Secret={secret}
        private const string ServiceBusConnectionStringEnvVarName = "ServiceBusConnectionString"; // e.g. Endpoint=sb://{service_bus_name}.servicebus.windows.net/;SharedAccessKeyName={key_name};SharedAccessKey={key}
        private const string ServiceBusTopicEnvVarName = "ServiceBusTopic";
        private const string ServiceBusSubscriptionEnvVarName = "ServiceBusSubscription";

        private static IConfigurationRefresher _refresher = null;

        static async Task Main(string[] args)
        {
            string appConfigurationConnectionString = Environment.GetEnvironmentVariable(AppConfigurationConnectionStringEnvVarName);

            IConfiguration configuration = new ConfigurationBuilder()
                .AddAzureAppConfiguration(options =>
                {
                    options.Connect(appConfigurationConnectionString);
                    options.ConfigureRefresh(refresh =>
                        refresh
                            .Register("TestApp:Settings:Message")
                            .SetCacheExpiration(TimeSpan.FromDays(30))  // Important: Reduce poll frequency
                    );

                    _refresher = options.GetRefresher();
                }).Build();

            RegisterRefreshEventHandler();
            var message = configuration["TestApp:Settings:Message"];
            Console.WriteLine($"Initial value: {configuration["TestApp:Settings:Message"]}");

            while (true)
            {
                await _refresher.TryRefreshAsync();

                if (configuration["TestApp:Settings:Message"] != message)
                {
                    Console.WriteLine($"New value: {configuration["TestApp:Settings:Message"]}");
                    message = configuration["TestApp:Settings:Message"];
                }
                
                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }

        private static void RegisterRefreshEventHandler()
        {
            string serviceBusConnectionString = Environment.GetEnvironmentVariable(ServiceBusConnectionStringEnvVarName);
            string serviceBusTopic = Environment.GetEnvironmentVariable(ServiceBusTopicEnvVarName);
            string serviceBusSubscription = Environment.GetEnvironmentVariable(ServiceBusSubscriptionEnvVarName);
            SubscriptionClient serviceBusClient = new SubscriptionClient(serviceBusConnectionString, serviceBusTopic, serviceBusSubscription);

            serviceBusClient.RegisterMessageHandler(
                handler: (message, cancellationToken) =>
               {
                   string messageText = Encoding.UTF8.GetString(message.Body);
                   JsonElement messageData = JsonDocument.Parse(messageText).RootElement.GetProperty("data");
                   string key = messageData.GetProperty("key").GetString();
                   Console.WriteLine($"Event received for Key = {key}");

                   _refresher.SetDirty();
                   return Task.CompletedTask;
               },
                exceptionReceivedHandler: (exceptionargs) =>
                {
                    Console.WriteLine($"{exceptionargs.Exception}");
                    return Task.CompletedTask;
                });
        }
    }
}

The SetDirty method is used to set the cached value for key-values registered for refresh as dirty. This ensures that the next call to RefreshAsync or TryRefreshAsync re-validates the cached values with App Configuration and updates them if needed.

A random delay is added before the cached value is marked as dirty to reduce potential throttling in case multiple instances refresh at the same time. The default maximum delay before the cached value is marked as dirty is 30 seconds, but can be overridden by passing an optional TimeSpan parameter to the SetDirty method.

Note

To reduce the number of requests to App Configuration when using push refresh, it is important to call SetCacheExpiration(TimeSpan cacheExpiration) with an appropriate value of cacheExpiration parameter. This controls the cache expiration time for pull refresh and can be used as a safety net in case there is an issue with the Event subscription or the Service Bus subscription. The recommended value is TimeSpan.FromDays(30).

Build and run the app locally

  1. Set an environment variable named AppConfigurationConnectionString, and set it to the access key to your App Configuration store. If you use the Windows command prompt, run the following command and restart the command prompt to allow the change to take effect:

     setx AppConfigurationConnectionString "connection-string-of-your-app-configuration-store"
    

    If you use Windows PowerShell, run the following command:

     $Env:AppConfigurationConnectionString = "connection-string-of-your-app-configuration-store"
    

    If you use macOS or Linux, run the following command:

     export AppConfigurationConnectionString='connection-string-of-your-app-configuration-store'
    
  2. Run the following command to build the console app:

     dotnet build
    
  3. After the build successfully completes, run the following command to run the app locally:

     dotnet run
    

    Push refresh run before update

  4. Sign in to the Azure portal. Select All resources, and select the App Configuration store instance that you created in the quickstart.

  5. Select Configuration Explorer, and update the values of the following keys:

    Key Value
    TestApp:Settings:Message Data from Azure App Configuration - Updated
  6. Wait for 30 seconds to allow the event to be processed and configuration to be updated.

    Push refresh run after updated

Clean up resources

If you do not want to continue using the resources created in this article, delete the resource group you created here to avoid charges.

Important

Deleting a resource group is irreversible. The resource group and all the resources in it are permanently deleted. Make sure that you don't accidentally delete the wrong resource group or resources. If you created the resources for this article inside a resource group that contains other resources you want to keep, delete each resource individually from its respective pane instead of deleting the resource group.

  1. Sign in to the Azure portal, and select Resource groups.
  2. In the Filter by name box, enter the name of your resource group.
  3. In the result list, select the resource group name to see an overview.
  4. Select Delete resource group.
  5. You're asked to confirm the deletion of the resource group. Enter the name of your resource group to confirm, and select Delete.

After a few moments, the resource group and all its resources are deleted.

Next steps

In this tutorial, you enabled your .NET Core app to dynamically refresh configuration settings from App Configuration. To learn how to use an Azure managed identity to streamline the access to App Configuration, continue to the next tutorial.