From 626105cf0c8ad94119bcb5667656ea7062ca82f8 Mon Sep 17 00:00:00 2001 From: SergioMatias94 Date: Mon, 9 Jun 2025 00:39:20 -0600 Subject: [PATCH] Implement azurite --- .../Configuration/RegisterBlueprint.cs | 6 ++ .../Contracts/IBlobStorageProvider.cs | 2 +- .../Provider/BlobStorageProvider.cs | 72 +++++++++++++++++-- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs b/Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs index ee4997f..3dcf087 100644 --- a/Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs +++ b/Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs @@ -22,6 +22,12 @@ namespace Core.Blueprint.Storage.Configuration { if (environment == "Local") { + var accountKey = configuration.GetSection("BlobStorage:AccountKey").Value; + var accountName = configuration.GetSection("BlobStorage:AccountName").Value; + + if(string.IsNullOrEmpty(accountKey) && string.IsNullOrEmpty(accountName)) + throw new ArgumentException("The BlobStorage configuration section is missing or empty."); + cfg.AddBlobServiceClient(configuration.GetConnectionString("BlobStorage")); } else diff --git a/Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs b/Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs index f47a099..62fee8a 100644 --- a/Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs +++ b/Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs @@ -162,7 +162,7 @@ namespace Core.Blueprint.Storage.Contracts /// /// Thrown if is null or empty. /// Thrown if there is an issue communicating with the Azure Blob service. - BlobDownloadUriAdapter GenerateBlobDownloadUri(string blobName); + ValueTask GenerateBlobDownloadUri(string blobName); /// /// Retrieves the hierarchical folder structure. diff --git a/Core.Blueprint.Storage/Provider/BlobStorageProvider.cs b/Core.Blueprint.Storage/Provider/BlobStorageProvider.cs index 5069a42..ffc753a 100644 --- a/Core.Blueprint.Storage/Provider/BlobStorageProvider.cs +++ b/Core.Blueprint.Storage/Provider/BlobStorageProvider.cs @@ -1,4 +1,5 @@ using Azure; +using Azure.Storage; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; @@ -6,6 +7,7 @@ using Azure.Storage.Sas; using Core.Blueprint.Storage.Adapters; using Core.Blueprint.Storage.Contracts; using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; namespace Core.Blueprint.Storage.Provider { @@ -15,10 +17,12 @@ namespace Core.Blueprint.Storage.Provider private readonly BlobContainerClient _blobContainerClient; private readonly string _containerName; private readonly Trie _trie = new Trie(); + private readonly IConfiguration _configuration; public BlobStorageProvider(BlobServiceClient blobServiceClient, IConfiguration configuration) { _blobServiceClient = blobServiceClient; + _configuration = configuration; _containerName = configuration.GetSection("BlobStorage:ContainerName").Value ?? ""; if (string.IsNullOrEmpty(_containerName)) @@ -278,7 +282,8 @@ namespace Core.Blueprint.Storage.Provider /// /// The name of the blob for which the download URI is being generated. /// - /// An instance of containing the generated URI, blob name, and status. + /// An instance of containing the generated URI, blob name, and status, + /// or null if the blob does not exist. /// /// /// The generated URI includes a Shared Access Signature (SAS) token, which allows secure, time-limited access to the blob. @@ -286,22 +291,36 @@ namespace Core.Blueprint.Storage.Provider /// /// Thrown if is null or empty. /// Thrown if there is an issue communicating with the Azure Blob service. - public BlobDownloadUriAdapter GenerateBlobDownloadUri(string blobName) + public async ValueTask GenerateBlobDownloadUri(string blobName) { - var delegationKey = _blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow, - DateTimeOffset.UtcNow.AddHours(2)); + if (string.IsNullOrWhiteSpace(blobName)) + throw new ArgumentNullException(nameof(blobName), "Blob name cannot be null or empty."); var blob = _blobContainerClient.GetBlobClient(blobName); - var sasBuilder = new BlobSasBuilder() + if (!await blob.ExistsAsync()) + return null; + + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty; + + if (environment == "Local") + { + return GenerateDownloadUri(blob); + } + + var delegationKey = await _blobServiceClient.GetUserDelegationKeyAsync( + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddHours(2)); + + var sasBuilder = new BlobSasBuilder { BlobContainerName = blob.BlobContainerName, BlobName = blob.Name, Resource = "b", StartsOn = DateTimeOffset.UtcNow, - ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5), + ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5) }; - sasBuilder.SetPermissions(BlobAccountSasPermissions.Read); + sasBuilder.SetPermissions(BlobSasPermissions.Read); sasBuilder.Protocol = SasProtocol.Https; var blobUriBuilder = new BlobUriBuilder(blob.Uri) @@ -317,6 +336,45 @@ namespace Core.Blueprint.Storage.Provider }; } + + /// + /// Generates a download URI for a blob using a Shared Access Signature in local (Azurite) environment. + /// + /// The blob client for which the URI is being generated. + /// An instance of containing the SAS URI and metadata. + private BlobDownloadUriAdapter GenerateDownloadUri(BlobClient blob) + { + var sasBuilder = new BlobSasBuilder + { + BlobContainerName = blob.BlobContainerName, + BlobName = blob.Name, + Resource = "b", + StartsOn = DateTimeOffset.UtcNow, + ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5) + }; + sasBuilder.SetPermissions(BlobSasPermissions.Read); + sasBuilder.Protocol = SasProtocol.HttpsAndHttp; + + var accountName = _configuration["BlobStorage:AccountName"]; + var accountKey = _configuration["BlobStorage:AccountKey"]; + + var storageCredentials = new StorageSharedKeyCredential(accountName, accountKey); + var sasToken = sasBuilder.ToSasQueryParameters(storageCredentials); + + var blobUriBuilder = new BlobUriBuilder(blob.Uri) + { + Sas = sasToken + }; + + return new BlobDownloadUriAdapter + { + Uri = blobUriBuilder.ToUri(), + Name = blob.Name, + Status = "Available" + }; + } + + /// /// Retrieves the hierarchical folder structure. ///