Skip to content

Commit 6fcabfc

Browse files
authored
[Extensions] Fix ASP.NET Core integration testing (Azure#48426)
* [Extensions] Fix ASP.NET Core integration testing The focus of these changes is to fix an assumption in the logic when creating clients from configuration. Currently, if the configuration section has a non-null value, it is assumed to be a connection string. This is not the case for ASP.NET Core test integration due to a bug in the test host which causes the Value property to return an empty string rather than the null returned by the actual configuration system.
1 parent f072158 commit 6fcabfc

File tree

8 files changed

+124
-9
lines changed

8 files changed

+124
-9
lines changed

sdk/extensions/Microsoft.Extensions.Azure/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fixed an issue when creating clients from configuration using the ASP.NET Core integration testing library, `Microsoft.AspNetCore.Mvc.Testing`. Due to a difference in how an section value is represented, logic was interpreting a setting with a child object as an empty connection string value. The child object is now properly parsed and applied for client construction. ([#48368](https://github.com/Azure/azure-sdk-for-net/issues/48368))
12+
1113
### Other Changes
1214

1315
## 1.10.0 (2025-02-11)

sdk/extensions/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ public static object CreateClient(
3030
{
3131
List<object> arguments = new List<object>();
3232
// Handle single values as connection strings
33-
if (configuration is IConfigurationSection section && section.Value != null)
33+
if (configuration is IConfigurationSection section && (!string.IsNullOrEmpty(section.Value)))
3434
{
3535
var connectionString = section.Value;
3636
configuration = new ConfigurationBuilder()
37-
.AddInMemoryCollection(new[]
38-
{
37+
.AddInMemoryCollection(
38+
[
3939
new KeyValuePair<string, string>(ConnectionStringParameterName, connectionString)
40-
})
40+
])
4141
.Build();
4242
}
4343
foreach (var constructor in clientType.GetConstructors().OrderByDescending(c => c.GetParameters().Length))

sdk/extensions/Microsoft.Extensions.Azure/src/Microsoft.Extensions.Azure.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<PackageReference Include="Microsoft.Extensions.Options" />
2626
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
2727
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
28-
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="8.0.0" />
28+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="8.0.0" />
2929
</ItemGroup>
3030

3131
<ItemGroup>

sdk/extensions/Microsoft.Extensions.Azure/tests/ClientFactoryTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
using Microsoft.Extensions.Configuration;
1313
using NUnit.Framework;
1414

15+
#if NET8_0_OR_GREATER
16+
using System.Threading.Tasks;
17+
using Microsoft.AspNetCore.Hosting;
18+
using Microsoft.AspNetCore.Mvc.Testing;
19+
using Microsoft.AspNetCore.TestHost;
20+
#endif
21+
1522
namespace Azure.Core.Extensions.Tests
1623
{
1724
public class ClientFactoryTests
@@ -626,6 +633,37 @@ public void CanUseAzureSasCredential()
626633
Assert.AreEqual("key", client.AzureSasCredential.Signature);
627634
}
628635

636+
#if NET8_0_OR_GREATER
637+
[Test]
638+
public async Task AllowsAspNetCoreIntegrationTestHostConfiguration()
639+
{
640+
var expectedKeyVaultUriValue = "https://fake.vault.azure.net/";
641+
642+
// When configuration is set using the test host, the behavior of IConfigurationSection
643+
// changes and the Value property is not null for a complex object. Instead it returns an
644+
// empty string, which we don't want to treat as a connection string.
645+
//
646+
// This is a bug in the configuration system, but there's no commitment for when it will
647+
// be fixed. See: https://github.com/dotnet/aspnetcore/issues/37680
648+
//
649+
var factory = new WebApplicationFactory<AspNetHost>()
650+
.WithWebHostBuilder(builder =>
651+
{
652+
builder.UseContentRoot("aspnet-host");
653+
654+
builder.UseConfiguration(
655+
GetConfiguration(
656+
new KeyValuePair<string, string>("KeyVault:VaultUri", expectedKeyVaultUriValue)));
657+
});
658+
659+
var client = factory.CreateClient();
660+
var response = await client.GetAsync("/keyvault");
661+
var keyVaultUriValue = await response.Content.ReadAsStringAsync();
662+
663+
Assert.AreEqual(expectedKeyVaultUriValue, keyVaultUriValue);
664+
}
665+
#endif
666+
629667
private IConfiguration GetConfiguration(params KeyValuePair<string, string>[] items)
630668
{
631669
return new ConfigurationBuilder().AddInMemoryCollection(items).Build();

sdk/extensions/Microsoft.Extensions.Azure/tests/Microsoft.Extensions.Azure.Tests.csproj

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,50 @@
22
<PropertyGroup>
33
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
44
</PropertyGroup>
5+
<ItemGroup>
6+
<None Include="aspnet-host\AspNetHostRoot.sln" />
7+
</ItemGroup>
58

69
<ItemGroup>
7-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="8.0.1" />
10+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" VersionOverride="8.0.1" />
11+
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" />
812
<PackageReference Include="Microsoft.NET.Test.Sdk" />
9-
<PackageReference Include="System.Net.WebSockets.Client" />
10-
<PackageReference Include="System.ValueTuple" />
1113
<PackageReference Include="NUnit" />
1214
<PackageReference Include="NUnit3TestAdapter" />
13-
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" />
15+
<PackageReference Include="System.Net.WebSockets.Client" />
16+
<PackageReference Include="System.ValueTuple" />
1417
</ItemGroup>
1518

1619
<ItemGroup>
1720
<ProjectReference Include="$(AzureCoreTestFramework)" />
1821
<ProjectReference Include="..\src\Microsoft.Extensions.Azure.csproj" />
1922
</ItemGroup>
23+
24+
<!-- Include ASPNET content for testing compatibility with the integration framework on net8+ only -->
25+
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
26+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
27+
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
28+
29+
<!--
30+
Override the ASPNET Core testing package locally until the 8.x bump in the engineering system.
31+
Tracked by: #48425
32+
-->
33+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" VersionOverride="8.0.13" />
34+
</ItemGroup>
35+
36+
<!-- Don't include the generated entry point for net8+ because one is explicitly defined in the ASPNET content -->
37+
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
38+
<GenerateProgramFile>false</GenerateProgramFile>
39+
</PropertyGroup>
40+
41+
<!-- Include the solution and content root markers for the ASPNET Core application -->
42+
<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
43+
<Copy SourceFiles="aspnet-host\AspNetHostRoot.sln" DestinationFolder="$(OutDir)" />
44+
</Target>
45+
46+
<ItemGroup>
47+
<None Update="aspnet-host\appsettings.json">
48+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
49+
</None>
50+
</ItemGroup>
2051
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#if NET8_0_OR_GREATER
5+
6+
using Azure.Security.KeyVault.Secrets;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.Extensions.Azure;
9+
using Microsoft.Extensions.DependencyInjection;
10+
11+
namespace Azure.Core.Extensions.Tests;
12+
13+
public class AspNetHost
14+
{
15+
public static void Main(string[] args)
16+
{
17+
var builder = WebApplication.CreateBuilder(args);
18+
builder.Environment.ContentRootPath = "aspnet-host";
19+
20+
builder.Services.AddAzureClients(clientBuilder =>
21+
{
22+
clientBuilder.AddSecretClient(builder.Configuration.GetSection("KeyVault"));
23+
});
24+
25+
var app = builder.Build();
26+
var secretClient = app.Services.GetRequiredService<SecretClient>();
27+
28+
app.MapGet("/keyvault", () => secretClient.VaultUri.AbsoluteUri);
29+
app.Run();
30+
}
31+
}
32+
#endif

sdk/extensions/Microsoft.Extensions.Azure/tests/aspnet-host/AspNetHostRoot.sln

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
},
8+
"AllowedHosts": "*",
9+
"KeyVault": {
10+
"VaultUri": "<vault_uri>"
11+
}
12+
}

0 commit comments

Comments
 (0)