Skip to main content

Overview

NuGet.Services.Configuration is a shared infrastructure library that bridges the standard Microsoft.Extensions.Configuration pipeline with Azure Key Vault secret injection. It provides custom IConfigurationSource and IConfigurationProvider wrappers that transparently resolve Key Vault secret references embedded inside configuration values — no application code needs to know whether a value came from a JSON file, an environment variable, or Key Vault. The library also ships an older, attribute-driven configuration model built around its own IConfigurationProvider and IConfigurationFactory abstractions. This model lets configuration POCO classes declare their Key Vault key names and prefixes through [ConfigurationKey] and [ConfigurationKeyPrefix] attributes and uses ConfigurationFactory to reflectively populate them at startup. This older pattern predates the Microsoft.Extensions.Configuration binder integration and coexists with the newer SecretInjectedConfiguration wrappers. Supporting both approaches, the library also provides ConfigurationRootSecretReaderFactory, which reads Key Vault connection details (KeyVault_VaultName, KeyVault_UseManagedIdentity, etc.) from the configuration root itself and produces the ISecretReader / ISecretInjector used by everything else. A NonCachingOptionsSnapshot<T> implementation ensures that when KeyVaultInjectingConfigurationProvider is in use, options objects are not cached across requests, allowing Key Vault secret rotation to take effect without a service restart.

Role in System

Application startup

  ├── ConfigurationRootSecretReaderFactory
  │     reads KeyVault_* keys from IConfigurationRoot
  │     creates ISecretReader (KeyVaultReader or EmptySecretReader)
  │     creates ISecretInjector (SecretInjector)

  ├── IConfigurationBuilder extensions (ConfigurationBuilderExtensions)
  │     .AddInjectedJsonFile()          → KeyVaultJsonInjectingConfigurationSource
  │     .AddInjectedEnvironmentVariables() → KeyVaultEnvironmentVariableInjectingConfigurationSource
  │     .AddInjectedInMemoryCollection()   → KeyVaultInMemoryCollectionInjectingConfigurationSource
  │           │
  │           └── each wraps its native provider with
  │               KeyVaultInjectingConfigurationProvider
  │                 TryGet() injects secrets on every read

  ├── SecretInjectedConfiguration / SecretInjectedConfigurationSection
  │     wraps an existing IConfiguration after the builder is built
  │     uses ICachingSecretInjector (cached lookup, then live lookup)

  ├── ConfigurationUtility.ConfigureInjected<T>()
  │     registers IConfigureOptions<T> backed by SecretInjectedConfiguration
  │     requires NonCachingOptionsSnapshot<T> to avoid stale cache

  └── Older attribute-driven model (still used by some services)
        ConfigurationFactory.Get<T>()
          reads [ConfigurationKey] / [ConfigurationKeyPrefix] attributes
          calls IConfigurationProvider.GetOrThrowAsync / GetOrDefaultAsync

Key Vault Injection at Read Time

KeyVaultInjectingConfigurationProvider wraps any existing IConfigurationProvider and calls ISecretInjector.InjectAsync on every TryGet call. A hardcoded exclusion list prevents injection on known connection-string keys that must be passed as-is.

Attribute-Driven POCO Binding

ConfigurationFactory uses reflection and TypeDescriptor to populate Configuration subclass properties. [ConfigurationKey] overrides the key name; [ConfigurationKeyPrefix] adds a prefix at the class or property level; [Required] and [DefaultValue] control error vs. fallback behavior.

Cached Secret Injection Wrapper

SecretInjectedConfiguration wraps any IConfiguration after the builder stage. It uses ICachingSecretInjector.TryInjectCached first, falling back to a live Key Vault call only on a cache miss, reducing latency and request volume.

Managed Identity and Certificate Auth

ConfigurationRootSecretReaderFactory supports two Key Vault auth modes: managed identity (with an optional client ID) and certificate-based auth (thumbprint, store name, store location). It rejects configurations that supply both simultaneously.

Key Files and Classes

