diff --git a/docs/Media/Readme.md b/docs/Media/Readme.md index 08504ae1..5f003ee1 100644 --- a/docs/Media/Readme.md +++ b/docs/Media/Readme.md @@ -24,7 +24,7 @@ The following settings in `appsettings.json` control media upload functionality: | AuthenticationMode | string | Authentication method - either `Default` for Microsoft Entra ID or `ConnectionString` for connection string auth | | ConnectionString | string | Azure Storage connection string (only used and mandatory when AuthenticationMode is `ConnectionString`) | | ServiceUrl | string | Azure Blob Storage service URL (only used and mandatory when AuthenticationMode is `Default`) | -| ContainerName | string | Name of the Azure Storage container to store uploaded files. | +| ContainerName | string | Name of the Azure Storage container to store uploaded files. It can be nested containers as well ``path/to/upload`` | | CdnEndpoint | string | Optional CDN endpoint to use for uploaded images. If set, the blog will return this URL instead of the storage account URL for uploaded assets. | ## Authentication Methods diff --git a/src/LinkDotNet.Blog.Web/Features/Services/FileUpload/AzureBlobStorageService.cs b/src/LinkDotNet.Blog.Web/Features/Services/FileUpload/AzureBlobStorageService.cs index 4756facc..0d69ff6f 100644 --- a/src/LinkDotNet.Blog.Web/Features/Services/FileUpload/AzureBlobStorageService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Services/FileUpload/AzureBlobStorageService.cs @@ -1,10 +1,11 @@ -using System; -using System.IO; -using System.Threading.Tasks; using Azure.Identity; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Microsoft.Extensions.Options; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; namespace LinkDotNet.Blog.Web.Features.Services.FileUpload; @@ -23,8 +24,10 @@ public async Task UploadFileAsync(string fileName, Stream fileStream, Up var containerName = azureBlobStorageConfiguration.Value.ContainerName; var client = CreateClient(azureBlobStorageConfiguration.Value); - var blobContainerClient = client.GetBlobContainerClient(containerName); - var blobClient = blobContainerClient.GetBlobClient(fileName); + + var (rootContainer, subContainer) = SplitContainerName(containerName); + var blobContainerClient = client.GetBlobContainerClient(rootContainer); + var blobClient = blobContainerClient.GetBlobClient($"{subContainer}/{fileName}"); var blobOptions = new BlobUploadOptions(); if (options.SetCacheControlHeader) @@ -39,6 +42,18 @@ public async Task UploadFileAsync(string fileName, Stream fileStream, Up return GetAssetUrl(blobClient.Uri.ToString(), azureBlobStorageConfiguration.Value); } + private static (string rootContainer, string subContainer) SplitContainerName(string containerName) + { + var containerNames = containerName.Split('/', StringSplitOptions.RemoveEmptyEntries); + + if (containerNames.Length == 0) + return (string.Empty, string.Empty); + + var rootContainer = containerNames[0]; + var subContainer = string.Join("/", containerNames.Skip(1)); + return (rootContainer, subContainer); + } + private static BlobServiceClient CreateClient(UploadConfiguration configuration) { if (configuration.AuthenticationMode == AuthenticationMode.ConnectionString.Key)