Azure App Configuration not returning key/value pairs when using .NET (9.0) ConfigurationBuilder and AddAzureAppConfiguration

Nico David Aspinall 40 Reputation points
2025-10-28T10:35:56.08+00:00

I have a simple .Net 9 C# Console App in which I am attempting to access the Azure App Configuration Resource to retrieve various configuration settings.

The code to achieve this is simple and has always worked in the past:

var configuration = new ConfigurationBuilder()
.AddAzureAppConfiguration(connectionString)
.Build();

The connection string is the primary read-only string provided by the Azure Portal.

The code executes as expected. However, the settings returned include ‘old’ key-value pairs but not those recently added. These are deliberately set to simple ‘text/plain’ values with single-level keys, i.e. Test : Test.

Running:

az appconfig kv list --connection-string $AppConfigConnectionString

returns the settings as expected.

What is going on here?

Azure App Configuration
Azure App Configuration
An Azure service that provides hosted, universal storage for Azure app configurations.
0 comments No comments
{count} votes

Answer accepted by question author
  1. Natheem Yousuf 340 Reputation points
    2025-10-28T11:18:44.91+00:00

    Most likely you’re seeing old values because of labels or the provider cache/refresh behavior — not because the service itself is stale (you already confirmed az appconfig kv list shows the new keys). Two things to check/fix

    1. Label filtering

    By default the .NET provider loads only key-values that have no label. If the new keys you added have a label (for example dev, prod, or even an accidental label), the provider will not return them unless you explicitly select that label (or select all labels).

    If you want to load keys regardless of label:

    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Configuration.AzureAppConfiguration;
    using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
    
    var builder = new ConfigurationBuilder();
    builder.AddAzureAppConfiguration(options =>
    {
        options.Connect(connectionString)
               .Select(KeyFilter.Any, LabelFilter.Any); // <-- load keys across all labels
    });
    var config = builder.Build();
    

    Or specify the exact label you use:

    .Select("MyApp:*", "dev") // load keys with label "dev"
    
    

    Docs: the provider defaults to loading only unlabeled keys; use LabelFilter.Any or pass your label via Select.

    2. Provider snapshot / cache

    The App Configuration .NET provider loads a snapshot and caches values. Adding keys in the portal does not magically push them into an already-running process. To pick up runtime changes you must either:

    restart the app (simple), or

    incorporate the provider’s refresh capability and call TryRefreshAsync/IConfigurationRefresher (recommended for long-running apps).

    Example pattern (console / background service):

    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Configuration.AzureAppConfiguration;
    using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
    
    IConfigurationRefresher refresher = null;
    
    var builder = new ConfigurationBuilder();
    builder.AddAzureAppConfiguration(options =>
    {
        options.Connect(connectionString)
               .Select(KeyFilter.Any, LabelFilter.Any)   // or your label
               .ConfigureRefresh(refresh =>
               {
                   // register a sentinel key or a key to watch
                   refresh.Register("SentinelKey", LabelFilter.Null, refreshAll: true)
                          .SetCacheExpiration(TimeSpan.FromSeconds(30)); // how often provider will poll
               });
    
        refresher = options.GetRefresher(); // keep a reference to trigger refreshes
    });
    
    var config = builder.Build();
    
    // Later, when your app is active, try to refresh:
    await refresher.TryRefreshAsync();  // will pull latest values if sentinel changed (or expired)
    
    

    Notes:

    • ConfigureRefresh tells the provider what to monitor (often a "sentinel" key) and how the cache behaves; it doesn’t automatically refresh on every read. You must call TryRefreshAsync() (or wire refresh into middleware, a timer, or request handling).

    If you want immediate pickup without sentinel logic, restart the app — that rebuilds the provider and loads the latest values.

    • If you call TryRefreshAsync() and nothing changes, you might need to SetDirty() / register proper sentinel keys so the provider knows to invalidate cache. (Some scenarios require SetDirty to force cache invalidation.)

    Reference: https://learn.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-dotnet-core

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Nico David Aspinall 40 Reputation points
    2025-10-28T13:27:04.96+00:00

    This is a 'Note to Self' but I will add it here for anyone who might be interested. I remember now that I had visited this issue some time ago and had resolved not to use the Azure App Configuration Configuration Provider and the extension to the Configuration Builder. I had forgotten. Now I remember why !?

    The main issue is that the Provider works very differently to the standard JSON Configuration Provider we use to load, for example, configuration settings from a 'appsettings.json' file. Notably it insists on flattening 'application/json' values to 'leaf node' key-value pairs with hierarchical keys and simple text values.

    This is fine in itself. But most developers, I'm sure, will migrate 'appsettings.json' data straight to Azure App Configuration. The standard JSON Configuration Provider is by no means perfect. It struggles with more complex JSON, most notably in my case Dictionaries, for example, of Azure Resources with accompanying settings. Faced with this I implemented my own 'Configurator' to effectively 'deserialize' an IConfiguration to a relatively dynamic class amenable to convenient 'interrogation'.

    The outcome of all this is that I will continue to use the 'Configurator' approach, and do the following using the .NET Configuration Client:

    // Custom Configuration Settings Container
    IConfigurationSettings configurationSettings;
    
    // Custom Configurator
    using IConfigurator configurator = new Configurator();
    
    var azureAppConfigurationClient = new ConfigurationClient(connectionString);
    
    Task.Run(async () =>
    {
        var azureResponse = await azureAppConfigurationClient
            .GetConfigurationSettingAsync("<application-settings-key>");
    
        if (azureResponse.Value is null)
        {
            this.Fail("Failed to read the application configuration settings");
            return;
        }
    
        var azureAppConfigurationValue = azureResponse.Value.Value;
    
        var azureAppConfigurationStream = new MemoryStream(Encoding.UTF8.GetBytes(azureAppConfigurationValue));
    
        var hostConfiguration = new ConfigurationBuilder()
            .AddJsonStream(azureAppConfigurationStream)
            .Build();
    
        configurationSettings = configurator.Configure(hostConfiguration);
    });
    
    ...
    
    // For example:
    
    var azureServiceBusSettings = configurationSettings.GetAzureResourceSettings("ServiceBus");
    
    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.