Skip to content

AOT-safe auth provider with feature switch and trimmer support#4348

Draft
paulmedynski wants to merge 2 commits into
dev/paul/assembly-signing-corefrom
dev/paul/aot
Draft

AOT-safe auth provider with feature switch and trimmer support#4348
paulmedynski wants to merge 2 commits into
dev/paul/assembly-signing-corefrom
dev/paul/aot

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Make authentication-provider discovery AOT-/trimmer-safe and restructure the provider registry so the reflection-based config/Azure-extension discovery can be trimmed away behind a feature switch.

The reflection that loaded the Azure extension and read app.config is now isolated in a single core-side component, gated by a feature switch, and the provider registry itself was relocated into the shared Abstractions assembly. The public entry point remains SqlAuthenticationProvider.GetProvider() / SetProvider() — no new public type is introduced.

🔗 PR Stack

Part of a 6-PR stack — current PR marked 👉. Indentation shows the branch base.

  • 🏗️ #4382 — Sign CI Package pipeline assemblies & tests · base: main
    • ✂️ 👉 #4348 — AOT-safe auth provider with feature switch & trimmer support
    • 🏷️ #4383 — Rename STRONG_NAME_SIGNINGASSEMBLY_SIGNING
      • 🪵 #4384 — Add Logging test package & CI
      • ☁️ #4385 — Sign Azure extension assembly & tests
      • 🧩 #4386 — Sign Microsoft.SqlServer.Server assembly & CI
flowchart TD
    main([main])
    PR1["🏗️ #4382<br/>signing-core"]
    AOT["✂️ #4348<br/>aot"]
    PR2["🏷️ #4383<br/>rename"]
    PR3["🪵 #4384<br/>logging-tests"]
    PR4["☁️ #4385<br/>azure-signing"]
    PR5["🧩 #4386<br/>sqlserver.server"]
    main --> PR1
    PR1 --> AOT
    PR1 --> PR2
    PR2 --> PR3
    PR2 --> PR4
    PR2 --> PR5
    click PR1 "https://github.com/dotnet/SqlClient/pull/4382" _blank
    click AOT "https://github.com/dotnet/SqlClient/pull/4348" _blank
    click PR2 "https://github.com/dotnet/SqlClient/pull/4383" _blank
    click PR3 "https://github.com/dotnet/SqlClient/pull/4384" _blank
    click PR4 "https://github.com/dotnet/SqlClient/pull/4385" _blank
    click PR5 "https://github.com/dotnet/SqlClient/pull/4386" _blank
    classDef current fill:#1f6feb,stroke:#1f6feb,color:#fff;
    class AOT current;
Loading

Architecture

  • Registry moved into Abstractions. AuthenticationProviderRegistry (internal) now lives in Microsoft.Data.SqlClient.Extensions.Abstractions and backs the public static SqlAuthenticationProvider.GetProvider/SetProvider. The old Abstractions→core reflection bridge (SqlAuthenticationProvider.Internal.cs) is deleted.
  • SqlAuthenticationProviderManager removed. It is replaced by an internal, core-side AuthenticationBootstrapper that performs the (reflection-based) config-driven and Azure-extension provider discovery and seeds the shared registry. It is lazy: AuthenticationBootstrapper.Bootstrap() — invoked once from SqlConnectionInternal.GetFedAuthToken — triggers a run-once Lazy<T> initialization. Constructing a bootstrapper instance directly (used by tests) has no global side effect.
  • Instantiable for testing. Both AuthenticationProviderRegistry and AuthenticationBootstrapper take an injected registry so tests can exercise them against isolated instances; UnitTests is granted InternalsVisibleTo from Abstractions.

