Skip to content

Add Microsoft.Extensions.Configuration.AppConfiguration package#56619

Closed
m-nash wants to merge 1 commit intomainfrom
add-configuration-appconfiguration
Closed

Add Microsoft.Extensions.Configuration.AppConfiguration package#56619
m-nash wants to merge 1 commit intomainfrom
add-configuration-appconfiguration

Conversation

@m-nash
Copy link
Copy Markdown
Member

@m-nash m-nash commented Feb 28, 2026

Description

Implements IConfigurationSource for Azure App Configuration using the System.ClientModel configuration pattern (ConfigurationClientSettings) and Azure Identity (WithAzureCredential).

Public API

The public API surface is minimal — two AddAppConfigurations extension methods on IConfigurationBuilder:

  • AddAppConfigurations(string sectionName) — loads settings using a ConfigurationClient configured from the named configuration section
  • AddAppConfigurations(string sectionName, Action<ConfigurationClientSettings> configure) — same, with an optional settings override

Design Notes

Unlike the Key Vault configuration provider, App Configuration keys use native : separators for hierarchical configuration (e.g., MyClient:Options:Retry:MaxRetries) since the service supports them directly. No -- to : translation is needed.

Internal Implementation

The internal implementation (provider, source, setting manager) mirrors the existing Microsoft.Extensions.Configuration.KeyVault package pattern with full test parity (17 tests).

Changes

  • New package: sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/
  • Added to sdk/appconfiguration/ci.yml

Fixes #55509

Implements IConfigurationSource for Azure App Configuration using the
System.ClientModel configuration pattern (ConfigurationClientSettings)
and Azure Identity. Keys use native colon (:) separators since App
Configuration supports them directly (unlike Key Vault secrets).

Fixes #55509

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the App Configuration Azure.ApplicationModel.Configuration label Feb 28, 2026
@github-actions
Copy link
Copy Markdown

API Change Check

APIView identified API level changes in this PR and created the following API reviews

Microsoft.Extensions.Configuration.AppConfiguration

@m-nash m-nash marked this pull request as ready for review March 2, 2026 17:55
Copilot AI review requested due to automatic review settings March 2, 2026 17:55
@m-nash m-nash marked this pull request as draft March 2, 2026 17:55
@m-nash m-nash marked this pull request as ready for review March 2, 2026 17:55
@m-nash m-nash marked this pull request as draft March 2, 2026 17:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Microsoft.Extensions.Configuration.AppConfiguration package under sdk/appconfiguration/ that provides IConfigurationBuilder extension methods to load configuration values from Azure App Configuration using the System.ClientModel configuration pattern (ConfigurationClientSettings) and Azure Identity configuration binding.

Changes:

  • Added a new extension package implementing an IConfigurationSource/IConfigurationProvider for Azure App Configuration.
  • Added a full NUnit test suite (including snippets) and API surface files for the new package.
  • Wired the new package into sdk/appconfiguration/ci.yml and documentation tooling configuration.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
sdk/appconfiguration/ci.yml Adds the new artifact to the App Configuration service pipeline.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/Snippets.cs Adds usage snippet for AddAppConfigurations.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/MyClientSettings.cs Test-only ClientSettings type for snippet/DI tests.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/MyClient.cs Test-only client type for snippet/DI tests.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/Microsoft.Extensions.Configuration.AppConfiguration.Tests.csproj Introduces test project for the new package.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/ConfigurationProviderExtensions.cs Adds test helper extension for reading provider values.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/tests/AppConfigurationTests.cs Adds provider behavior tests (load/filter/reload/custom mapping/custom data).
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/Properties/AssemblyInfo.cs Adds InternalsVisibleTo for tests/proxy generation.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/Microsoft.Extensions.Configuration.AppConfiguration.csproj Defines the new package project and dependencies.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/AppConfigurationSource.cs Implements IConfigurationSource to create the provider.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/AppConfigurationSettingManager.cs Adds a manager abstraction for filtering/mapping/settings-to-data conversion.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/AppConfigurationProvider.cs Implements the configuration provider, including background polling reload.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/AppConfigurationOptions.cs Provides internal options for reload interval and selector/manager.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/src/AppConfigurationExtensions.cs Adds the public AddAppConfigurations extension methods.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/api/Microsoft.Extensions.Configuration.AppConfiguration.netstandard2.0.cs Adds API surface snapshot for netstandard2.0.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/api/Microsoft.Extensions.Configuration.AppConfiguration.net8.0.cs Adds API surface snapshot for net8.0.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/api/Microsoft.Extensions.Configuration.AppConfiguration.net10.0.cs Adds API surface snapshot for net10.0.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/README.md Adds package README with getting started + example.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/Directory.Build.props Marks the package directory as a client library for build tooling.
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/CHANGELOG.md Adds initial Unreleased changelog entry for 1.0.0-beta.1.
eng/.docsettings.yml Adds the new README to the docsettings allowlist/known issues list.

