-
|
I need to set CORS policy for Azure Blob Storage but I get an error var blobServiceClient = app.Services.GetRequiredService<BlobServiceClient>();
var properties = await blobServiceClient.GetPropertiesAsync(); //<--- Error already happens here
properties.Value.Cors =
new[]
{
new BlobCorsRule
{
MaxAgeInSeconds = 1000,
AllowedHeaders = "*",
AllowedMethods = "GET, PUT, POST, DELETE, OPTIONS",
AllowedOrigins = "https://myurl.com",
ExposedHeaders = "*"
}
};
// Act
await blobServiceClient.SetPropertiesAsync(properties); |
Beta Was this translation helpful? Give feedback.
Replies: 8 comments 5 replies
-
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
@AndiRudi Any luck on this issue? I am in the same boat. |
Beta Was this translation helpful? Give feedback.
-
|
I empathize with the frustration, azure is difficult. You can look up the required role for the operation https://learn.microsoft.com/en-us/rest/api/storageservices/set-blob-service-properties?tabs=microsoft-entra-id#authorization Here's the role assignment you need to add. Least privileged built-in role: Storage Account Contributor If you're not using Aspire 9, upgrade and you can do something like this: |
Beta Was this translation helpful? Give feedback.
-
|
Would be great to have a possibility to add CORS rules as part of storage.ConfigureInfrastructure extension method. I need to allow a web app (that is a part of Aspire solution) to directly access blog storage (with Delegated SAS token). Now I manually need to configure CORS for storage account. |
Beta Was this translation helpful? Give feedback.
-
|
I made it working this way: |
Beta Was this translation helpful? Give feedback.
-
|
The accepted answer didn't work for me. It runs just fine, but the settings weren't changed. I guess because I'm using Azuite emulated storage? Not sure. The only thing that worked for me was to simply use the Azure Storage API at runtime to configure the settings. I added this to my Program.cs on my backend: and this to my service layer: |
Beta Was this translation helpful? Give feedback.
-
|
I'm using this extension method which covers Azure and the emulator /// <summary>
/// Sets the CORS rules for the blob service of the storage resource.
/// Applies to the Azure resource during deployment and also during run mode (e.g., when using the emulator).
/// </summary>
public static IResourceBuilder<AzureStorageResource> SetBlobCorsRules(
this IResourceBuilder<AzureStorageResource> storage,
IEnumerable<BlobCorsRule> rules
)
{
storage.ConfigureInfrastructure(storageAccount =>
{
var blobService = storageAccount.GetProvisionableResources().OfType<BlobService>().Single();
blobService.CorsRules =
[
.. rules.Select(rule => new BicepValue<StorageCorsRule>(
new StorageCorsRule()
{
AllowedHeaders =
[
.. rule
.AllowedHeaders.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.Select(h => new BicepValue<string>(h)),
],
AllowedMethods =
[
.. rule
.AllowedMethods.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.Select(m =>
m switch
{
"GET" => CorsRuleAllowedMethod.Get,
"POST" => CorsRuleAllowedMethod.Post,
"PUT" => CorsRuleAllowedMethod.Put,
"DELETE" => CorsRuleAllowedMethod.Delete,
"HEAD" => CorsRuleAllowedMethod.Head,
"OPTIONS" => CorsRuleAllowedMethod.Options,
_ => throw new InvalidOperationException($"Invalid CORS method: {m}"),
}
),
],
AllowedOrigins =
[
.. rule
.AllowedOrigins.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.Select(o => new BicepValue<string>(o)),
],
ExposedHeaders =
[
.. rule
.ExposedHeaders.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.Select(e => new BicepValue<string>(e)),
],
MaxAgeInSeconds = rule.MaxAgeInSeconds,
}
)),
];
});
if (storage.ApplicationBuilder.ExecutionContext.IsRunMode)
{
var appBuilder = storage.ApplicationBuilder;
var logger = appBuilder
.Services.BuildServiceProvider()
.GetRequiredService<ILogger<DistributedApplication>>();
// `First`, not `Single` because `AddBlobContainer` and `AddBlobs` both create one
// Assume it doesn't matter which one we use, because they are both the same blob service
var blobResource = appBuilder
.Resources.OfType<AzureBlobStorageResource>()
.First(resource => resource.Parent == storage.Resource);
storage.OnResourceReady(
async (_, _, cancellationToken) =>
{
var connectionString = await blobResource
.ConnectionStringExpression.GetValueAsync(cancellationToken)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException("Blob connection string is null or empty.");
}
var client = new BlobServiceClient(connectionString);
const int maxAttempts = 10;
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
var propertiesResult = await client
.GetPropertiesAsync(cancellationToken: cancellationToken)
.ConfigureAwait(false);
var properties = propertiesResult.Value;
properties.Cors = [.. rules];
await client
.SetPropertiesAsync(properties, cancellationToken: cancellationToken)
.ConfigureAwait(false);
logger.LogInformation(
"Applied blob CORS rule(s) to storage resource '{StorageResourceName}'.",
storage.Resource.Name
);
return;
}
catch (Exception ex)
when (attempt < maxAttempts
&& (ex is RequestFailedException or HttpRequestException or IOException)
)
{
logger.LogDebug(
ex,
"Failed to apply blob CORS rule(s) on attempt {Attempt}/{MaxAttempts} for '{StorageResourceName}'. Retrying.",
attempt,
maxAttempts,
storage.Resource.Name
);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
throw new InvalidOperationException(
$"Failed to apply blob CORS rule(s) to storage resource '{storage.Resource.Name}' after {maxAttempts} attempts."
);
}
);
}
return storage;
}
``` |
Beta Was this translation helpful? Give feedback.
I made it working this way: