Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fcda82f

Browse files
[release/9.3] Fix Blob Container Connection String Format Exception (#9496)
* Fix blob storage connection string configuration * Rervert program change * Remove connection string embedding * Add tests * Fix test --------- Co-authored-by: Sebastien Ros <[email protected]>
1 parent 8b51817 commit fcda82f

File tree

6 files changed

+98
-52
lines changed

6 files changed

+98
-52
lines changed

src/Aspire.Hosting.Azure.Storage/AzureBlobStorageResource.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ public class AzureBlobStorageResource(string name, AzureStorageResource storage)
1616
IResourceWithParent<AzureStorageResource>,
1717
IResourceWithAzureFunctionsConfig
1818
{
19-
// NOTE: if ever these contants are changed, the AzureBlobStorageContainerSettings in Aspire.Azure.Storage.Blobs class should be updated as well.
20-
private const string Endpoint = nameof(Endpoint);
21-
private const string ContainerName = nameof(ContainerName);
22-
2319
/// <summary>
2420
/// Gets the parent AzureStorageResource of this AzureBlobStorageResource.
2521
/// </summary>
@@ -39,13 +35,18 @@ internal ReferenceExpression GetConnectionString(string? blobContainerName)
3935
}
4036

4137
ReferenceExpressionBuilder builder = new();
42-
builder.Append($"{Endpoint}=\"{ConnectionStringExpression}\";");
4338

44-
if (!string.IsNullOrEmpty(blobContainerName))
39+
if (Parent.IsEmulator)
40+
{
41+
builder.AppendFormatted(ConnectionStringExpression);
42+
}
43+
else
4544
{
46-
builder.Append($"{ContainerName}={blobContainerName};");
45+
builder.Append($"Endpoint={ConnectionStringExpression}");
4746
}
4847

48+
builder.Append($";ContainerName={blobContainerName}");
49+
4950
return builder.Build();
5051
}
5152

src/Components/Aspire.Azure.Storage.Blobs/AzureBlobStorageContainerSettings.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Data.Common;
5+
using System.Text.RegularExpressions;
56
using Aspire.Azure.Common;
67

78
namespace Aspire.Azure.Storage.Blobs;
@@ -11,6 +12,9 @@ namespace Aspire.Azure.Storage.Blobs;
1112
/// </summary>
1213
public sealed partial class AzureBlobStorageContainerSettings : AzureStorageBlobsSettings, IConnectionStringSettings
1314
{
15+
[GeneratedRegex(@"(?i)ContainerName\s*=\s*([^;]+);?", RegexOptions.IgnoreCase)]
16+
private static partial Regex ContainerNameRegex();
17+
1418
/// <summary>
1519
/// Gets or sets the name of the blob container.
1620
/// </summary>
@@ -23,15 +27,31 @@ void IConnectionStringSettings.ParseConnectionString(string? connectionString)
2327
return;
2428
}
2529

26-
// NOTE: if ever these contants are changed, the AzureBlobStorageResource in Aspire.Hosting.Azure.Storage class should be updated as well.
27-
const string Endpoint = nameof(Endpoint);
28-
const string ContainerName = nameof(ContainerName);
29-
3030
DbConnectionStringBuilder builder = new() { ConnectionString = connectionString };
31-
if (builder.TryGetValue(Endpoint, out var endpoint) && builder.TryGetValue(ContainerName, out var containerName))
31+
32+
if (builder.TryGetValue("ContainerName", out var containerName))
33+
{
34+
BlobContainerName = containerName?.ToString();
35+
36+
// Remove the ContainerName property from the connection string as BlobServiceClient would fail to parse it.
37+
connectionString = ContainerNameRegex().Replace(connectionString, "");
38+
39+
// NB: we can't remove ContainerName by using the DbConnectionStringBuilder as it would escape the AccountKey value
40+
// when the connection string is built and BlobServiceClient doesn't support escape sequences.
41+
}
42+
43+
// Connection string built from a URI? e.g., Endpoint=https://{account_name}.blob.core.windows.net;ContainerName=...;
44+
if (builder.TryGetValue("Endpoint", out var endpoint) && endpoint is string)
45+
{
46+
if (Uri.TryCreate(endpoint.ToString(), UriKind.Absolute, out var uri))
47+
{
48+
ServiceUri = uri;
49+
}
50+
}
51+
else
3252
{
33-
ConnectionString = endpoint.ToString();
34-
BlobContainerName = containerName.ToString();
53+
// Otherwise preserve the existing connection string
54+
ConnectionString = connectionString;
3555
}
3656
}
3757
}