FileClass / TypePurpose
ConfigurationRootSecretReaderFactory.csConfigurationRootSecretReaderFactoryReads Key Vault connection settings from an IConfigurationRoot and implements ISecretReaderFactory to produce ISecretReader (or EmptySecretReader when no vault is configured) and ISecretInjector.
KeyVaultInjectingConfigurationProvider.csKeyVaultInjectingConfigurationProviderWraps any IConfigurationProvider and intercepts TryGet to inject Key Vault secrets into values before returning them. Maintains a hardcoded exclusion set for specific database connection-string keys.
KeyVaultJsonInjectingConfigurationSource.csKeyVaultJsonInjectingConfigurationSourceIConfigurationSource that builds a JsonConfigurationProvider and wraps it with KeyVaultInjectingConfigurationProvider. Used by AddInjectedJsonFile.
KeyVaultEnvironmentVariableInjectingConfigurationSource.csKeyVaultEnvironmentVariableInjectingConfigurationSourceIConfigurationSource that builds an EnvironmentVariablesConfigurationProvider (with optional prefix) and wraps it with KeyVaultInjectingConfigurationProvider.
KeyVaultInMemoryCollectionInjectingConfigurationSource.csKeyVaultInMemoryCollectionInjectingConfigurationSourceIConfigurationSource that builds a MemoryConfigurationProvider from an IReadOnlyDictionary<string, string> and wraps it with KeyVaultInjectingConfigurationProvider.
ConfigurationBuilderExtensions.csConfigurationBuilderExtensionsExtension methods on IConfigurationBuilder: AddInjectedJsonFile, AddInjectedEnvironmentVariables, AddInjectedInMemoryCollection. Each adds the corresponding injecting source.
SecretInjectedConfiguration.csSecretInjectedConfigurationIConfiguration decorator that wraps an existing configuration and injects cached secrets on indexer access and GetSection / GetChildren calls.
SecretInjectedConfigurationSection.csSecretInjectedConfigurationSectionExtends SecretInjectedConfiguration to implement IConfigurationSection, including secret injection on the Value property.
SecretConfigurationReader.csSecretConfigurationReaderIConfigurationRoot wrapper that eagerly injects all secret references at construction time and re-injects on Reload. An older, eager-injection alternative to the lazy KeyVaultInjectingConfigurationProvider approach.
ConfigurationUtility.csConfigurationUtilityStatic helpers: ConvertFromString<T> (via TypeDescriptor), InjectCachedSecret (try cache then live), and ConfigureInjected<T> extension on IServiceCollection that registers an IConfigureOptions<T> backed by SecretInjectedConfiguration.
NonCachingOptionsSnapshot.csNonCachingOptionsSnapshot<TOptions>IOptionsSnapshot<TOptions> implementation that bypasses the default per-request cache. Required when using KeyVaultInjectingConfigurationProvider so that secret rotation is reflected without a restart.
ConfigurationFactory.csConfigurationFactoryReflectively populates Configuration subclasses by reading [ConfigurationKey] / [ConfigurationKeyPrefix] attributes from each property and calling IConfigurationProvider.GetOrThrowAsync or GetOrDefaultAsync accordingly.
Configuration.csConfigurationAbstract base class for POCO configuration objects used with ConfigurationFactory. Records a CreatedTime (UTC) at instantiation.
IConfigurationFactory.csIConfigurationFactoryInterface with a single Get<T>() method that returns a fully populated T : Configuration.
IConfigurationProvider.csIConfigurationProviderInterface for the older async configuration provider pattern: GetOrThrowAsync<T> and GetOrDefaultAsync<T>. Not the same as Microsoft.Extensions.Configuration.IConfigurationProvider.
ConfigurationProvider.csConfigurationProviderAbstract base that implements the NuGet-specific IConfigurationProvider by delegating to a protected abstract Get(key) method and handling conversion and exception normalization.
ConfigurationKeyAttribute.csConfigurationKeyAttributeProperty-level attribute. Overrides the configuration key used by ConfigurationFactory for that property.
ConfigurationKeyPrefixAttribute.csConfigurationKeyPrefixAttributeClass- or property-level attribute. Specifies a prefix prepended to configuration keys. Class-level prefix applies to all properties; property-level prefix overrides the class-level one for that property only.
ConfigurationNullOrEmptyException.csConfigurationNullOrEmptyExceptionTyped exception thrown by ConfigurationProvider.GetOrThrowAsync when a found key maps to a null or empty value.
SecretDictionary.csSecretDictionaryIDictionary<string, string> decorator that injects secrets via ISecretInjector on every read access. Supports an optional exclusion set of keys that should be returned verbatim.
DictionaryExtensions.csDictionaryExtensionsExtension methods on IDictionary<string, string>: GetOrDefault<T> and GetOrThrow<T>, both using ConfigurationUtility.ConvertFromString for type conversion.
StringArrayConverter.csStringArrayConverterArrayConverter subclass that splits a semicolon-delimited string into a string[], trimming each element and discarding empty entries. Used with TypeDescriptor for properties typed as string[].
ConfigurationExtensions.csConfigurationExtensionsExtension method GetTokenCredential on IConfiguration. Returns DefaultAzureCredential in DEBUG builds and ManagedIdentityCredential in release builds, reading the client ID from ManagedIdentityClientId.
Constants.csConstantsString constants for all well-known configuration key names: KeyVault_VaultName, KeyVault_UseManagedIdentity, KeyVault_ClientId, KeyVault_CertificateThumbprint, ManagedIdentityClientId, Local_Development, and storage-related identity keys.

