Skip to content
Closed
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
10 changes: 9 additions & 1 deletion src/Aspire.Hosting.Azure.Storage/AzureBlobStorageResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,16 @@ internal ReferenceExpression GetConnectionString(string? blobContainerName)
return ConnectionStringExpression;
}

// For non-emulator environments (deployed), return the connection string directly
// which will be used as a ServiceUri with the container name appended by the client
if (!Parent.IsEmulator)
{
return ConnectionStringExpression;
}

// For emulator environment, create a connection string with the emulator-specific format
ReferenceExpressionBuilder builder = new();
builder.Append($"{Endpoint}=\"{ConnectionStringExpression}\";");
builder.Append($"{Endpoint}={ConnectionStringExpression};");

if (!string.IsNullOrEmpty(blobContainerName))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,39 @@ void IConnectionStringSettings.ParseConnectionString(string? connectionString)
return;
}

// NOTE: if ever these contants are changed, the AzureBlobStorageResource in Aspire.Hosting.Azure.Storage class should be updated as well.
// NOTE: if ever these constants are changed, the AzureBlobStorageResource in Aspire.Hosting.Azure.Storage class should be updated as well.
const string Endpoint = nameof(Endpoint);
const string ContainerName = nameof(ContainerName);

DbConnectionStringBuilder builder = new() { ConnectionString = connectionString };
if (builder.TryGetValue(Endpoint, out var endpoint) && builder.TryGetValue(ContainerName, out var containerName))
// First try to parse as a connection string in the format Endpoint=value;ContainerName=value
try
{
ConnectionString = endpoint.ToString();
BlobContainerName = containerName.ToString();
DbConnectionStringBuilder builder = new() { ConnectionString = connectionString };
if (builder.TryGetValue(Endpoint, out var endpoint) && builder.TryGetValue(ContainerName, out var containerName))
{
ConnectionString = endpoint?.ToString();
BlobContainerName = containerName?.ToString();
return; // Successfully parsed
}
}
catch (ArgumentException)
{
// If this is not a valid connection string, it might be a direct URL endpoint
// from a deployed environment - we'll handle it in the next step
}

// Handle the case where connectionString is a direct URL endpoint (for deployed environments)
// In this case, we use it as the ConnectionString directly and expect the BlobContainerName
// to be set separately through configuration
if (Uri.TryCreate(connectionString, UriKind.Absolute, out _))
{
ConnectionString = connectionString;
// BlobContainerName should be set through configuration
}
else
{
// If we get here, the string is neither a valid connection string nor a valid URI
throw new ArgumentException($"Invalid connection string format. Expected either a connection string with format 'Endpoint=value;ContainerName=value' or a valid URI.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ public class AzureBlobStorageContainerSettingsTests
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(";")]
[InlineData("Endpoint=https://example.blob.core.windows.net;")]
[InlineData("ContainerName=my-container;")]
[InlineData("Endpoint=https://example.blob.core.windows.net;ExtraParam=value;")]
public void ParseConnectionString_invalid_input(string? connectionString)
public void ParseConnectionString_null_or_empty_input(string? connectionString)
{
var settings = new AzureBlobStorageContainerSettings();

Expand All @@ -26,15 +22,23 @@ public void ParseConnectionString_invalid_input(string? connectionString)
Assert.Null(settings.BlobContainerName);
}

[Fact]
public void ParseConnectionString_invalid_input_results_in_AE()
[Theory]
[InlineData(";")]
[InlineData("InvalidConnectionString")]
[InlineData("Endpoint=")]
[InlineData("Endpoint=https://example.blob.core.windows.net;")]
[InlineData("ContainerName=my-container;")]
[InlineData("Endpoint=https://example.blob.core.windows.net;ExtraParam=value;")]
public void ParseConnectionString_invalid_input_throws(string connectionString)
{
var settings = new AzureBlobStorageContainerSettings();
string connectionString = "InvalidConnectionString";

Assert.Throws<ArgumentException>(() => ((IConnectionStringSettings)settings).ParseConnectionString(connectionString));
}

// The "partial_input_handled_gracefully" test was removed because these cases
// now throw exceptions in our stricter validation

[Theory]
[InlineData("Endpoint=https://example.blob.core.windows.net;ContainerName=my-container")]
[InlineData("Endpoint=https://example.blob.core.windows.net;ContainerName=my-container;ExtraParam=value")]
Expand All @@ -49,4 +53,44 @@ public void ParseConnectionString_valid_input(string connectionString)
Assert.Equal("https://example.blob.core.windows.net", settings.ConnectionString);
Assert.Equal("my-container", settings.BlobContainerName);
}

[Fact]
public void ParseConnectionString_with_quoted_endpoint()
{
// This test reproduces the issue where the connection string has quotes around the endpoint value
var settings = new AzureBlobStorageContainerSettings();
string connectionString = "Endpoint=\"https://example.blob.core.windows.net\";ContainerName=my-container";

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

Assert.Equal("https://example.blob.core.windows.net", settings.ConnectionString);
Assert.Equal("my-container", settings.BlobContainerName);
}

[Fact]
public void ParseConnectionString_with_emulator_format()
{
// Test with emulator connection string format where Endpoint value is itself a connection string
var settings = new AzureBlobStorageContainerSettings();
string connectionString = "Endpoint=\"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=key;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;\";ContainerName=foo-container;";

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

Assert.Equal("DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=key;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;", settings.ConnectionString);
Assert.Equal("foo-container", settings.BlobContainerName);
}

[Theory]
[InlineData("https://example.blob.core.windows.net")]
[InlineData("http://localhost:10000/devstoreaccount1")]
public void ParseConnectionString_url_endpoint(string endpoint)
{
var settings = new AzureBlobStorageContainerSettings();

// Using a URL directly as a connection string (deployed environment scenario)
((IConnectionStringSettings)settings).ParseConnectionString(endpoint);

Assert.Equal(endpoint, settings.ConnectionString);
Assert.Null(settings.BlobContainerName); // Container name should be provided separately
}
}