Comment on lines +84 to +86
bool hasChanges = oldLoadedSettings == null
|| newLoadedSettings.Count != (oldLoadedSettings.Count + newLoadedSettings.Count - oldLoadedSettings.Count)
|| oldLoadedSettings.Any();
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hasChanges calculation is incorrect: (oldLoadedSettings.Count + newLoadedSettings.Count - oldLoadedSettings.Count) always simplifies to newLoadedSettings.Count, so this count comparison can never detect added/removed keys. This can prevent Data from being refreshed (and OnReload() raised) when settings are added/removed but existing keys are up-to-date. Track the original old count before removals and compare it to newLoadedSettings.Count, and/or explicitly detect additions/removals/updates.

Copilot uses AI. Check for mistakes.
var newLoadedSettings = new Dictionary<string, ConfigurationSetting>(StringComparer.OrdinalIgnoreCase);
var oldLoadedSettings = Interlocked.Exchange(ref _loadedSettings, null);

await foreach (var setting in _client.GetConfigurationSettingsAsync(_selector).ConfigureAwait(false))
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async settings query doesn't pass _cancellationToken.Token to GetConfigurationSettingsAsync(...), so disposal won't cancel an in-progress refresh. Pass the token to the client call so the background polling loop can shut down promptly.

Suggested change
await foreach (var setting in _client.GetConfigurationSettingsAsync(_selector).ConfigureAwait(false))
await foreach (var setting in _client.GetConfigurationSettingsAsync(_selector, _cancellationToken.Token).ConfigureAwait(false))

Copilot uses AI. Check for mistakes.
using System;
using System.Diagnostics.CodeAnalysis;
using Azure.Data.AppConfiguration;
using Azure.Identity;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Azure.Identity; is unused in this file. Since warnings are treated as errors, this will fail the build; remove the unused using (the package reference can remain).

Suggested change
using Azure.Identity;

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +116
while (!_cancellationToken.IsCancellationRequested)
{
await WaitForReload().ConfigureAwait(false);
try
{
await LoadAsync().ConfigureAwait(false);
}
catch (Exception)
{
// Ignore
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaitForReload() uses Task.Delay(..., _cancellationToken.Token), which will throw OperationCanceledException when disposed/canceled. That exception isn't caught in PollForSettingChangesAsync, so the background polling task can fault on disposal (potentially surfacing as an unobserved task exception). Catch OperationCanceledException around the delay (or in the loop) and exit cleanly when cancellation is requested.

Copilot uses AI. Check for mistakes.
/// </summary>
public override void Load()
{
var settingPages = _client.GetConfigurationSettings(_selector);
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provider creates a _cancellationToken for shutdown, but the synchronous settings query doesn't pass it to GetConfigurationSettings. This means an in-flight load won't be cancelable during Dispose(). Consider passing _cancellationToken.Token to GetConfigurationSettings(_selector, ...) to honor cancellation.

Suggested change
var settingPages = _client.GetConfigurationSettings(_selector);
var settingPages = _client.GetConfigurationSettings(_selector, _cancellationToken.Token);

Copilot uses AI. Check for mistakes.

using System;
using System.Collections.Generic;
using System.Linq;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using System.Linq; is unused in this file. The repo treats warnings as errors, so this will break the build; remove the unused using (or use it).

Suggested change
using System.Linq;

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
/// Default implementation of <see cref="AppConfigurationSettingManager"/> that loads all settings
/// and replaces '--' with ':' in key names.
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML summary says the default manager "replaces '--' with ':' in key names", but GetKey currently returns setting.Key unchanged. Update the comment to match the actual behavior (or implement the replacement if that's intended).

Suggested change
/// Default implementation of <see cref="AppConfigurationSettingManager"/> that loads all settings
/// and replaces '--' with ':' in key names.
/// Default implementation of <see cref="AppConfigurationSettingManager"/> that loads all settings.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +6
using System;
using System.ClientModel;
using System.Diagnostics.CodeAnalysis;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using System; is unused in this snippet. With warnings treated as errors, the test project will fail to compile; remove the unused using.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +155
bool hasChanges = oldLoadedSettings == null
|| newLoadedSettings.Count != (oldLoadedSettings.Count + newLoadedSettings.Count - oldLoadedSettings.Count)
|| oldLoadedSettings.Any();

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the sync Load(): this hasChanges count comparison simplifies to comparing newLoadedSettings.Count to itself, so it can never detect additions/removals when oldLoadedSettings has been drained of up-to-date keys. Fix the change-detection logic here as well (e.g., keep the original old count before removals and compare to the new count).

Copilot uses AI. Check for mistakes.
@zhiyuanliang-ms
Copy link
Copy Markdown
Member

zhiyuanliang-ms commented Mar 3, 2026

Hey, @m-nash

There is already the Microsoft.Extensions.Configuration.AzureAppConfiguration nuget package.

Here is the repo: https://github.com/Azure/AppConfiguration-DotnetProvider

@m-nash
Copy link
Copy Markdown
Member Author

m-nash commented Mar 4, 2026

Closing in favor of Azure/AppConfiguration-DotnetProvider#728 which implements this directly in the existing provider package.

@m-nash m-nash closed this Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App Configuration Azure.ApplicationModel.Configuration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Microsoft.Extensions.Configuration.Azure.AppConfiguration: Add IConfigurationSource Extension

3 participants