Add Microsoft.Extensions.Configuration.AppConfiguration package#56619
Add Microsoft.Extensions.Configuration.AppConfiguration package#56619
Conversation
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>
API Change CheckAPIView identified API level changes in this PR and created the following API reviews |
There was a problem hiding this comment.
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/IConfigurationProviderfor 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.ymland 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. |
| bool hasChanges = oldLoadedSettings == null | ||
| || newLoadedSettings.Count != (oldLoadedSettings.Count + newLoadedSettings.Count - oldLoadedSettings.Count) | ||
| || oldLoadedSettings.Any(); |
There was a problem hiding this comment.
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.
| 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)) |
There was a problem hiding this comment.
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.
| await foreach (var setting in _client.GetConfigurationSettingsAsync(_selector).ConfigureAwait(false)) | |
| await foreach (var setting in _client.GetConfigurationSettingsAsync(_selector, _cancellationToken.Token).ConfigureAwait(false)) |
| using System; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using Azure.Data.AppConfiguration; | ||
| using Azure.Identity; |
There was a problem hiding this comment.
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).
| using Azure.Identity; |
| while (!_cancellationToken.IsCancellationRequested) | ||
| { | ||
| await WaitForReload().ConfigureAwait(false); | ||
| try | ||
| { | ||
| await LoadAsync().ConfigureAwait(false); | ||
| } | ||
| catch (Exception) | ||
| { | ||
| // Ignore | ||
| } |
There was a problem hiding this comment.
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.
| /// </summary> | ||
| public override void Load() | ||
| { | ||
| var settingPages = _client.GetConfigurationSettings(_selector); |
There was a problem hiding this comment.
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.
| var settingPages = _client.GetConfigurationSettings(_selector); | |
| var settingPages = _client.GetConfigurationSettings(_selector, _cancellationToken.Token); |
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; |
There was a problem hiding this comment.
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).
| using System.Linq; |
| /// Default implementation of <see cref="AppConfigurationSettingManager"/> that loads all settings | ||
| /// and replaces '--' with ':' in key names. |
There was a problem hiding this comment.
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).
| /// 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. |
| using System; | ||
| using System.ClientModel; | ||
| using System.Diagnostics.CodeAnalysis; |
There was a problem hiding this comment.
using System; is unused in this snippet. With warnings treated as errors, the test project will fail to compile; remove the unused using.
| bool hasChanges = oldLoadedSettings == null | ||
| || newLoadedSettings.Count != (oldLoadedSettings.Count + newLoadedSettings.Count - oldLoadedSettings.Count) | ||
| || oldLoadedSettings.Any(); | ||
|
|
There was a problem hiding this comment.
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).
|
Hey, @m-nash There is already the Here is the repo: https://github.com/Azure/AppConfiguration-DotnetProvider |
|
Closing in favor of Azure/AppConfiguration-DotnetProvider#728 which implements this directly in the existing provider package. |
Description
Implements
IConfigurationSourcefor Azure App Configuration using the System.ClientModel configuration pattern (ConfigurationClientSettings) and Azure Identity (WithAzureCredential).Public API
The public API surface is minimal — two
AddAppConfigurationsextension methods onIConfigurationBuilder:AddAppConfigurations(string sectionName)— loads settings using aConfigurationClientconfigured from the named configuration sectionAddAppConfigurations(string sectionName, Action<ConfigurationClientSettings> configure)— same, with an optional settings overrideDesign 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.KeyVaultpackage pattern with full test parity (17 tests).Changes
sdk/appconfiguration/Microsoft.Extensions.Configuration.AppConfiguration/sdk/appconfiguration/ci.ymlFixes #55509