AOT / trimming

  • Feature switch: Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery
    • [FeatureSwitchDefinition] on .NET 9+ for linker-integrated trimming.
    • ILLink.Substitutions.xml for .NET 8 trimmer support.
    • When set to false, the reflective LoadAzureExtensionProvider() (and its Assembly.Load / Activator.CreateInstance paths) is trimmed away.
  • Unified ILLink substitutions: a single cross-platform ILLink.Substitutions.xml (embedded on all non-net462 TFMs) carries both the auth-provider feature switch and the UseManagedNetworkingOnWindows entries; the managed-networking property uses a runtime platform guard so the substitution is safe on all platforms.
  • AotCompatibility test app (tools/AotCompatibility/): validates Native AOT publish succeeds and that the trimmer removes the reflection code when the switch is disabled.

Registry behavior

  • Single ConcurrentDictionary<SqlAuthenticationMethod, ProviderEntry> (a readonly record struct carrying the provider + an IsPermanent flag), replacing the previous two-map design. An IsExternalInit polyfill is added so the record compiles on netstandard2.0.
  • App-config ("permanent") providers take precedence: SetProvider refuses to overwrite a permanent provider (and the failure path is driven through AddOrUpdate); SetPermanentProvider is last-in-wins. This matches the previous SqlAuthenticationProviderManager behavior.
  • SetProvider uses add/update factories so BeforeLoad fires on the first registration too (matching the documented contract), and the BeforeLoad/BeforeUnload lifecycle callbacks are routed through a single helper that logs and swallows provider exceptions. Their docs now state they must be idempotent (the concurrent AddOrUpdate factory may run more than once).

Tests

  • Registry tests are in their own AuthenticationProviderRegistryTest.cs, with added coverage for permanent last-in-wins, lifecycle-callback invocation/isolation, and per-method keying.
  • Bootstrapper tests are reorganized by where their preconditions hold:
    • UnitTests asserts (in its ctor) that the Azure extension is absent and covers the Azure-absent + stub-based CreateAzureAuthenticationProvider paths.
    • Azure.Test (the only project that references the Azure extension) covers the real provider: CreateAzureAuthenticationProvider constructor selection (no global state) and the end-to-end bootstrap (global). The serialized collection is renamed to SqlAuthenticationProviderGlobal, and tests that don't mutate global state were freed from it.

Verification

  • dotnet build build.proj succeeds (Project mode) and -p:ReferenceType=Package.
  • Abstractions, UnitTests, and Azure extension test suites pass.
  • net9.0 core build has no trimmer/IL warnings; the feature-switch substitution keeps LoadAzureExtensionProvider trimmable.
  • AOT compatibility tool builds.
  • No remaining references to SqlAuthenticationProviderManager or the Abstractions→core reflection bridge.

Copilot AI review requested due to automatic review settings June 9, 2026 13:56
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Jun 9, 2026
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Jun 9, 2026
@paulmedynski paulmedynski added Public API 🆕 Issues/PRs that introduce new APIs to the driver. Area\Azure Connectivity Use this to tag issues that are related to Azure connectivity. Hotfix 7.0.2 PRs targeting main that should be backported to release/7.0 for the 7.0.2 release. labels Jun 9, 2026
@paulmedynski paulmedynski added this to the 7.1.0-preview2 milestone Jun 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 exposes an AOT-friendly public authentication provider registration surface (SqlAuthenticationProviderManager) and introduces a feature switch to enable trimming away reflection-based Azure extension provider discovery for NativeAOT/trimming scenarios.

Changes:

  • Made SqlAuthenticationProviderManager public with GetProvider/SetProvider APIs and added ApplicationClientId as part of the public surface.
  • Added Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery feature switch with trimmer support ([FeatureSwitchDefinition] on .NET 9+ and ILLink.Substitutions.xml for .NET 8).
  • Added an tools/AotCompatibility test app plus new/updated unit & functional tests covering the new public API and config behavior.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tools/AotCompatibility/README.md Documents the AOT compatibility test app and feature switch usage.
