Skip to content

Commit 1bf2b52

Browse files
authored
Merge pull request #4586 from Jacob-Polane/users/polane/fix/main/blobstorage
chore: added blob string normalisation and logging
2 parents 7097309 + 9e38278 commit 1bf2b52

File tree

1 file changed

+66
-22
lines changed

1 file changed

+66
-22
lines changed

shesha-core/src/Shesha.Framework/Services/StoredFiles/AzureStoredFileService.cs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Abp.Domain.Repositories;
33
using Azure.Storage.Blobs;
44
using Azure.Storage.Blobs.Models;
5+
using Castle.Core.Logging;
56
using Microsoft.AspNetCore.Hosting;
67
using Microsoft.Extensions.Configuration;
78
using Microsoft.Extensions.Hosting;
@@ -20,13 +21,16 @@ public class AzureStoredFileService : StoredFileServiceBase, IStoredFileService
2021
private const string ContainerName = "files";
2122
private readonly IocManager _iocManager;
2223
private readonly IConfigurationRoot _configuration;
23-
private BlobContainerClient? _blobContainerClient;
24+
private readonly ILogger _logger;
25+
private readonly Lazy<BlobContainerClient> _blobContainerClient;
2426

25-
public AzureStoredFileService(IRepository<StoredFile, Guid> fileService, IRepository<StoredFileVersion, Guid> versionService, IRepository<StoredFileVersionDownload, Guid> storedFileVersionDownloadService, IocManager iocManager)
27+
public AzureStoredFileService(IRepository<StoredFile, Guid> fileService, IRepository<StoredFileVersion, Guid> versionService, IRepository<StoredFileVersionDownload, Guid> storedFileVersionDownloadService, IocManager iocManager, ILogger logger)
2628
: base(fileService, versionService, storedFileVersionDownloadService)
2729
{
2830
_iocManager = iocManager;
2931
_configuration = GetConfiguration();
32+
_blobContainerClient = new Lazy<BlobContainerClient>(CreateBlobContainerClient);
33+
_logger = logger;
3034
}
3135

3236
private IConfigurationRoot GetConfiguration()
@@ -38,36 +42,76 @@ private IConfigurationRoot GetConfiguration()
3842
/// <summary>
3943
/// Returns connection string. Note: for the Azure environment - uses standard environment variable
4044
/// </summary>
41-
private string GetConnectionString() => _configuration.GetRequiredConnectionString(ConnectionStringName);
42-
43-
private BlobContainerClient BlobContainerClient
45+
private string GetStorageValue()
4446
{
45-
get
46-
{
47-
// If Container name is not passed from the configs then we use the defaults container name which is 'files'
48-
var containerName = _configuration.GetSection(CloudStorageName).GetValue<string>("ContainerName") ?? ContainerName;
49-
50-
if (_blobContainerClient != null)
51-
return _blobContainerClient;
52-
53-
var containerClient = new BlobContainerClient(GetConnectionString(), containerName);
54-
containerClient.CreateIfNotExists();
47+
var value = _configuration.GetSection(CloudStorageName).GetValue<string>("ConnectionString");
48+
if (string.IsNullOrWhiteSpace(value))
49+
value = _configuration.GetConnectionString(ConnectionStringName) ?? throw new InvalidOperationException("BlobStorage Connection not set.");
50+
return value;
51+
}
5552

56-
// Setup the permissions on the container to be public
57-
containerClient.SetAccessPolicy(PublicAccessType.BlobContainer);
53+
/// <summary>
54+
/// Creates a <see cref="BlobContainerClient"/> by auto-detecting the authentication
55+
/// method from the format of the configured storage value.
56+
/// </summary>
57+
private BlobContainerClient CreateBlobContainerClient()
58+
{
59+
var value = GetStorageValue();
60+
var containerName = _configuration.GetSection(CloudStorageName)
61+
.GetValue<string>("ContainerName") ?? ContainerName;
5862

59-
_blobContainerClient = containerClient;
60-
return _blobContainerClient;
63+
// URL-based auth: SAS token
64+
if (value.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
65+
{
66+
var uri = new Uri(value);
67+
bool hasSasToken = !string.IsNullOrEmpty(uri.Query);
68+
// uri.Segments for https://account.blob.core.windows.net/mycontainer?...
69+
// is ['/', 'mycontainer'] (length 2), indicating container is in the URL path.
70+
bool hasContainerInPath = uri.Segments.Length > 1 &&
71+
!string.IsNullOrEmpty(uri.Segments[1].Trim('/'));
72+
73+
if (!hasSasToken)
74+
throw new InvalidOperationException(
75+
$"The configured storage URL '{uri.Host}' has no SAS token. " +
76+
"Provide a SAS URL (https://…?sv=…&sig=…) or a classic connection string.");
77+
78+
if (hasContainerInPath)
79+
{
80+
// Container-level SAS URL — container name is already in the URI path.
81+
// The ContainerName setting from config is ignored to avoid a mismatch.
82+
_logger.Warn("SAS URL container differs from configured ContainerName. Using URL container.");
83+
return new BlobContainerClient(uri);
84+
}
85+
86+
// Account-level SAS URL — combine with the configured container name.
87+
return new BlobServiceClient(uri).GetBlobContainerClient(containerName);
6188
}
89+
90+
// Classic connection string (AccountKey or Azurite emulator).
91+
// Container is auto-created and set to public blob access on first use.
92+
var client = new BlobContainerClient(value, containerName);
93+
client.CreateIfNotExists();
94+
client.SetAccessPolicy(PublicAccessType.BlobContainer);
95+
return client;
6296
}
6397

98+
private BlobContainerClient BlobContainerClient => _blobContainerClient.Value;
99+
64100
private BlobClient GetBlobClient(string blobName)
65101
{
66102
var directoryName = _configuration.GetSection(CloudStorageName).GetValue<string>("DirectoryName");
67-
return BlobContainerClient.GetBlobClient(Path.Combine(directoryName ?? "", blobName));
103+
104+
var normalizedDirectory = directoryName?.Replace('\\', '/').Trim('/');
105+
106+
var normalizedBlobName = blobName.Replace('\\', '/').TrimStart('/');
107+
108+
var blobPath = string.IsNullOrWhiteSpace(directoryName)
109+
? normalizedBlobName
110+
: $"{normalizedDirectory}/{normalizedBlobName}";
111+
return BlobContainerClient.GetBlobClient(blobPath);
68112
}
69113

70-
private string GetAzureFileName(StoredFileVersion version) => version.Id + version.FileType;
114+
private static string GetAzureFileName(StoredFileVersion version) => version.Id + version.FileType;
71115

72116
private async Task<Stream> GetStreamInternalAsync(string filePath)
73117
{
@@ -109,7 +153,7 @@ public override Stream GetStream(StoredFileVersion fileVersion)
109153
public override async Task UpdateVersionContentAsync(StoredFileVersion version, Stream stream)
110154
{
111155
if (stream == null)
112-
throw new Exception($"{nameof(stream)} must not be null");
156+
throw new ArgumentNullException($"{nameof(stream)} must not be null");
113157

114158
var blob = GetBlobClient(GetAzureFileName(version));
115159

0 commit comments

Comments
 (0)