diff --git a/.github/workflows/aot-check.yml b/.github/workflows/aot-check.yml index 2e81c1969..b0427b167 100644 --- a/.github/workflows/aot-check.yml +++ b/.github/workflows/aot-check.yml @@ -29,5 +29,5 @@ jobs: - name: Runs powershell script id: aot-powershell - run: build\test-aot.ps1 'net8.0' + run: build\test-aot.ps1 'net9.0' diff --git a/Directory.Build.props b/Directory.Build.props index b6448fd88..ae43d78fd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - 3.8.5 + 3.9.1 $(MicrosoftIdentityWebVersion) @@ -95,7 +95,7 @@ 4.6.0 4.36.0 4.57.0-preview - 9.0.0 + 9.1.0 8.0.5 diff --git a/build/test-aot.ps1 b/build/test-aot.ps1 index 99e02bb5f..faee0c9a1 100644 --- a/build/test-aot.ps1 +++ b/build/test-aot.ps1 @@ -16,7 +16,7 @@ foreach ($line in $($publishOutput -split "`r`n")) } Write-Host "Actual warning count is: ", $actualWarningCount -$expectedWarningCount = 65 +$expectedWarningCount = 50 if ($LastExitCode -ne 0) { diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs index 84022a689..19d8863e1 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.cs @@ -19,6 +19,11 @@ namespace Microsoft.Identity.Web internal partial class DownstreamApi : IDownstreamApi { /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task GetForUserAsync( string? serviceName, Action? downstreamApiOptionsOverride = null, @@ -46,6 +51,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task GetForUserAsync( string? serviceName, TInput input, @@ -82,6 +92,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task GetForAppAsync( string? serviceName, Action? downstreamApiOptionsOverride = null, @@ -108,6 +123,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task GetForAppAsync( string? serviceName, TInput input, @@ -143,6 +163,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PostForUserAsync( string? serviceName, TInput input, @@ -177,6 +202,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PostForUserAsync( string? serviceName, TInput input, @@ -213,6 +243,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PostForAppAsync( string? serviceName, TInput input, @@ -246,6 +281,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PostForAppAsync( string? serviceName, TInput input, @@ -281,6 +321,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PutForUserAsync( string? serviceName, TInput input, @@ -315,6 +360,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PutForUserAsync( string? serviceName, TInput input, @@ -351,6 +401,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PutForAppAsync( string? serviceName, TInput input, @@ -384,6 +439,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PutForAppAsync( string? serviceName, TInput input, @@ -421,6 +481,11 @@ ex is InvalidOperationException #if !NETFRAMEWORK && !NETSTANDARD2_0 /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PatchForUserAsync( string? serviceName, TInput input, @@ -455,6 +520,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PatchForUserAsync( string? serviceName, TInput input, @@ -491,6 +561,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PatchForAppAsync( string? serviceName, TInput input, @@ -524,6 +599,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task PatchForAppAsync( string? serviceName, TInput input, @@ -561,6 +641,11 @@ ex is InvalidOperationException #endif // !NETFRAMEWORK && !NETSTANDARD2_0 /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task DeleteForUserAsync( string? serviceName, TInput input, @@ -595,6 +680,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task DeleteForUserAsync( string? serviceName, TInput input, @@ -631,6 +721,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task DeleteForAppAsync( string? serviceName, TInput input, @@ -664,6 +759,11 @@ ex is InvalidOperationException } /// + +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif public async Task DeleteForAppAsync( string? serviceName, TInput input, @@ -701,6 +801,7 @@ ex is InvalidOperationException #if NET8_0_OR_GREATER /// + public async Task GetForUserAsync( string? serviceName, JsonTypeInfo outputJsonTypeInfo, @@ -729,6 +830,7 @@ ex is InvalidOperationException } /// + public async Task GetForUserAsync( string? serviceName, TInput input, @@ -767,6 +869,7 @@ ex is InvalidOperationException } /// + public async Task GetForAppAsync( string? serviceName, JsonTypeInfo outputJsonTypeInfo, @@ -794,6 +897,7 @@ ex is InvalidOperationException } /// + public async Task GetForAppAsync( string? serviceName, TInput input, @@ -831,6 +935,7 @@ ex is InvalidOperationException } /// + public async Task PostForUserAsync( string? serviceName, TInput input, @@ -866,6 +971,7 @@ ex is InvalidOperationException } /// + public async Task PostForUserAsync( string? serviceName, TInput input, @@ -904,6 +1010,7 @@ ex is InvalidOperationException } /// + public async Task PostForAppAsync( string? serviceName, TInput input, @@ -938,6 +1045,7 @@ ex is InvalidOperationException } /// + public async Task PostForAppAsync( string? serviceName, TInput input, @@ -975,6 +1083,7 @@ ex is InvalidOperationException } /// + public async Task PutForUserAsync( string? serviceName, TInput input, @@ -1010,6 +1119,7 @@ ex is InvalidOperationException } /// + public async Task PutForUserAsync( string? serviceName, TInput input, @@ -1048,6 +1158,7 @@ ex is InvalidOperationException } /// + public async Task PutForAppAsync( string? serviceName, TInput input, @@ -1082,6 +1193,7 @@ ex is InvalidOperationException } /// + public async Task PutForAppAsync( string? serviceName, TInput input, @@ -1119,6 +1231,7 @@ ex is InvalidOperationException } /// + public async Task PatchForUserAsync( string? serviceName, TInput input, @@ -1154,6 +1267,7 @@ ex is InvalidOperationException } /// + public async Task PatchForUserAsync( string? serviceName, TInput input, @@ -1192,6 +1306,7 @@ ex is InvalidOperationException } /// + public async Task PatchForAppAsync( string? serviceName, TInput input, @@ -1226,6 +1341,7 @@ ex is InvalidOperationException } /// + public async Task PatchForAppAsync( string? serviceName, TInput input, @@ -1263,6 +1379,7 @@ ex is InvalidOperationException } /// + public async Task DeleteForUserAsync( string? serviceName, TInput input, @@ -1298,6 +1415,7 @@ ex is InvalidOperationException } /// + public async Task DeleteForUserAsync( string? serviceName, TInput input, @@ -1336,6 +1454,7 @@ ex is InvalidOperationException } /// + public async Task DeleteForAppAsync( string? serviceName, TInput input, @@ -1370,6 +1489,7 @@ ex is InvalidOperationException } /// + public async Task DeleteForAppAsync( string? serviceName, TInput input, diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt index c57431c99..44ff93ac3 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.HttpMethods.tt @@ -70,15 +70,22 @@ namespace Microsoft.Identity.Web firstMethod = false; #> /// + +<# if ((hasInput || hasOutput) && (framework != "net8" && framework != "net9")){ #> +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif +<# } #> public async <#= returnType #> <#= httpMethod #>For<#= token #>Async<#= template #>( string? serviceName, <# if (hasInput){ #> TInput input, <# } #> -<# if (hasInput && framework == "net8"){ #> +<# if (hasInput && (framework == "net8" || framework == "net9")){ #> JsonTypeInfo inputJsonTypeInfo, <# } #> -<# if (hasOutput && framework == "net8"){ #> +<# if (hasOutput && (framework == "net8" || framework == "net9")){ #> JsonTypeInfo outputJsonTypeInfo, <# } #> Action? downstreamApiOptionsOverride = null, diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs index eb498f990..a1bd51c4e 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs @@ -1,177 +1,194 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Runtime.CompilerServices; -using System.Security.Claims; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Identity.Abstractions; -using Microsoft.Identity.Client; - -namespace Microsoft.Identity.Web -{ - /// - internal partial class DownstreamApi : IDownstreamApi - { - private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider; - private readonly IHttpClientFactory _httpClientFactory; - private readonly IOptionsMonitor _namedDownstreamApiOptions; - private const string Authorization = "Authorization"; - protected readonly ILogger _logger; - private const string AuthSchemeDstsSamlBearer = "http://schemas.microsoft.com/dsts/saml2-bearer"; - - /// - /// Constructor. - /// - /// Authorization header provider. - /// Named options provider. - /// HTTP client factory. - /// Logger. - public DownstreamApi( - IAuthorizationHeaderProvider authorizationHeaderProvider, - IOptionsMonitor namedDownstreamApiOptions, - IHttpClientFactory httpClientFactory, - ILogger logger) - { - _authorizationHeaderProvider = authorizationHeaderProvider; - _namedDownstreamApiOptions = namedDownstreamApiOptions; - _httpClientFactory = httpClientFactory; - _logger = logger; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task CallApiAsync( - string? serviceName, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = null, - HttpContent? content = null, - CancellationToken cancellationToken = default) - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - return CallApiInternalAsync(serviceName, effectiveOptions, effectiveOptions.RequestAppToken, content, - user, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task CallApiAsync( - DownstreamApiOptions downstreamApiOptions, - ClaimsPrincipal? user = null, - HttpContent? content = null, - CancellationToken cancellationToken = default) - { - return CallApiInternalAsync(null, downstreamApiOptions, downstreamApiOptions.RequestAppToken, content, - user, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task CallApiForUserAsync( - string? serviceName, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = null, - HttpContent? content = null, - CancellationToken cancellationToken = default) - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - return CallApiInternalAsync(serviceName, effectiveOptions, false, content, user, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task CallApiForAppAsync( - string? serviceName, - Action? downstreamApiOptionsOverride = null, - HttpContent? content = null, - CancellationToken cancellationToken = default) - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - return CallApiInternalAsync(serviceName, effectiveOptions, true, content, null, cancellationToken); - } - - /// - public async Task CallApiForUserAsync( - string? serviceName, - TInput input, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = null, - CancellationToken cancellationToken = default) where TOutput : class - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpContent? effectiveInput = SerializeInput(input, effectiveOptions); - - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, - effectiveInput, user, cancellationToken).ConfigureAwait(false); - - // Only dispose the HttpContent if was created here, not provided by the caller. - if (input is not HttpContent) - { - effectiveInput?.Dispose(); - } - - return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async Task CallApiForAppAsync( - string? serviceName, - TInput input, - Action? downstreamApiOptionsOverride = null, - CancellationToken cancellationToken = default) where TOutput : class - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpContent? effectiveInput = SerializeInput(input, effectiveOptions); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, - effectiveInput, null, cancellationToken).ConfigureAwait(false); - - // Only dispose the HttpContent if was created here, not provided by the caller. - if (input is not HttpContent) - { - effectiveInput?.Dispose(); - } - - return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async Task CallApiForAppAsync(string serviceName, - Action? downstreamApiOptionsOverride = null, - CancellationToken cancellationToken = default) where TOutput : class - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, - null, null, cancellationToken).ConfigureAwait(false); - - return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); - } - - /// - public async Task CallApiForUserAsync( - string? serviceName, - Action? downstreamApiOptionsOverride = null, - ClaimsPrincipal? user = null, - CancellationToken cancellationToken = default) where TOutput : class - { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, - null, user, cancellationToken).ConfigureAwait(false); - return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Security.Claims; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Identity.Abstractions; +using Microsoft.Identity.Client; + +namespace Microsoft.Identity.Web +{ + /// + internal partial class DownstreamApi : IDownstreamApi + { + private readonly IAuthorizationHeaderProvider _authorizationHeaderProvider; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOptionsMonitor _namedDownstreamApiOptions; + private const string Authorization = "Authorization"; + protected readonly ILogger _logger; + private const string AuthSchemeDstsSamlBearer = "http://schemas.microsoft.com/dsts/saml2-bearer"; + + /// + /// Constructor. + /// + /// Authorization header provider. + /// Named options provider. + /// HTTP client factory. + /// Logger. + public DownstreamApi( + IAuthorizationHeaderProvider authorizationHeaderProvider, + IOptionsMonitor namedDownstreamApiOptions, + IHttpClientFactory httpClientFactory, + ILogger logger) + { + _authorizationHeaderProvider = authorizationHeaderProvider; + _namedDownstreamApiOptions = namedDownstreamApiOptions; + _httpClientFactory = httpClientFactory; + _logger = logger; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task CallApiAsync( + string? serviceName, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = null, + HttpContent? content = null, + CancellationToken cancellationToken = default) + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + return CallApiInternalAsync(serviceName, effectiveOptions, effectiveOptions.RequestAppToken, content, + user, cancellationToken); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task CallApiAsync( + DownstreamApiOptions downstreamApiOptions, + ClaimsPrincipal? user = null, + HttpContent? content = null, + CancellationToken cancellationToken = default) + { + return CallApiInternalAsync(null, downstreamApiOptions, downstreamApiOptions.RequestAppToken, content, + user, cancellationToken); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task CallApiForUserAsync( + string? serviceName, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = null, + HttpContent? content = null, + CancellationToken cancellationToken = default) + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + return CallApiInternalAsync(serviceName, effectiveOptions, false, content, user, cancellationToken); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task CallApiForAppAsync( + string? serviceName, + Action? downstreamApiOptionsOverride = null, + HttpContent? content = null, + CancellationToken cancellationToken = default) + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + return CallApiInternalAsync(serviceName, effectiveOptions, true, content, null, cancellationToken); } -#if NET8_0_OR_GREATER + /// +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + public async Task CallApiForUserAsync( + string? serviceName, + TInput input, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = null, + CancellationToken cancellationToken = default) where TOutput : class + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpContent? effectiveInput = SerializeInput(input, effectiveOptions); + + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, + effectiveInput, user, cancellationToken).ConfigureAwait(false); + + // Only dispose the HttpContent if was created here, not provided by the caller. + if (input is not HttpContent) + { + effectiveInput?.Dispose(); + } + + return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); + } + + /// +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async Task CallApiForAppAsync( + string? serviceName, + TInput input, + Action? downstreamApiOptionsOverride = null, + CancellationToken cancellationToken = default) where TOutput : class + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpContent? effectiveInput = SerializeInput(input, effectiveOptions); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, + effectiveInput, null, cancellationToken).ConfigureAwait(false); + + // Only dispose the HttpContent if was created here, not provided by the caller. + if (input is not HttpContent) + { + effectiveInput?.Dispose(); + } + + return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); + } + + /// +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async Task CallApiForAppAsync(string serviceName, + Action? downstreamApiOptionsOverride = null, + CancellationToken cancellationToken = default) where TOutput : class + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, + null, null, cancellationToken).ConfigureAwait(false); + + return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); + } + + /// +#if NET8_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + public async Task CallApiForUserAsync( + string? serviceName, + Action? downstreamApiOptionsOverride = null, + ClaimsPrincipal? user = null, + CancellationToken cancellationToken = default) where TOutput : class + { + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, + null, user, cancellationToken).ConfigureAwait(false); + return await DeserializeOutputAsync(response, effectiveOptions).ConfigureAwait(false); + } + +#if NET8_0_OR_GREATER /// public async Task CallApiForUserAsync( string? serviceName, @@ -183,19 +200,19 @@ public Task CallApiForAppAsync( CancellationToken cancellationToken = default) where TOutput : class { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); - - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, - effectiveInput, user, cancellationToken).ConfigureAwait(false); - - // Only dispose the HttpContent if was created here, not provided by the caller. - if (input is not HttpContent) - { - effectiveInput?.Dispose(); - } - - return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); + + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, + effectiveInput, user, cancellationToken).ConfigureAwait(false); + + // Only dispose the HttpContent if was created here, not provided by the caller. + if (input is not HttpContent) + { + effectiveInput?.Dispose(); + } + + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); } /// @@ -207,9 +224,9 @@ public Task CallApiForAppAsync( CancellationToken cancellationToken = default) where TOutput : class { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, - null, user, cancellationToken).ConfigureAwait(false); + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, false, + null, user, cancellationToken).ConfigureAwait(false); return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); } @@ -223,17 +240,17 @@ public Task CallApiForAppAsync( CancellationToken cancellationToken = default) where TOutput : class { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, - effectiveInput, null, cancellationToken).ConfigureAwait(false); - - // Only dispose the HttpContent if was created here, not provided by the caller. - if (input is not HttpContent) - { - effectiveInput?.Dispose(); - } - + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpContent? effectiveInput = SerializeInput(input, effectiveOptions, inputJsonTypeInfo); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, + effectiveInput, null, cancellationToken).ConfigureAwait(false); + + // Only dispose the HttpContent if was created here, not provided by the caller. + if (input is not HttpContent) + { + effectiveInput?.Dispose(); + } + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); } @@ -245,235 +262,263 @@ public Task CallApiForAppAsync( CancellationToken cancellationToken = default) where TOutput : class { - DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); - HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, - null, null, cancellationToken).ConfigureAwait(false); - + DownstreamApiOptions effectiveOptions = MergeOptions(serviceName, downstreamApiOptionsOverride); + HttpResponseMessage response = await CallApiInternalAsync(serviceName, effectiveOptions, true, + null, null, cancellationToken).ConfigureAwait(false); + return await DeserializeOutputAsync(response, effectiveOptions, outputJsonTypeInfo).ConfigureAwait(false); } - internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions, JsonTypeInfo inputJsonTypeInfo) - { - return SerializeInputImpl(input, effectiveOptions, inputJsonTypeInfo); + internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions, JsonTypeInfo inputJsonTypeInfo) + { + HttpContent? httpContent; + + if (effectiveOptions.Serializer != null) + { + httpContent = effectiveOptions.Serializer(input); + } + else + { + // if the input is already an HttpContent, it's used as is, and should already contain a ContentType. + httpContent = input switch + { + HttpContent content => content, + string str when !string.IsNullOrEmpty(effectiveOptions.ContentType) && effectiveOptions.ContentType.StartsWith("text", StringComparison.OrdinalIgnoreCase) => new StringContent(str), + string str => new StringContent(JsonSerializer.Serialize(str, inputJsonTypeInfo), + Encoding.UTF8, + "application/json"), + byte[] bytes => new ByteArrayContent(bytes), + Stream stream => new StreamContent(stream), + null => null, + _ => new StringContent( + JsonSerializer.Serialize(input, inputJsonTypeInfo), + Encoding.UTF8, + "application/json"), + }; + } + return httpContent; } - internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) - where TOutput : class + internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) + where TOutput : class { return await DeserializeOutputImplAsync(response, effectiveOptions, outputJsonTypeInfo); } -#endif +#endif - /// - /// Merge the options from configuration and override from caller. - /// - /// Named configuration. - /// Delegate to override the configuration. + /// + /// Merge the options from configuration and override from caller. + /// + /// Named configuration. + /// Delegate to override the configuration. internal /* for tests */ DownstreamApiOptions MergeOptions( - string? optionsInstanceName, - Action? calledApiOptionsOverride) - { - // Gets the options from configuration (or default value) - DownstreamApiOptions options; - if (optionsInstanceName != null) - { - options = _namedDownstreamApiOptions.Get(optionsInstanceName); - } - else - { - options = _namedDownstreamApiOptions.CurrentValue; - } - - DownstreamApiOptions clonedOptions = new DownstreamApiOptions(options); - calledApiOptionsOverride?.Invoke(clonedOptions); - return clonedOptions; - } - - /// - /// Merge the options from configuration and override from caller. - /// - /// Named configuration. - /// Delegate to override the configuration. - /// Http method overriding the configuration options. + string? optionsInstanceName, + Action? calledApiOptionsOverride) + { + // Gets the options from configuration (or default value) + DownstreamApiOptions options; + if (optionsInstanceName != null) + { + options = _namedDownstreamApiOptions.Get(optionsInstanceName); + } + else + { + options = _namedDownstreamApiOptions.CurrentValue; + } + + DownstreamApiOptions clonedOptions = new DownstreamApiOptions(options); + calledApiOptionsOverride?.Invoke(clonedOptions); + return clonedOptions; + } + + /// + /// Merge the options from configuration and override from caller. + /// + /// Named configuration. + /// Delegate to override the configuration. + /// Http method overriding the configuration options. internal /* for tests */ DownstreamApiOptions MergeOptions( - string? optionsInstanceName, - Action? calledApiOptionsOverride, HttpMethod httpMethod) - { - // Gets the options from configuration (or default value) - DownstreamApiOptions options; - if (optionsInstanceName != null) - { - options = _namedDownstreamApiOptions.Get(optionsInstanceName); - } - else - { - options = _namedDownstreamApiOptions.CurrentValue; - } - - DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod(options, httpMethod.ToString()); - calledApiOptionsOverride?.Invoke(clonedOptions); - return clonedOptions; + string? optionsInstanceName, + Action? calledApiOptionsOverride, HttpMethod httpMethod) + { + // Gets the options from configuration (or default value) + DownstreamApiOptions options; + if (optionsInstanceName != null) + { + options = _namedDownstreamApiOptions.Get(optionsInstanceName); + } + else + { + options = _namedDownstreamApiOptions.CurrentValue; + } + + DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod(options, httpMethod.ToString()); + calledApiOptionsOverride?.Invoke(clonedOptions); + return clonedOptions; } - internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions) - { - return SerializeInputImpl(input, effectiveOptions, null); - } - - private static HttpContent? SerializeInputImpl(TInput input, DownstreamApiOptions effectiveOptions, JsonTypeInfo? inputJsonTypeInfo = null) - { +#if NET7_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + internal static HttpContent? SerializeInput(TInput input, DownstreamApiOptions effectiveOptions) + { HttpContent? httpContent; if (effectiveOptions.Serializer != null) - { + { httpContent = effectiveOptions.Serializer(input); - } - else - { + } + else + { // if the input is already an HttpContent, it's used as is, and should already contain a ContentType. httpContent = input switch { HttpContent content => content, string str when !string.IsNullOrEmpty(effectiveOptions.ContentType) && effectiveOptions.ContentType.StartsWith("text", StringComparison.OrdinalIgnoreCase) => new StringContent(str), string str => new StringContent( - inputJsonTypeInfo == null ? JsonSerializer.Serialize(str) : JsonSerializer.Serialize(str, inputJsonTypeInfo), + JsonSerializer.Serialize(str), Encoding.UTF8, "application/json"), byte[] bytes => new ByteArrayContent(bytes), Stream stream => new StreamContent(stream), null => null, _ => new StringContent( - inputJsonTypeInfo == null ? JsonSerializer.Serialize(input) : JsonSerializer.Serialize(input, inputJsonTypeInfo), + JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"), }; - } - return httpContent; - } - - internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions) + } + return httpContent; + } + +#if NET7_0_OR_GREATER + [RequiresUnreferencedCode("Calls JsonSerializer.Serialize")] + [RequiresDynamicCode("Calls JsonSerializer.Serialize")] +#endif + internal static async Task DeserializeOutputAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions) where TOutput : class { - try - { - response.EnsureSuccessStatusCode(); - } - catch - { - string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - -#if NET5_0_OR_GREATER - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); -#else - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); -#endif - } - - HttpContent content = response.Content; - - if (content == null) - { - return default; - } - - string? mediaType = content.Headers.ContentType?.MediaType; - - if (effectiveOptions.Deserializer != null) - { - return effectiveOptions.Deserializer(content) as TOutput; - } + try + { + response.EnsureSuccessStatusCode(); + } + catch + { + string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + +#if NET5_0_OR_GREATER + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); +#else + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); +#endif + } + + HttpContent content = response.Content; + + if (content == null) + { + return default; + } + + string? mediaType = content.Headers.ContentType?.MediaType; + + if (effectiveOptions.Deserializer != null) + { + return effectiveOptions.Deserializer(content) as TOutput; + } else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) { return content as TOutput; - } - else - { - string stringContent = await content.ReadAsStringAsync(); - if (mediaType == "application/json") - { - return JsonSerializer.Deserialize(stringContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - } - if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)) - { - // Handle other content types here - throw new NotSupportedException("Content type not supported. Provide your own deserializer. "); - } - return stringContent as TOutput; } - } - - private static async Task DeserializeOutputImplAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) - where TOutput : class - { - try - { - response.EnsureSuccessStatusCode(); - } - catch - { - string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - -#if NET5_0_OR_GREATER - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); -#else - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); -#endif - } - - HttpContent content = response.Content; - - if (content == null) - { - return default; - } - - string? mediaType = content.Headers.ContentType?.MediaType; - - if (effectiveOptions.Deserializer != null) - { - return effectiveOptions.Deserializer(content) as TOutput; - } + else + { + string stringContent = await content.ReadAsStringAsync(); + if (mediaType == "application/json") + { + return JsonSerializer.Deserialize(stringContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)) + { + // Handle other content types here + throw new NotSupportedException("Content type not supported. Provide your own deserializer. "); + } + return stringContent as TOutput; + } + } + + private static async Task DeserializeOutputImplAsync(HttpResponseMessage response, DownstreamApiOptions effectiveOptions, JsonTypeInfo outputJsonTypeInfo) + where TOutput : class + { + try + { + response.EnsureSuccessStatusCode(); + } + catch + { + string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + +#if NET5_0_OR_GREATER + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}", null, response.StatusCode); +#else + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); +#endif + } + + HttpContent content = response.Content; + + if (content == null) + { + return default; + } + + string? mediaType = content.Headers.ContentType?.MediaType; + + if (effectiveOptions.Deserializer != null) + { + return effectiveOptions.Deserializer(content) as TOutput; + } else if (typeof(TOutput).IsAssignableFrom(typeof(HttpContent))) { return content as TOutput; - } - else - { - string stringContent = await content.ReadAsStringAsync(); - if (mediaType == "application/json") - { - return JsonSerializer.Deserialize(stringContent, outputJsonTypeInfo); - } - if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)) - { - // Handle other content types here - throw new NotSupportedException("Content type not supported. Provide your own deserializer. "); - } - return stringContent as TOutput; - } - } - + } + else + { + string stringContent = await content.ReadAsStringAsync(); + if (mediaType == "application/json") + { + return JsonSerializer.Deserialize(stringContent, outputJsonTypeInfo); + } + if (mediaType != null && !mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase)) + { + // Handle other content types here + throw new NotSupportedException("Content type not supported. Provide your own deserializer. "); + } + return stringContent as TOutput; + } + } + internal /* for tests */ async Task CallApiInternalAsync( - string? serviceName, - DownstreamApiOptions effectiveOptions, - bool appToken, - HttpContent? content = null, - ClaimsPrincipal? user = null, - CancellationToken cancellationToken = default) - { - // Downstream API URI - string apiUrl = effectiveOptions.GetApiUrl(); - + string? serviceName, + DownstreamApiOptions effectiveOptions, + bool appToken, + HttpContent? content = null, + ClaimsPrincipal? user = null, + CancellationToken cancellationToken = default) + { + // Downstream API URI + string apiUrl = effectiveOptions.GetApiUrl(); + // Create an HTTP request message using HttpRequestMessage httpRequestMessage = new( - new HttpMethod(effectiveOptions.HttpMethod), - apiUrl); - - await UpdateRequestAsync(httpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken); - + new HttpMethod(effectiveOptions.HttpMethod), + apiUrl); + + await UpdateRequestAsync(httpRequestMessage, content, effectiveOptions, appToken, user, cancellationToken); + using HttpClient client = string.IsNullOrEmpty(serviceName) ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient(serviceName); - - // Send the HTTP message + + // Send the HTTP message var downstreamApiResult = await client.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); // Retry only if the resource sent 401 Unauthorized with WWW-Authenticate header and claims @@ -495,7 +540,7 @@ public Task CallApiForAppAsync( return downstreamApiResult; } - + internal /* internal for test */ async Task UpdateRequestAsync( HttpRequestMessage httpRequestMessage, HttpContent? content, @@ -513,10 +558,10 @@ public Task CallApiForAppAsync( effectiveOptions.RequestAppToken = appToken; - // Obtention of the authorization header (except when calling an anonymous endpoint - // which is done by not specifying any scopes - if (effectiveOptions.Scopes != null && effectiveOptions.Scopes.Any()) - { + // Obtention of the authorization header (except when calling an anonymous endpoint + // which is done by not specifying any scopes + if (effectiveOptions.Scopes != null && effectiveOptions.Scopes.Any()) + { string authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderAsync( effectiveOptions.Scopes, effectiveOptions, @@ -532,18 +577,18 @@ public Task CallApiForAppAsync( { httpRequestMessage.Headers.Add(Authorization, authorizationHeader); } - } - else - { - Logger.UnauthenticatedApiCall(_logger, null); - } - if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader)) + } + else + { + Logger.UnauthenticatedApiCall(_logger, null); + } + if (!string.IsNullOrEmpty(effectiveOptions.AcceptHeader)) { httpRequestMessage.Headers.Accept.ParseAdd(effectiveOptions.AcceptHeader); - } - // Opportunity to change the request message + } + // Opportunity to change the request message effectiveOptions.CustomizeHttpRequestMessage?.Invoke(httpRequestMessage); - } + } internal /* for test */ static Dictionary CallerSDKDetails { get; } = new() { @@ -565,5 +610,5 @@ private static void AddCallerSDKTelemetry(DownstreamApiOptions effectiveOptions) CallerSDKDetails["caller-sdk-ver"]; } } - } -} + } +} diff --git a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApiExtensions.cs b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApiExtensions.cs index 54b3b47b0..1856962b9 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApiExtensions.cs +++ b/src/Microsoft.Identity.Web.DownstreamApi/DownstreamApiExtensions.cs @@ -30,7 +30,9 @@ public static IServiceCollection AddDownstreamApi( { _ = Throws.IfNull(services); - services.Configure(serviceName, configuration); + // Help the compiler figure out the type so that the code generator generates + // the binding code + services.Configure(serviceName, configuration.GetSection(string.Empty)); RegisterDownstreamApi(services); return services; } @@ -50,7 +52,7 @@ public static IServiceCollection AddDownstreamApi( { _ = Throws.IfNull(services); - services.Configure(serviceName, configureOptions); + services.Configure(serviceName, configureOptions); RegisterDownstreamApi(services); return services; @@ -69,15 +71,14 @@ public static IServiceCollection AddDownstreamApis( { _ = Throws.IfNull(services); - Dictionary options = new(); - configurationSection.Bind(options); + // Help the compiler figure out the type so that the code generator generates + // the binding code + Dictionary options =configurationSection.Get>() + ?? new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var optionsForService in options.Keys) { - // lambda expression is needed as a workaround for IL2026 and IL3050 so the ConfigBinder Source Generator works - // https://github.com/dotnet/aspire/blob/2ed738cb524f7ce82490f0da33a1ea3e194011e8/src/Components/Aspire.Azure.Messaging.ServiceBus/AspireServiceBusExtensions.cs#L105 - IConfigurationSection optionsForServiceSection = configurationSection.GetSection(optionsForService); - services.Configure(optionsForService, bindOptions => optionsForServiceSection.Bind(bindOptions)); + services.Configure(optionsForService, configurationSection.GetSection(optionsForService)); } RegisterDownstreamApi(services); return services; diff --git a/src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj b/src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj index ba139f474..2ae4be127 100644 --- a/src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj +++ b/src/Microsoft.Identity.Web.DownstreamApi/Microsoft.Identity.Web.DownstreamApi.csproj @@ -1,4 +1,4 @@ - + Microsoft Identity Web downstream API Microsoft Identity Web downstream API @@ -6,8 +6,9 @@ {A123BD94-812D-40EC-9576-1A7AB5C59913} README.md - + true + true diff --git a/src/Microsoft.Identity.Web.MicrosoftGraph/Microsoft.Identity.Web.MicrosoftGraph.csproj b/src/Microsoft.Identity.Web.MicrosoftGraph/Microsoft.Identity.Web.MicrosoftGraph.csproj index fbca3319c..f32827cd6 100644 --- a/src/Microsoft.Identity.Web.MicrosoftGraph/Microsoft.Identity.Web.MicrosoftGraph.csproj +++ b/src/Microsoft.Identity.Web.MicrosoftGraph/Microsoft.Identity.Web.MicrosoftGraph.csproj @@ -11,6 +11,11 @@ {E4BC2331-6822-45C3-9702-D76DD0556532} README.md + + true + true + + True diff --git a/src/Microsoft.Identity.Web.MicrosoftGraphBeta/Microsoft.Identity.Web.MicrosoftGraphBeta.csproj b/src/Microsoft.Identity.Web.MicrosoftGraphBeta/Microsoft.Identity.Web.MicrosoftGraphBeta.csproj index 89cc55f31..d88d67cec 100644 --- a/src/Microsoft.Identity.Web.MicrosoftGraphBeta/Microsoft.Identity.Web.MicrosoftGraphBeta.csproj +++ b/src/Microsoft.Identity.Web.MicrosoftGraphBeta/Microsoft.Identity.Web.MicrosoftGraphBeta.csproj @@ -12,6 +12,11 @@ $(NoWarn);NU5104 + + true + true + + diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClientInfo.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClientInfo.cs index 72c52e76e..47dc1fd00 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClientInfo.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClientInfo.cs @@ -17,9 +17,6 @@ internal class ClientInfo [JsonPropertyName(ClaimConstants.UniqueTenantIdentifier)] public string? UniqueTenantIdentifier { get; set; } = null; -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Calls Microsoft.Identity.Web.ClientInfo.DeserializeFromJson(byte[]).")] -#endif public static ClientInfo? CreateFromJson(string? clientInfo) { if (string.IsNullOrEmpty(clientInfo)) @@ -31,22 +28,30 @@ internal class ClientInfo return bytes != null ? DeserializeFromJson(bytes) : null; } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Deserialize(ReadOnlySpan, JsonSerializerOptions).")] -#endif internal static ClientInfo? DeserializeFromJson(byte[]? jsonByteArray) { if (jsonByteArray == null || jsonByteArray.Length == 0) { return default; } - - var options = new JsonSerializerOptions +#if NET6_0_OR_GREATER + return JsonSerializer.Deserialize(jsonByteArray, ClientInfoJsonContext.Default.ClientInfo); +#else + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, }; return JsonSerializer.Deserialize(jsonByteArray, options); +#endif } } + +#if NET6_0_OR_GREATER + [JsonSerializable(typeof(ClientInfo))] + [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)] + partial class ClientInfoJsonContext : JsonSerializerContext + { + } +#endif } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj index 4a2fcf8d0..34322a4d9 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj +++ b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj @@ -8,6 +8,11 @@ README.md + + + diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt index 5dfd019d9..2c5e8d3a5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Unshipped.txt @@ -1 +1,2 @@ -const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! \ No newline at end of file +const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! +Microsoft.Identity.Web.ClientInfoJsonContext \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt index 5dfd019d9..2c5e8d3a5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Unshipped.txt @@ -1 +1,2 @@ -const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! \ No newline at end of file +const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! +Microsoft.Identity.Web.ClientInfoJsonContext \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt index 5dfd019d9..2c5e8d3a5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt @@ -1 +1,2 @@ -const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! \ No newline at end of file +const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! +Microsoft.Identity.Web.ClientInfoJsonContext \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt index 5dfd019d9..2c5e8d3a5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt @@ -1 +1,2 @@ -const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! \ No newline at end of file +const Microsoft.Identity.Web.IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client: " -> string! +Microsoft.Identity.Web.ClientInfoJsonContext \ No newline at end of file diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirerFactory.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirerFactory.cs index 6e9ede235..d07119d64 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirerFactory.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirerFactory.cs @@ -247,8 +247,7 @@ private IConfiguration ReadConfiguration() /// Returns the base path for configuration files protected virtual string DefineConfiguration(IConfigurationBuilder builder) { - Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); - return Path.GetDirectoryName(assembly!.Location)!; + return Path.GetDirectoryName(AppContext.BaseDirectory)!; } ITokenAcquirerFactory implementation; diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/WebApiBuilders.cs b/src/Microsoft.Identity.Web.TokenAcquisition/WebApiBuilders.cs index f51ce2901..152970ddc 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/WebApiBuilders.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/WebApiBuilders.cs @@ -37,7 +37,8 @@ public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAc { if (configuration != null) { - services.Configure(authenticationScheme, configuration); + // TODO: This never was right. And the configureConfidentialClientApplicationOptions delegate is not used + // services.Configure(authenticationScheme, configuration); services.Configure(authenticationScheme, options => { configuration.Bind(options); }); diff --git a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config index 4c9a36187..84916fe17 100644 --- a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config +++ b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config @@ -58,7 +58,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -82,23 +82,23 @@ - + - + - + - + - + diff --git a/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config b/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config index 3c287d045..dd9bee28b 100644 --- a/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config +++ b/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config @@ -59,11 +59,11 @@ - + - + @@ -75,7 +75,7 @@ - + @@ -83,23 +83,23 @@ - + - + - + - + - + diff --git a/tests/Microsoft.Identity.Web.AotCompatibility.TestApp/Microsoft.Identity.Web.AotCompatibility.TestApp.csproj b/tests/Microsoft.Identity.Web.AotCompatibility.TestApp/Microsoft.Identity.Web.AotCompatibility.TestApp.csproj index d35fa212d..f87105e11 100644 --- a/tests/Microsoft.Identity.Web.AotCompatibility.TestApp/Microsoft.Identity.Web.AotCompatibility.TestApp.csproj +++ b/tests/Microsoft.Identity.Web.AotCompatibility.TestApp/Microsoft.Identity.Web.AotCompatibility.TestApp.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 Exe true true