src/Components/Aspire.Azure.Storage.Blobs/AzureStorageBlobsSettings.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,18 @@ public class AzureStorageBlobsSettings : IConnectionStringSettings
5252

5353
void IConnectionStringSettings.ParseConnectionString(string? connectionString)
5454
{
55-
if (!string.IsNullOrEmpty(connectionString))
55+
if (string.IsNullOrEmpty(connectionString))
5656
{
57-
if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
58-
{
59-
ServiceUri = uri;
60-
}
61-
else
62-
{
63-
ConnectionString = connectionString;
64-
}
57+
return;
58+
}
59+
60+
if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
61+
{
62+
ServiceUri = uri;
63+
}
64+
else
65+
{
66+
ConnectionString = connectionString;
6567
}
6668
}
6769
}

tests/Aspire.Azure.Storage.Blobs.Tests/AzureBlobStorageContainerSettingsTests.cs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,7 @@ namespace Aspire.Hosting.Azure.Tests;
99

1010
public class AzureBlobStorageContainerSettingsTests
1111
{
12-
[Theory]
13-
[InlineData(null)]
14-
[InlineData("")]
15-
[InlineData(";")]
16-
[InlineData("Endpoint=https://example.blob.core.windows.net;")]
17-
[InlineData("ContainerName=my-container;")]
18-
[InlineData("Endpoint=https://example.blob.core.windows.net;ExtraParam=value;")]
19-
public void ParseConnectionString_invalid_input(string? connectionString)
20-
{
21-
var settings = new AzureBlobStorageContainerSettings();
22-
23-
((IConnectionStringSettings)settings).ParseConnectionString(connectionString);
24-
25-
Assert.Null(settings.ConnectionString);
26-
Assert.Null(settings.BlobContainerName);
27-
}
12+
private const string EmulatorConnectionString = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1";
2813

2914
[Fact]
3015
public void ParseConnectionString_invalid_input_results_in_AE()
@@ -40,13 +25,42 @@ public void ParseConnectionString_invalid_input_results_in_AE()
4025
[InlineData("Endpoint=https://example.blob.core.windows.net;ContainerName=my-container;ExtraParam=value")]
4126
[InlineData("endpoint=https://example.blob.core.windows.net;containername=my-container")]
4227
[InlineData("ENDPOINT=https://example.blob.core.windows.net;CONTAINERNAME=my-container")]
43-
public void ParseConnectionString_valid_input(string connectionString)
28+
[InlineData("Endpoint=\"https://example.blob.core.windows.net\";ContainerName=\"my-container\"")]
29+
public void ParseConnectionString_With_ServiceUri(string connectionString)
30+
{
31+
var settings = new AzureBlobStorageContainerSettings();
32+
33+
((IConnectionStringSettings)settings).ParseConnectionString(connectionString);
34+
35+
Assert.Equal("https://example.blob.core.windows.net/", settings.ServiceUri?.ToString());
36+
Assert.Equal("my-container", settings.BlobContainerName);
37+
}
38+
39+
[Theory]
40+
[InlineData($"{EmulatorConnectionString};ContainerName=my-container")]
41+
[InlineData($"{EmulatorConnectionString};ContainerName=\"my-container\"")]
42+
public void ParseConnectionString_With_ConnectionString(string connectionString)
43+
{
44+
var settings = new AzureBlobStorageContainerSettings();
45+
46+
((IConnectionStringSettings)settings).ParseConnectionString(connectionString);
47+
48+
Assert.Contains(EmulatorConnectionString, settings.ConnectionString, StringComparison.OrdinalIgnoreCase);
49+
Assert.DoesNotContain("ContainerName", settings.ConnectionString, StringComparison.OrdinalIgnoreCase);
50+
Assert.Equal("my-container", settings.BlobContainerName);
51+
Assert.Null(settings.ServiceUri);
52+
}
53+
54+
[Theory]
55+
[InlineData($"Endpoint=not-a-uri;ContainerName=my-container")]
56+
public void ParseConnectionString_With_NotAUri(string connectionString)
4457
{
4558
var settings = new AzureBlobStorageContainerSettings();
4659

4760
((IConnectionStringSettings)settings).ParseConnectionString(connectionString);
4861

49-
Assert.Equal("https://example.blob.core.windows.net", settings.ConnectionString);
62+
Assert.True(string.IsNullOrEmpty(settings.ConnectionString));
5063
Assert.Equal("my-container", settings.BlobContainerName);
64+
Assert.Null(settings.ServiceUri);
5165
}
5266
}

