Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<docs>
<members name="SqlAuthenticationProviderManager">
<SqlAuthenticationProviderManager>
<summary>
Manages authentication provider registration for SQL Server connections.
Provides methods to get and set <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> instances
for specific <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationMethod" /> values.
</summary>
<remarks>
<para>
The <see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod)" />
and <see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod,Microsoft.Data.SqlClient.SqlAuthenticationProvider)" />
methods are AOT-safe because they do not use reflection — providers are registered
via direct static method calls.
</para>
<para>
By default, the manager's static constructor performs reflection-based discovery of the
Azure authentication extension assembly. This is safe for JIT applications but incompatible
with NativeAOT trimming. To disable this discovery and make the manager fully AOT-compatible,
set the <c>Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery</c>
runtime configuration option to <see langword="false" /> at publish time, and register
providers explicitly using
<see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod,Microsoft.Data.SqlClient.SqlAuthenticationProvider)" />.
</para>
</remarks>
</SqlAuthenticationProviderManager>
<GetProvider>
<summary>
Gets the authentication provider registered for the specified authentication method.
</summary>
<param name="authenticationMethod">The authentication method to look up.</param>
<returns>
The registered <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> for the
specified method, or <see langword="null" /> if no provider is registered.
</returns>
</GetProvider>
<SetProvider>
<summary>
Registers an authentication provider for the specified authentication method.
</summary>
<param name="authenticationMethod">The authentication method to register a provider for.</param>
<param name="provider">The authentication provider to register.</param>
<returns>
<see langword="true" /> if the provider was registered successfully;
<see langword="false" /> if the authentication method has already been claimed
via application configuration and cannot be overridden.
</returns>
<exception cref="T:System.NotSupportedException">
Thrown if the provider does not support the specified authentication method.
</exception>
<remarks>
<para>
Providers registered via application configuration (app.config) cannot be replaced
by calling this method. In that case, this method returns <see langword="false" />.
</para>
<para>
If a provider was previously registered for the same method (not via app.config),
it will be replaced and its <see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProvider.BeforeUnload(Microsoft.Data.SqlClient.SqlAuthenticationMethod)" />
method will be called before the new provider's
<see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProvider.BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod)" />
method is invoked.
</para>
</remarks>
<example>
The following example demonstrates registering a provider for AOT applications.
Use the default constructor to authenticate with the built-in first-party application client ID:
<code language="c#">
// Uses the built-in 1st-party application client ID.
var provider = new ActiveDirectoryAuthenticationProvider();

SqlAuthenticationProviderManager.SetProvider(
SqlAuthenticationMethod.ActiveDirectoryDefault, provider);
</code>
Or supply a custom application client ID registered in your Entra ID tenant:
<code language="c#">
// Uses a custom application client ID.
var provider = new ActiveDirectoryAuthenticationProvider("your-app-client-id");

SqlAuthenticationProviderManager.SetProvider(
SqlAuthenticationMethod.ActiveDirectoryDefault, provider);
</code>
</example>
</SetProvider>
</members>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ See the LICENSE file in the project root for more information.
<summary>Gets an authentication provider by method.</summary>
<param name="authenticationMethod">The authentication method.</param>
<returns>The authentication provider or <see langword="null" /> if not found.</returns>
<remarks>
This method is provided for backward compatibility only. New code should use
<see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod)" />
instead. This method will be deprecated in a future version and subsequently removed.
</remarks>
</GetProvider>
<SetProvider>
<summary>Sets an authentication provider by method.</summary>
Expand All @@ -138,6 +143,11 @@ See the LICENSE file in the project root for more information.
<returns>
<see langword="true" /> if the operation succeeded; otherwise, <see langword="false" /> (for example, the existing provider disallows overriding).
</returns>
<remarks>
This method is provided for backward compatibility only. New code should use
<see cref="M:Microsoft.Data.SqlClient.SqlAuthenticationProviderManager.SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod,Microsoft.Data.SqlClient.SqlAuthenticationProvider)" />
instead. This method will be deprecated in a future version and subsequently removed.
</remarks>
</SetProvider>
</members>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static Internal()
// Get handles to the get/set static methods.
_getProvider = manager.GetMethod(
"GetProvider",
BindingFlags.NonPublic | BindingFlags.Static);
BindingFlags.Public | BindingFlags.Static);

if (_getProvider is null)
{
Expand All @@ -81,7 +81,7 @@ static Internal()

_setProvider = manager.GetMethod(
"SetProvider",
BindingFlags.NonPublic | BindingFlags.Static);
BindingFlags.Public | BindingFlags.Static);