tools/AotCompatibility/Program.cs Implements runtime checks and trimming verification via the ILC map file.
tools/AotCompatibility/Directory.Packages.props Enables CPM for the tool (project-mode, no package versions).
tools/AotCompatibility/Directory.Build.props Prevents inheriting repo-wide build props for the tool.
tools/AotCompatibility/AotCompatibility.csproj Adds the NativeAOT/trimming test app and propagates the feature switch via RuntimeHostConfigurationOption.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs Adds unit tests for the new public manager API and interop with Abstractions.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs Extends default-switch tests to cover the new auth discovery switch.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs Adjusts tests (pragma) and adds validation for reading ApplicationClientId from config.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config Adds applicationClientId attribute to the auth provider config section.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs Adds pragma suppression around newly-obsoleted Abstractions API usage.
src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs Adds helper plumbing to reset the new switch in tests.
src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.xml Adds substitutions for the new feature switch (and retains UseManagedNetworking stubs).
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs Makes the type public and guards reflection-based discovery behind the new switch; adds AOT/trimming annotations.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs Introduces the new switch property and [FeatureSwitchDefinition] for .NET 9+.
src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj Embeds ILLink.Substitutions.xml for all non-net462 TFMs.
src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs Adds the new public API to the reference assembly (without nullable annotations).
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs Updates reflection binding flags to target public manager methods.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs Marks legacy Abstractions APIs obsolete in favor of the new manager APIs.
doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml Adds public XML doc content for the new manager API.
Comments suppressed due to low confidence (1)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs:346

  • SetProvider is now a public API but does not validate the provider argument; passing null currently results in a NullReferenceException. Public APIs should throw ArgumentNullException for null arguments.
        /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/SetProvider/*'/>
        public static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider)
        {
            if (!provider.IsSupported(authenticationMethod))
            {
                throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name);

Comment thread doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml Outdated
Comment thread doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml Outdated
Comment thread tools/AotCompatibility/Program.cs
Comment thread tools/AotCompatibility/Program.cs Outdated
Comment thread tools/AotCompatibility/README.md Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj Outdated
@paulmedynski paulmedynski changed the title feat: AOT-safe auth provider with feature switch and trimmer support AOT-safe auth provider with feature switch and trimmer support Jun 9, 2026
paulmedynski added a commit that referenced this pull request Jun 9, 2026
- Fix SetProvider exception docs: SqlException → NotSupportedException
- Clarify AOT remarks: static ctor uses reflection by default; describe
  how to disable it for full AOT compatibility
- Return non-zero exit code on SqlConnection construction failure
- Replace Directory.GetFiles with EnumerateFiles(IgnoreInaccessible)
- Clarify README: 'the test app default' vs library default
- Add XML summary docs to all new unit test methods
- Remove [Obsolete] from SqlAuthenticationProvider.GetProvider/SetProvider;
  add remarks pointing to SqlAuthenticationProviderManager equivalents
  noting future deprecation and removal
- Remove corresponding #pragma CS0618 suppressions from tests
- Document applicationClientId in test app.config

@paulmedynski paulmedynski left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I decided against the obsoletion.

Copilot AI review requested due to automatic review settings June 9, 2026 17:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 6 comments.

Comment thread tools/AotCompatibility/README.md
paulmedynski added a commit that referenced this pull request Jun 9, 2026
… doc fixes

- Wrap `using System.Diagnostics.CodeAnalysis` in #if NET (unused on net462)
- Gate ILLink substitution tests by TFM; add NETFRAMEWORK negative assertions
- Fix remarks: "on all TFMs" -> "on .NET 8+" (net462 has no trimmer)
- Add -f/-r flags to AotCompatibility README publish examples
@paulmedynski

Copy link
Copy Markdown
Contributor Author

Addressed all actionable review feedback in 11d75ab:

  1. Unused using on net462 (comment): Wrapped using System.Diagnostics.CodeAnalysis; in #if NET since the attributes are only referenced inside that conditional.

  2. Missing using for SqlConnection (comment): No change needed — SqlConnection resolves via parent namespace (Microsoft.Data.SqlClient.UnitTestsMicrosoft.Data.SqlClient). Build confirms clean with no CS0246.

  3. Cross-platform substitution assertion on net462 (comment): Added #if NETFRAMEWORK branch with negative assertion (DoesNotContain) confirming the resource is correctly excluded, matching the csproj condition.

  4. Windows substitution assertion on net462 (comment): Same approach — net462 now asserts DoesNotContain("ILLink.Substitutions.Windows.xml").

  5. "on all TFMs" wording (comment): Reworded to "on .NET 8+" to accurately reflect that net462 has no trimmer support.

  6. Publish commands missing -f/-r (comment): Added explicit -f net9.0 -r linux-x64 (and -r win-x64 for Windows) so the commands match the documented output paths.

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.42%. Comparing base (47f4e52) to head (f02279b).

Files with missing lines Patch % Lines
...icrosoft/Data/SqlClient/LocalAppContextSwitches.cs 63.63% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4348      +/-   ##
==========================================
- Coverage   65.39%   63.42%   -1.98%     
==========================================
  Files         285      280       -5     
  Lines       43331    66217   +22886     
==========================================
+ Hits        28337    41999   +13662     
- Misses      14994    24218    +9224     
Flag Coverage Δ
CI-SqlClient ?
PR-SqlClient-Project 63.42% <77.77%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj
@paulmedynski paulmedynski marked this pull request as ready for review June 10, 2026 11:34
@paulmedynski paulmedynski requested a review from a team as a code owner June 10, 2026 11:34
Copilot AI review requested due to automatic review settings June 10, 2026 11:34
@paulmedynski paulmedynski enabled auto-merge (squash) June 10, 2026 11:34
auto-merge was automatically disabled June 18, 2026 17:22

Pull request was converted to draft

@paulmedynski

Copy link
Copy Markdown
Contributor Author

We have another design approach that we would like to explore, so pausing this PR for the time being.

@paulmedynski paulmedynski removed the Hotfix 7.0.2 PRs targeting main that should be backported to release/7.0 for the 7.0.2 release. label Jun 18, 2026
paulmedynski added a commit that referenced this pull request Jun 19, 2026
- Fix SetProvider exception docs: SqlException → NotSupportedException
- Clarify AOT remarks: static ctor uses reflection by default; describe
  how to disable it for full AOT compatibility
- Return non-zero exit code on SqlConnection construction failure
- Replace Directory.GetFiles with EnumerateFiles(IgnoreInaccessible)
- Clarify README: 'the test app default' vs library default
- Add XML summary docs to all new unit test methods
- Remove [Obsolete] from SqlAuthenticationProvider.GetProvider/SetProvider;
  add remarks pointing to SqlAuthenticationProviderManager equivalents
  noting future deprecation and removal
- Remove corresponding #pragma CS0618 suppressions from tests
- Document applicationClientId in test app.config
paulmedynski added a commit that referenced this pull request Jun 19, 2026
… doc fixes

- Wrap `using System.Diagnostics.CodeAnalysis` in #if NET (unused on net462)
- Gate ILLink substitution tests by TFM; add NETFRAMEWORK negative assertions
- Fix remarks: "on all TFMs" -> "on .NET 8+" (net462 has no trimmer)
- Add -f/-r flags to AotCompatibility README publish examples
paulmedynski added a commit that referenced this pull request Jun 19, 2026
- Rename s_useManagedNetworking → s_useManagedNetworkingOnWindows in
  LocalAppContextSwitchesHelper to match field rename in
  LocalAppContextSwitches (3 sites: ctor, Dispose, property)
- Use parameterless ActiveDirectoryAuthenticationProvider ctor when
  ApplicationClientId is null to preserve built-in default client ID
- Add -f net9.0 -r linux-x64 to AOT README publish-with-reflection example
Copilot AI review requested due to automatic review settings June 19, 2026 17:52
paulmedynski added a commit that referenced this pull request Jun 19, 2026
- Stream ILC map file line-by-line instead of File.ReadAllText to avoid
  memory pressure from large map files (tools/AotCompatibility)
- Add warning when AppContext feature switch is absent (expected in JIT mode)
- Fix stale app.config comment referencing removed public ApplicationClientId
- Move app.config auth provider tests from FunctionalTests to UnitTests
- Add separate token-acquisition test for config-registered provider
- Remove duplicate IsDummySqlAuthenticationProviderSetByDefault test,
  its DummySqlAuthenticationProvider, and app.config from FunctionalTests
- Add internal ApplicationClientId property for test verification

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 45 out of 45 changed files in this pull request and generated 2 comments.

Comment on lines +257 to +264
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/ApplicationClientId/*'/>
public static string? ApplicationClientId => Instance._applicationClientId;
/// <summary>
/// Gets the application client ID read from the app.config configuration section,
/// or <see langword="null"/> if none was configured.
/// </summary>
internal static string? ApplicationClientId => Instance._applicationClientId;

Copilot AI review requested due to automatic review settings June 19, 2026 20:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 64 out of 65 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file
Comments suppressed due to low confidence (2)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs:404

  • LoadAzureExtensionProvider() registers the Azure extension as the default provider by calling _registry.SetProvider(...) for each Active Directory method. Under the new lazy-bootstrap model, this can override a provider that was already registered via SqlAuthenticationProvider.SetProvider() earlier in process startup (before the first federated/AD connection authenticates), changing the previous precedence (user registration should win over “default” providers).

To preserve expected behavior, the bootstrapper should only set these defaults when no provider is currently registered (or introduce an atomic TrySetProviderIfAbsent API on AuthenticationProviderRegistry and use it here).
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs:39

  • PR description says it makes SqlAuthenticationProviderManager public and exposes GetProvider/SetProvider, but the implementation in this diff removes SqlAuthenticationProviderManager entirely and instead introduces an internal AuthenticationBootstrapper + an Abstractions-level AuthenticationProviderRegistry backing SqlAuthenticationProvider.GetProvider/SetProvider. Please update the PR description to reflect the actual public surface and architecture (registry moved into Abstractions; lazy bootstrap in core).

Comment on lines +100 to +104
if (_permanentProviders.Contains(authenticationMethod))
{
SqlClientEventSource.Log.TryTraceEvent(
"AuthenticationProviderRegistry.SetProvider | Failed to add provider {0} because a " +
"permanent provider with type {1} already existed for authentication {2}.",
@paulmedynski paulmedynski changed the base branch from main to dev/paul/assembly-signing-core June 22, 2026 13:07
Gate the reflection-based config/Azure-extension provider discovery behind a
feature switch so the trimmer can remove it, and relocate the provider registry
into the shared Abstractions assembly. The public surface stays
SqlAuthenticationProvider.GetProvider/SetProvider (no new public type).

- Registry: move into Abstractions as internal AuthenticationProviderRegistry
  (one ConcurrentDictionary of a ProviderEntry record struct; IsExternalInit
  polyfill for netstandard2.0); delete the Abstractions->core reflection bridge.
- Bootstrapper: replace SqlAuthenticationProviderManager with an internal,
  core-side AuthenticationBootstrapper that lazily (Lazy<T>) discovers
  config-driven and Azure-extension providers and seeds the registry, triggered
  once from SqlConnectionInternal.GetFedAuthToken.
- AOT: feature switch
  Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery
  ([FeatureSwitchDefinition] on .NET 9+, ILLink.Substitutions.xml on .NET 8)
  trims LoadAzureExtensionProvider when disabled; AotCompatibility tool
  validates Native AOT publish and trimming.
- Behavior: app-config ("permanent") providers take precedence and are
  last-in-wins; BeforeLoad fires on first registration; lifecycle callbacks are
  isolated from exceptions and documented as idempotent.
- Tests: registry and bootstrapper tests reorganized -- UnitTests covers the
  Azure-absent and stub paths, Azure.Test covers the real provider (global vs
  non-global split); serialized collection renamed to SqlAuthenticationProviderGlobal.
the AuthenticationProviderRegistry (internal to this assembly). -->
<ItemGroup Condition="'$(SigningKeyPath)' == ''">
<InternalsVisibleTo Include="$(AssemblyName).Test" />
<InternalsVisibleTo Include="UnitTests" />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Give SqlClient access to Abstractions internals so it can call SetPermanentProvider().

Give UnitTests access to Abstractions internals so it can use non-global isloated provider registry instances.

/// <see cref="SqlAuthenticationProvider.SetProvider"/> accessors.
/// </summary>
/// <remarks>
/// Production code uses this shared instance. Tests can instead construct an isolated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we can apply this pattern to other globals and singletons (like LocalAppContextSwitches) to avoid tests clobbering each other.

// addValueFactory: no provider is registered for this method yet.
(SqlAuthenticationMethod key) =>
{
InvokeProviderCallback(provider, provider.BeforeLoad, key, nameof(SqlAuthenticationProvider.BeforeLoad));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The old Manager SetProvider() wasn't calling BeforeLoad when a provider was being registered for the first time against a particular method. This was a bug - the docs say BeforeLoad is always called before registering a provider, regardless of whether or not it was new or a replacement.

The old code also wasn't protecting against BeforeLoad/Unload callbacks that threw, which would abort the AddOrUpdate(). Now we catch-and-ignore in these Invoke() helpers, preventing misbehaving providers from perturbing the registry. This was also a bug.

/// Callers are responsible for verifying that the provider supports the authentication method
/// before registering it.
/// <para>
/// This is a last-in-wins operation: a later <see cref="SetPermanentProvider"/> call for the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This behaviour isn't documented anywhere public, but it is preserved from the original Manager implementation.

/// (used by records and init-only properties). It is provided by the BCL on .NET, but not on
/// the netstandard2.0 / .NET Framework targets this assembly supports, so we define it here.
/// </summary>
internal static class IsExternalInit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This lets the C# 14 compiler write the ProviderEntry getters/setters properly when targeting .NET Standard 2.0.

@@ -432,55 +509,10 @@ private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationS
return null;
}

/// <summary>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Deleted entirely - now lives in Abstractions.

@@ -57,8 +57,9 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable
private readonly bool? _useOverallConnectTimeoutForPoolWaitOriginal;
#if NET && _WINDOWS

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note that these _WINDOWS conditions are never set in tests, so these branches are currently never compiled or tested. That is tracked elsewhere to be fixed.

/// that configuration source.
/// </summary>
[ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))]
public async Task IsDummySqlAuthenticationProviderSetByDefault()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replaced by Bootstrapper tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@paulmedynski - Remove this before we merge the PR.

@paulmedynski paulmedynski moved this from In review to In progress in SqlClient Board Jun 22, 2026
… tests

- AuthenticationBootstrapper: replace backing fields with internal
  auto-properties (Registry, ApplicationClientId, UseWamBroker).
- AuthenticationProviderRegistry: remove PermanentProviderException
  sentinel; document SetProvider null behavior.
- UnitTests: assert Registry/ApplicationClientId/UseWamBroker; add
  UseWamBroker config tests (app.config sets useWamBroker=true).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 54 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file
Comments suppressed due to low confidence (1)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs:392

  • This comment is now misleading: AuthenticationProviderRegistry.SetProvider will override any existing non-permanent provider, and only refuses to replace permanent (app.config) providers. As written, it suggests defaults apply only when no provider exists, which is not accurate.

Comment on lines +145 to +169
if (!reflectionEnabled && hasLoadAzure)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(" FAIL: Reflection code was NOT trimmed!");
Console.ResetColor();
return 1;
}
else if (!reflectionEnabled && !hasLoadAzure)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(" PASS: Reflection code was successfully trimmed.");
Console.ResetColor();
}
else if (reflectionEnabled && hasLoadAzure)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(" PASS: Reflection code is present (as expected with discovery enabled).");
Console.ResetColor();
}
else
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(" WARN: Reflection code absent despite discovery being enabled.");
Console.ResetColor();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Azure Connectivity Use this to tag issues that are related to Azure connectivity. Public API 🆕 Issues/PRs that introduce new APIs to the driver.

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Entra ID authentication broken under NativeAOT in v7.0 — Extensions.Azure reflection-based discovery is AOT-incompatible

5 participants