tests/Aspire.Hosting.Azure.Tests/AzureStorageEmulatorFunctionalTests.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,29 @@ public async Task VerifyWaitForOnAzureStorageEmulatorForBlobContainersBlocksDepe
110110
[RequiresDocker]
111111
public async Task VerifyAzureStorageEmulatorResource()
112112
{
113+
var blobsResourceName = "BlobConnection";
114+
var blobContainerName = "my-container";
115+
113116
using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper);
114-
var storage = builder.AddAzureStorage("storage").RunAsEmulator().AddBlobs("BlobConnection");
117+
var blobs = builder.AddAzureStorage("storage").RunAsEmulator().AddBlobs(blobsResourceName);
118+
var container = blobs.AddBlobContainer(blobContainerName);
115119

116120
using var app = builder.Build();
117121
await app.StartAsync();
118122

119123
var hb = Host.CreateApplicationBuilder();
120-
hb.Configuration["ConnectionStrings:BlobConnection"] = await storage.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
121-
hb.AddAzureBlobClient("BlobConnection");
124+
hb.Configuration[$"ConnectionStrings:{blobsResourceName}"] = await blobs.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
125+
hb.Configuration[$"ConnectionStrings:{blobContainerName}"] = await container.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
126+
hb.AddAzureBlobClient(blobsResourceName);
127+
hb.AddAzureBlobContainerClient(blobContainerName);
122128

123129
using var host = hb.Build();
124130
await host.StartAsync();
125131

126-
var serviceClient = host.Services.GetRequiredService<BlobServiceClient>();
127-
var blobContainer = (await serviceClient.CreateBlobContainerAsync("container")).Value;
128-
var blobClient = blobContainer.GetBlobClient("testKey");
132+
var blobServiceClient = host.Services.GetRequiredService<BlobServiceClient>();
133+
var blobContainerClient = host.Services.GetRequiredService<BlobContainerClient>();
134+
await blobContainerClient.CreateIfNotExistsAsync(); // For Aspire 9.3 only
135+
var blobClient = blobContainerClient.GetBlobClient("testKey");
129136

130137
await blobClient.UploadAsync(BinaryData.FromString("testValue"));
131138

tests/Aspire.Hosting.Azure.Tests/AzureStorageExtensionsTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,12 @@ public async Task AddBlobContainer_ConnectionString_resolved_expected_RunAsEmula
231231
var blobs = storage.AddBlobs("blob");
232232
var blobContainer = blobs.AddBlobContainer(name: "myContainer", blobContainerName);
233233

234-
string? blobConntionString = await ((IResourceWithConnectionString)blobs.Resource).GetConnectionStringAsync();
235-
string expected = $"Endpoint=\"{blobConntionString}\";ContainerName={blobContainerName};";
234+
string? blobConnectionString = await ((IResourceWithConnectionString)blobs.Resource).GetConnectionStringAsync();
235+
string? blobContainerConnectionString = await ((IResourceWithConnectionString)blobContainer.Resource).GetConnectionStringAsync();
236236

237-
Assert.Equal(expected, await ((IResourceWithConnectionString)blobContainer.Resource).GetConnectionStringAsync());
237+
Assert.NotNull(blobConnectionString);
238+
Assert.Contains(blobConnectionString, blobContainerConnectionString);
239+
Assert.Contains($"ContainerName={blobContainerName}", blobContainerConnectionString);
238240
}
239241

240242
[Fact]
@@ -252,7 +254,7 @@ public async Task AddBlobContainer_ConnectionString_resolved_expected()
252254
var blobContainer = blobs.AddBlobContainer(name: "myContainer", blobContainerName);
253255

254256
string? blobsConnectionString = await ((IResourceWithConnectionString)blobs.Resource).GetConnectionStringAsync();
255-
string expected = $"Endpoint=\"{blobsConnectionString}\";ContainerName={blobContainerName};";
257+
string expected = $"Endpoint={blobsConnectionString};ContainerName={blobContainerName}";
256258

257259
Assert.Equal(expected, await ((IResourceWithConnectionString)blobContainer.Resource).GetConnectionStringAsync());
258260
}
@@ -266,7 +268,7 @@ public void AddBlobContainer_ConnectionString_unresolved_expected()
266268
var blobs = storage.AddBlobs("blob");
267269
var blobContainer = blobs.AddBlobContainer(name: "myContainer");
268270

269-
Assert.Equal("Endpoint=\"{storage.outputs.blobEndpoint}\";ContainerName=myContainer;", blobContainer.Resource.ConnectionStringExpression.ValueExpression);
271+
Assert.Equal("Endpoint={storage.outputs.blobEndpoint};ContainerName=myContainer", blobContainer.Resource.ConnectionStringExpression.ValueExpression);
270272
}
271273

272274
[Fact]

0 commit comments

Comments
 (0)