if (_setProvider is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,13 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) {


/// <include file='../doc/SqlAuthenticationProvider.xml' path='docs/members[@name="SqlAuthenticationProvider"]/GetProvider/*'/>
//
// We would like to deprecate this method in favour of
// SqlAuthenticationProviderManager.GetProvider().
//
public static SqlAuthenticationProvider? GetProvider(
SqlAuthenticationMethod authenticationMethod)
{
return Internal.GetProvider(authenticationMethod);
}

/// <include file='../doc/SqlAuthenticationProvider.xml' path='docs/members[@name="SqlAuthenticationProvider"]/SetProvider/*'/>
//
// We would like to deprecate this method in favour of
// SqlAuthenticationProviderManager.SetProvider().
//
public static bool SetProvider(
SqlAuthenticationMethod authenticationMethod,
SqlAuthenticationProvider provider)
Expand Down
14 changes: 14 additions & 0 deletions src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// NOTE: This ref assembly intentionally does not use #nullable annotations.
// The implementation source uses nullable (e.g. string?, SqlAuthenticationProvider?)
// but the ref/notsupported projects omit them for consistency with the existing
// codebase convention and to avoid GenAPI nullable attribute complications.

namespace Microsoft.Data.SqlClient;

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/ApplicationIntent.xml' path='docs/members[@name="ApplicationIntent"]/ApplicationIntent/*'/>
Expand Down Expand Up @@ -47,6 +52,15 @@ protected SqlAuthenticationInitializer() { }
public abstract void Initialize();
}

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/SqlAuthenticationProviderManager/*'/>
public sealed class SqlAuthenticationProviderManager
{
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/GetProvider/*'/>
public static Microsoft.Data.SqlClient.SqlAuthenticationProvider GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { throw null; }
/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/SetProvider/*'/>
public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; }
}
Comment thread
paulmedynski marked this conversation as resolved.

/// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml' path='docs/members[@name="SqlBatch"]/SqlBatch/*'/>
#if NET
public class SqlBatch : System.Data.Common.DbBatch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,14 @@

<!-- Embedded resources ============================================== -->
<ItemGroup>
<!-- Linker directives to replace UseManagedNetworking with a constant if consumer specifies -->
<!-- the app context switch in their csproj. This file only applies to netcore on windows. -->
<!-- This file does not support pre-processor directives, so it must be conditionally -->
<!-- included into the build. -->
<!-- Cross-platform linker substitution: auth provider feature switch and -->
<!-- UseManagedNetworkingOnWindows feature switch. The UseManagedNetworking property -->
<!-- includes a platform guard (!OperatingSystem.IsWindows()) so the substitution of -->
<!-- UseManagedNetworkingOnWindows is safe on all platforms. -->
<!-- Excluded from net462 (no trimmer support). -->
<EmbeddedResource Include="Resources\ILLink.Substitutions.xml"
Condition="'$(NormalizedTargetOs)' == 'windows_nt' AND '$(TargetFramework)' != 'net462'" />
LogicalName="ILLink.Substitutions.xml"
Condition="'$(TargetFramework)' != 'net462'" />
Comment thread
paulmedynski marked this conversation as resolved.
Comment thread
paulmedynski marked this conversation as resolved.

<!-- Used by SqlMetaDataFactory to construct its DataSet -->
<EmbeddedResource Include="Resources\Microsoft.Data.SqlClient.SqlMetaData.xml">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ internal static class LocalAppContextSwitches
private const string UseOverallConnectTimeoutForPoolWaitString =
"Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait";

#if NET && _WINDOWS
#if NET
/// <summary>
/// The name of the app context switch that controls whether to use the
/// managed SNI implementation instead of the native SNI implementation on
Expand All @@ -148,6 +148,14 @@ internal static class LocalAppContextSwitches
/// </summary>
private const string UseMinimumLoginTimeoutString =
"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin";

/// <summary>
/// The name of the app context switch that controls whether
/// SqlAuthenticationProviderManager uses reflection to discover and load
/// the Azure extension authentication provider at startup.
/// </summary>
internal const string EnableReflectionBasedAuthenticationProviderDiscoveryString =
"Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery";

#endregion

Expand Down Expand Up @@ -246,18 +254,23 @@ private enum SwitchValue : byte
/// </summary>
private static SwitchValue s_useOverallConnectTimeoutForPoolWait = SwitchValue.None;

#if NET && _WINDOWS
#if NET
/// <summary>
/// The cached value of the UseManagedNetworking switch.
/// The cached value of the UseManagedNetworkingOnWindows switch.
/// </summary>
private static SwitchValue s_useManagedNetworking = SwitchValue.None;
private static SwitchValue s_useManagedNetworkingOnWindows = SwitchValue.None;
#endif

/// <summary>
/// The cached value of the UseMinimumLoginTimeout switch.
/// </summary>
private static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None;

/// <summary>
/// The cached value of the EnableReflectionBasedAuthenticationProviderDiscovery switch.
/// </summary>
private static SwitchValue s_enableReflectionBasedAuthenticationProviderDiscovery = SwitchValue.None;

#endregion

#region Static Initialization
Expand Down Expand Up @@ -590,51 +603,47 @@ public static bool UseCompatibilityAsyncBehaviour
defaultValue: false,
ref s_useOverallConnectTimeoutForPoolWait);

#if NET && _WINDOWS
#if NET
/// <summary>
/// When set to true, .NET on Windows will use the managed SNI
/// implementation instead of the native SNI implementation.
/// Returns whether to use the managed SNI implementation. On non-Windows
/// platforms this always returns <see langword="true"/> (native SNI is not
/// available). On Windows it delegates to the
/// <c>Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows</c>
/// AppContext switch (default <see langword="false"/>).
///
/// ILLink.Substitutions.xml allows the unused SNI implementation to be
/// trimmed away when the corresponding AppContext switch is set at compile
/// time. In such cases, this property will return a constant value, even if
/// the AppContext switch is set or reset at runtime. See the
/// ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml
/// resource files for details.
///
/// The default value of this switch is false.
/// trimmed away when the AppContext switch is set at publish time. The
/// trimmer substitutes <see cref="UseManagedNetworkingOnWindows"/> which is
/// guarded by the platform check here, so the substitution is safe on all
/// platforms.
/// </summary>
public static bool UseManagedNetworking =>
!OperatingSystem.IsWindows() || UseManagedNetworkingOnWindows;

/// <summary>
/// Returns the value of the UseManagedNetworkingOnWindows AppContext switch.
/// This property is the trimmer substitution target — callers should use
/// <see cref="UseManagedNetworking"/> which includes the platform guard.
/// </summary>
public static bool UseManagedNetworking
private static bool UseManagedNetworkingOnWindows
{
get
{
if (s_useManagedNetworking != SwitchValue.None)
if (s_useManagedNetworkingOnWindows != SwitchValue.None)
{
return s_useManagedNetworking == SwitchValue.True;
}

if (!OperatingSystem.IsWindows())
{
s_useManagedNetworking = SwitchValue.True;
return true;
return s_useManagedNetworkingOnWindows == SwitchValue.True;
}

if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue)
{
s_useManagedNetworking = SwitchValue.True;
s_useManagedNetworkingOnWindows = SwitchValue.True;
return true;
}

s_useManagedNetworking = SwitchValue.False;
s_useManagedNetworkingOnWindows = SwitchValue.False;
return false;
}
}
#elif NET
/// <summary>
/// .NET Core on Unix does not support native SNI, so this will always be
/// true.
/// </summary>
public static bool UseManagedNetworking => true;
#else
/// <summary>
/// .NET Framework does not support the managed SNI, so this will always be
Expand All @@ -655,6 +664,58 @@ public static bool UseManagedNetworking
defaultValue: true,
ref s_useMinimumLoginTimeout);

/// <summary>
/// When set to true (the default), SqlAuthenticationProviderManager will
/// use reflection (Assembly.Load + Activator.CreateInstance) to discover
/// and load the Azure extension authentication provider at startup.
///
/// AOT applications should set this to false and register providers
/// explicitly via SqlAuthenticationProviderManager.SetProvider().
///
/// The default value of this switch is true.
/// </summary>
/// <remarks>
/// <para>
/// This switch can be consumed in two ways:
/// </para>
/// <para>
/// <b>1. Runtime AppContext switch (non-AOT apps):</b>
/// Set via <c>AppContext.SetSwitch("Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery", false)</c>
/// or in <c>runtimeconfig.json</c>. Must be set before the first <c>SqlConnection</c> is opened
/// (i.e. before <c>SqlAuthenticationProviderManager</c>'s static constructor runs).
/// The reflection code remains present in the assembly but is not called.
/// </para>
/// <para>
/// <b>2. Trimmer/AOT feature switch (published AOT apps):</b>
/// Set in the consuming app's .csproj:
/// <code>
/// &lt;RuntimeHostConfigurationOption
/// Include="Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery"
/// Value="false"
/// Trim="true" /&gt;
/// </code>
/// At publish time, the trimmer substitutes this property with constant <c>false</c>
/// (via <c>[FeatureSwitchDefinition]</c> on .NET 9+ and <c>ILLink.Substitutions.xml</c>
/// on .NET 8+). The dead <c>if (false)</c> branch is eliminated, and all unreachable
/// reflection code (<c>Assembly.Load</c>, <c>Activator.CreateInstance</c>, exception
/// filters) is removed from the final binary.
/// </para>
/// <para>
/// The two approaches differ in that the runtime switch leaves reflection IL in the
/// binary (just skips calling it), while the trimmer switch physically removes the
/// code — eliminating AOT warnings and reducing binary size.
/// </para>
/// </remarks>
#if NET9_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.FeatureSwitchDefinition(
EnableReflectionBasedAuthenticationProviderDiscoveryString)]
#endif
internal static bool EnableReflectionBasedAuthenticationProviderDiscovery =>
AcquireAndReturn(
EnableReflectionBasedAuthenticationProviderDiscoveryString,
defaultValue: true,
ref s_enableReflectionBasedAuthenticationProviderDiscovery);

#endregion

#region Helpers
Expand Down
Loading
Loading