Dependencies

NuGet Package References

PackagePurpose
Microsoft.Extensions.Configuration.AbstractionsIConfiguration, IConfigurationBuilder, IConfigurationSource, IConfigurationProvider, IConfigurationSection interfaces
Microsoft.Extensions.Configuration.Binderconfiguration.Bind(settings) used inside ConfigureInjected<T>
Microsoft.Extensions.Configuration.EnvironmentVariablesEnvironmentVariablesConfigurationSource wrapped by KeyVaultEnvironmentVariableInjectingConfigurationSource
Microsoft.Extensions.Configuration.FileExtensionsFile provider resolution used in JSON source setup
Microsoft.Extensions.Configuration.JsonJsonConfigurationSource / JsonConfigurationProvider wrapped by KeyVaultJsonInjectingConfigurationSource
Microsoft.Extensions.OptionsIOptionsSnapshot<T>, IConfigureOptions<T>, ConfigureNamedOptions<T>, used by NonCachingOptionsSnapshot and ConfigureInjected

Internal Project References

ProjectPurpose
NuGet.Services.KeyVaultProvides ISecretInjector, ICachingSecretInjector, ISecretReader, ISecretReaderFactory, ISecretInjector.InjectAsync, KeyVaultConfiguration, KeyVaultReader, SecretInjector, EmptySecretReader, and CertificateUtility

Notable Patterns and Implementation Details

Sync-over-async in KeyVaultInjectingConfigurationProvider.TryGet. Secret injection calls _secretInjector.InjectAsync(...).ConfigureAwait(false).GetAwaiter().GetResult() synchronously. This is a known limitation: Microsoft.Extensions.Configuration providers expose only synchronous TryGet, so there is no async path available at this layer. This can cause thread-pool starvation under high load if Key Vault calls are slow.
Hardcoded injection exclusions. KeyVaultInjectingConfigurationProvider skips secret injection for four specific keys: GalleryDb:ConnectionString, ValidationDb:ConnectionString, SupportRequestDb:ConnectionString, and StatisticsDb:ConnectionString. These are expected to contain raw ADO.NET connection strings that must not be processed as Key Vault references.
Two distinct secret injection lifecycles coexist. KeyVaultInjectingConfigurationProvider (via ConfigurationBuilderExtensions) injects on every TryGet call during the provider pipeline. SecretInjectedConfiguration (via ConfigurationUtility.ConfigureInjected) injects at options-binding time using a caching injector. SecretConfigurationReader injects eagerly at construction and on Reload. Services may use any of these; newer code generally prefers the SecretInjectedConfiguration path.
NonCachingOptionsSnapshot<T> must be registered explicitly. The default IOptionsSnapshot<T> caches the bound options object for the lifetime of the current scope (request). When Key Vault secret rotation is required, the consuming service must replace the default registration with NonCachingOptionsSnapshot<T> using services.Add(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(NonCachingOptionsSnapshot<>))) before services.AddOptions().
Local development mode. When Local_Development is true in configuration, ConfigurationRootSecretReaderFactory passes a localDevelopment: true flag into KeyVaultConfiguration. Combined with ConfigurationExtensions.GetTokenCredential returning DefaultAzureCredential in DEBUG builds, this allows developers to authenticate via their local Azure CLI or Visual Studio credentials instead of a managed identity or certificate.
SecretConfigurationReader.Providers throws NotImplementedException. Any code that tries to enumerate the underlying providers via IConfigurationRoot.Providers on a SecretConfigurationReader instance will receive a NotImplementedException. This is intentional but can cause failures if middleware or libraries inspect the provider list.