From fffa9f92376a964d58e03cc60e079c4b37fd3783 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 3 Jun 2020 15:11:33 +0800 Subject: [PATCH 1/8] Add Volo.Abp.BlobStoring.Azure project. --- framework/Volo.Abp.sln | 7 +++++ .../FodyWeavers.xml | 3 ++ .../FodyWeavers.xsd | 30 +++++++++++++++++++ .../Volo.Abp.BlobStoring.Azure.csproj | 21 +++++++++++++ .../Azure/AbpBlobStoringAzureModule.cs | 12 ++++++++ ...ureBlobContainerConfigurationExtensions.cs | 24 +++++++++++++++ .../BlobStoring/Azure/AzureBlobProvider.cs | 29 ++++++++++++++++++ .../Azure/AzureBlobProviderConfiguration.cs | 12 ++++++++ .../AzureBlobProviderConfigurationNames.cs | 8 +++++ 9 files changed, 146 insertions(+) create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 135afaf4d9..d488b3d5b0 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -301,6 +301,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSy EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -895,6 +897,10 @@ Global {75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Debug|Any CPU.Build.0 = Debug|Any CPU {75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.ActiveCfg = Release|Any CPU {75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.Build.0 = Release|Any CPU + {C44242F7-D55D-4867-AAF4-A786E404312E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C44242F7-D55D-4867-AAF4-A786E404312E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1047,6 +1053,7 @@ Global {02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {68443D4A-1608-4039-B995-7AF4CF82E9F8} = {447C8A77-E5F0-4538-8687-7383196D04EA} {75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj b/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj new file mode 100644 index 0000000000..f7e004cfc8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Volo.Abp.BlobStoring.Azure + Volo.Abp.BlobStoring.Azure + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs new file mode 100644 index 0000000000..a7a935abf5 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Azure +{ + [DependsOn( + typeof(AbpBlobStoringModule) + )] + public class AbpBlobStoringAzureModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..917870aa9d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Volo.Abp.BlobStoring.Azure +{ + public static class AzureBlobContainerConfigurationExtensions + { + public static AzureBlobProviderConfiguration GetAzureConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new AzureBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseAzure( + this BlobContainerConfiguration containerConfiguration, + Action fileSystemConfigureAction) + { + containerConfiguration.ProviderType = typeof(AzureBlobProvider); + + fileSystemConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs new file mode 100644 index 0000000000..895fa5c72f --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -0,0 +1,29 @@ +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobProvider : BlobProviderBase, ITransientDependency + { + public override Task SaveAsync(BlobProviderSaveArgs args) + { + throw new System.NotImplementedException(); + } + + public override Task DeleteAsync(BlobProviderDeleteArgs args) + { + throw new System.NotImplementedException(); + } + + public override Task ExistsAsync(BlobProviderExistsArgs args) + { + throw new System.NotImplementedException(); + } + + public override Task GetOrNullAsync(BlobProviderGetArgs args) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs new file mode 100644 index 0000000000..53352661f9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobProviderConfiguration + { + private readonly BlobContainerConfiguration _containerConfiguration; + + public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..8452b0531f --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobProviderConfigurationNames + { + + + } +} From 44521a6bd30ba1e6385ab79017665ffa93c40d1a Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 3 Jun 2020 18:25:38 +0800 Subject: [PATCH 2/8] Implement azure blob storage. --- framework/Volo.Abp.sln | 7 +++ .../Volo.Abp.BlobStoring.Azure.csproj | 1 + .../BlobStoring/Azure/AzureBlobProvider.cs | 59 ++++++++++++++++--- .../Azure/AzureBlobProviderConfiguration.cs | 12 ++++ .../AzureBlobProviderConfigurationNames.cs | 6 +- .../Volo/Abp/BlobStoring/BlobProviderArgs.cs | 2 +- .../System/IO/AbpStreamExtensions.cs | 3 + .../Volo.Abp.BlobStoring.Azure.Tests.csproj | 19 ++++++ .../Azure/AbpBlobStoringAzureTestBase.cs | 12 ++++ .../Azure/AbpBlobStoringAzureTestModule.cs | 49 +++++++++++++++ .../Azure/AzureBlobContainer_Tests.cs | 12 ++++ 11 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj create mode 100644 framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs create mode 100644 framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs create mode 100644 framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index d488b3d5b0..e02d40b1e0 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -303,6 +303,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -901,6 +903,10 @@ Global {C44242F7-D55D-4867-AAF4-A786E404312E}.Debug|Any CPU.Build.0 = Debug|Any CPU {C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.Build.0 = Release|Any CPU + {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1054,6 +1060,7 @@ Global {68443D4A-1608-4039-B995-7AF4CF82E9F8} = {447C8A77-E5F0-4538-8687-7383196D04EA} {75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj b/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj index f7e004cfc8..1e7c51f4fc 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index 895fa5c72f..d43fbb2158 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -1,29 +1,72 @@ using System.IO; using System.Threading.Tasks; +using Azure.Storage.Blobs; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BlobStoring.Azure { public class AzureBlobProvider : BlobProviderBase, ITransientDependency { - public override Task SaveAsync(BlobProviderSaveArgs args) + public override async Task SaveAsync(BlobProviderSaveArgs args) { - throw new System.NotImplementedException(); + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + + if (!args.OverrideExisting && await BlobExistsAsync(blobClient)) + { + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + await blobClient.UploadAsync(args.BlobStream, true); + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + if (await BlobExistsAsync(blobClient)) + { + return (await blobClient.DeleteAsync()).Status == 200; + } + + return false; + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + return await BlobExistsAsync(blobClient); + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + if (!await BlobExistsAsync(blobClient)) + { + return null; + } + + var download = await blobClient.DownloadAsync(); + + var memoryStream = new MemoryStream(); + await download.Value.Content.CopyToAsync(memoryStream); + return memoryStream; } - public override Task DeleteAsync(BlobProviderDeleteArgs args) + protected virtual BlobClient GetBlobClient(AzureBlobProviderConfiguration configuration, string blobName) { - throw new System.NotImplementedException(); + var blobContainerClient = GetBlobContainerClient(configuration); + return blobContainerClient.GetBlobClient(blobName); } - public override Task ExistsAsync(BlobProviderExistsArgs args) + protected virtual BlobContainerClient GetBlobContainerClient(AzureBlobProviderConfiguration configuration) { - throw new System.NotImplementedException(); + var blobServiceClient = new BlobServiceClient(configuration.ConnectionString); + return blobServiceClient.GetBlobContainerClient(configuration.ContainerName); } - public override Task GetOrNullAsync(BlobProviderGetArgs args) + private static async Task BlobExistsAsync(BlobClient blobClient) { - throw new System.NotImplementedException(); + var response = await blobClient.ExistsAsync(); + return response.Value; } } } diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs index 53352661f9..67f6eca307 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs @@ -2,6 +2,18 @@ { public class AzureBlobProviderConfiguration { + public string ConnectionString + { + get => _containerConfiguration.GetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString); + set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + public string ContainerName + { + get => _containerConfiguration.GetConfiguration(AzureBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + private readonly BlobContainerConfiguration _containerConfiguration; public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs index 8452b0531f..f90f037046 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs @@ -1,8 +1,8 @@ namespace Volo.Abp.BlobStoring.Azure { - public class AzureBlobProviderConfigurationNames + public static class AzureBlobProviderConfigurationNames { - - + public const string ConnectionString = "Azure.ConnectionString"; + public const string ContainerName = "Azure.ContainerName"; } } diff --git a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs index fcc264977a..0170409a16 100644 --- a/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs +++ b/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs @@ -28,4 +28,4 @@ namespace Volo.Abp.BlobStoring CancellationToken = cancellationToken; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs b/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs index 466d73b2a7..bd85773a9b 100644 --- a/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs @@ -9,6 +9,7 @@ namespace System.IO { using (var memoryStream = new MemoryStream()) { + stream.Position = 0; stream.CopyTo(memoryStream); return memoryStream.ToArray(); } @@ -18,6 +19,7 @@ namespace System.IO { using (var memoryStream = new MemoryStream()) { + stream.Position = 0; await stream.CopyToAsync(memoryStream, cancellationToken); return memoryStream.ToArray(); } @@ -25,6 +27,7 @@ namespace System.IO public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken) { + stream.Position = 0; return stream.CopyToAsync( destination, 81920, //this is already the default value, but needed to set to be able to pass the cancellationToken diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj new file mode 100644 index 0000000000..ff97ea97d3 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj @@ -0,0 +1,19 @@ + + + + + + netcoreapp3.1 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs new file mode 100644 index 0000000000..7213c8eb78 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class AbpBlobStoringAzureTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs new file mode 100644 index 0000000000..354b8b93b6 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs @@ -0,0 +1,49 @@ +using System; +using Azure.Storage.Blobs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Azure +{ + [DependsOn( + typeof(AbpBlobStoringAzureModule), + typeof(AbpBlobStoringTestModule) + )] + public class AbpBlobStoringAzureTestModule : AbpModule + { + private readonly string _randomContainerName = "abp-azure-test-container-" + Guid.NewGuid().ToString("N"); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets("9f0d2c00-80c1-435b-bfab-2c39c8249091"); + })); + + var configuration = context.Services.GetConfiguration(); + + var blobServiceClient = new BlobServiceClient(configuration["Azure:ConnectionString"]); + blobServiceClient.CreateBlobContainer(_randomContainerName); + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseAzure(azure => + { + azure.ConnectionString = configuration["Azure:ConnectionString"]; + azure.ContainerName = _randomContainerName; + }); + }); + }); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + var configuration = context.ServiceProvider.GetRequiredService(); + var blobServiceClient = new BlobServiceClient(configuration["Azure:ConnectionString"]); + blobServiceClient.DeleteBlobContainer(_randomContainerName); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs new file mode 100644 index 0000000000..f4e820d46f --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobContainer_Tests : BlobContainer_Tests + { + public AzureBlobContainer_Tests() + { + + } + } +} From 0e2e5629aa9370c5f548fdacf1bcba1d4c70f17e Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 3 Jun 2020 18:44:39 +0800 Subject: [PATCH 3/8] Skip azure unit testing. --- .../Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs | 4 +++- .../Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs index 354b8b93b6..4a1b0f8114 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs @@ -12,13 +12,15 @@ namespace Volo.Abp.BlobStoring.Azure )] public class AbpBlobStoringAzureTestModule : AbpModule { + public static string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + private readonly string _randomContainerName = "abp-azure-test-container-" + Guid.NewGuid().ToString("N"); public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => { - builder.AddUserSecrets("9f0d2c00-80c1-435b-bfab-2c39c8249091"); + builder.AddUserSecrets(UserSecretsId); })); var configuration = context.Services.GetConfiguration(); diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs index f4e820d46f..75070d2cac 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs @@ -1,7 +1,9 @@ using Xunit; +//#define I_HAVE_SET_THE_CORRECT_CONNECTIONSTRING_IN_THE_USERSECRETS_FILE namespace Volo.Abp.BlobStoring.Azure { +#if I_HAVE_SET_THE_CORRECT_CONNECTION_STRING_IN_THE_USERSECRETS_FILE public class AzureBlobContainer_Tests : BlobContainer_Tests { public AzureBlobContainer_Tests() @@ -9,4 +11,5 @@ namespace Volo.Abp.BlobStoring.Azure } } +#endif } From 3acc5c8c85eb3c60a56d6bf01ed018e8abf8cfb4 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 3 Jun 2020 19:05:23 +0800 Subject: [PATCH 4/8] Small refactoring. --- .../BlobStoring/Azure/AbpBlobStoringAzureModule.cs | 4 +--- .../Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs | 4 +--- .../Azure/AbpBlobStoringAzureTestModule.cs | 12 ++++++++---- .../BlobStoring/Azure/AzureBlobContainer_Tests.cs | 7 ++++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs index a7a935abf5..0b386fbec1 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs @@ -2,9 +2,7 @@ namespace Volo.Abp.BlobStoring.Azure { - [DependsOn( - typeof(AbpBlobStoringModule) - )] + [DependsOn(typeof(AbpBlobStoringModule))] public class AbpBlobStoringAzureModule : AbpModule { diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index d43fbb2158..eb69bb802a 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -45,7 +45,6 @@ namespace Volo.Abp.BlobStoring.Azure } var download = await blobClient.DownloadAsync(); - var memoryStream = new MemoryStream(); await download.Value.Content.CopyToAsync(memoryStream); return memoryStream; @@ -65,8 +64,7 @@ namespace Volo.Abp.BlobStoring.Azure private static async Task BlobExistsAsync(BlobClient blobClient) { - var response = await blobClient.ExistsAsync(); - return response.Value; + return (await blobClient.ExistsAsync()).Value; } } } diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs index 4a1b0f8114..4711699726 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs @@ -12,7 +12,9 @@ namespace Volo.Abp.BlobStoring.Azure )] public class AbpBlobStoringAzureTestModule : AbpModule { - public static string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private string _connectionString; private readonly string _randomContainerName = "abp-azure-test-container-" + Guid.NewGuid().ToString("N"); @@ -25,7 +27,9 @@ namespace Volo.Abp.BlobStoring.Azure var configuration = context.Services.GetConfiguration(); - var blobServiceClient = new BlobServiceClient(configuration["Azure:ConnectionString"]); + _connectionString = configuration["Azure:ConnectionString"]; + + var blobServiceClient = new BlobServiceClient(_connectionString); blobServiceClient.CreateBlobContainer(_randomContainerName); Configure(options => @@ -34,7 +38,7 @@ namespace Volo.Abp.BlobStoring.Azure { containerConfiguration.UseAzure(azure => { - azure.ConnectionString = configuration["Azure:ConnectionString"]; + azure.ConnectionString = _connectionString; azure.ContainerName = _randomContainerName; }); }); @@ -44,7 +48,7 @@ namespace Volo.Abp.BlobStoring.Azure public override void OnApplicationShutdown(ApplicationShutdownContext context) { var configuration = context.ServiceProvider.GetRequiredService(); - var blobServiceClient = new BlobServiceClient(configuration["Azure:ConnectionString"]); + var blobServiceClient = new BlobServiceClient(_connectionString); blobServiceClient.DeleteBlobContainer(_randomContainerName); } } diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs index 75070d2cac..751918be85 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs @@ -1,9 +1,10 @@ using Xunit; -//#define I_HAVE_SET_THE_CORRECT_CONNECTIONSTRING_IN_THE_USERSECRETS_FILE namespace Volo.Abp.BlobStoring.Azure { -#if I_HAVE_SET_THE_CORRECT_CONNECTION_STRING_IN_THE_USERSECRETS_FILE + /* + //Please set the correct connection string in secrets.json and continue the test. + public class AzureBlobContainer_Tests : BlobContainer_Tests { public AzureBlobContainer_Tests() @@ -11,5 +12,5 @@ namespace Volo.Abp.BlobStoring.Azure } } -#endif + */ } From 057c4c1a250c3d14471ef87a6b9b9db1ce04b46f Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Thu, 4 Jun 2020 15:35:43 +0800 Subject: [PATCH 5/8] Compatible with multi-tenancy. --- ...ureBlobContainerConfigurationExtensions.cs | 4 +- .../BlobStoring/Azure/AzureBlobProvider.cs | 29 ++++++---- .../Azure/DefaultAzureBlobNameCalculator.cs | 22 +++++++ .../Azure/IAzureBlobNameCalculator.cs | 7 +++ .../Azure/AbpBlobStoringAzureTestBase.cs | 8 +++ .../Azure/AbpBlobStoringAzureTestModule.cs | 14 ++++- .../Azure/AzureBlobNameCalculator_Tests.cs | 57 +++++++++++++++++++ 7 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs create mode 100644 framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs create mode 100644 framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobNameCalculator_Tests.cs diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs index 917870aa9d..945c930175 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs @@ -12,11 +12,11 @@ namespace Volo.Abp.BlobStoring.Azure public static BlobContainerConfiguration UseAzure( this BlobContainerConfiguration containerConfiguration, - Action fileSystemConfigureAction) + Action azureConfigureAction) { containerConfiguration.ProviderType = typeof(AzureBlobProvider); - fileSystemConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration)); + azureConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration)); return containerConfiguration; } diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index eb69bb802a..e0b70dc66c 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -1,15 +1,24 @@ using System.IO; using System.Threading.Tasks; using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Specialized; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BlobStoring.Azure { public class AzureBlobProvider : BlobProviderBase, ITransientDependency { + protected IAzureBlobNameCalculator AzureBlobNameCalculator { get; } + + public AzureBlobProvider(IAzureBlobNameCalculator azureBlobNameCalculator) + { + AzureBlobNameCalculator = azureBlobNameCalculator; + } + public override async Task SaveAsync(BlobProviderSaveArgs args) { - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + var blobName = AzureBlobNameCalculator.Calculate(args); + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); if (!args.OverrideExisting && await BlobExistsAsync(blobClient)) { @@ -21,24 +30,22 @@ namespace Volo.Abp.BlobStoring.Azure public override async Task DeleteAsync(BlobProviderDeleteArgs args) { - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); - if (await BlobExistsAsync(blobClient)) - { - return (await blobClient.DeleteAsync()).Status == 200; - } - - return false; + var blobName = AzureBlobNameCalculator.Calculate(args); + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); + return await blobClient.DeleteIfExistsAsync(); } public override async Task ExistsAsync(BlobProviderExistsArgs args) { - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + var blobName = AzureBlobNameCalculator.Calculate(args); + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); return await BlobExistsAsync(blobClient); } public override async Task GetOrNullAsync(BlobProviderGetArgs args) { - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), args.BlobName); + var blobName = AzureBlobNameCalculator.Calculate(args); + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); if (!await BlobExistsAsync(blobClient)) { return null; @@ -62,7 +69,7 @@ namespace Volo.Abp.BlobStoring.Azure return blobServiceClient.GetBlobContainerClient(configuration.ContainerName); } - private static async Task BlobExistsAsync(BlobClient blobClient) + private static async Task BlobExistsAsync(BlobBaseClient blobClient) { return (await blobClient.ExistsAsync()).Value; } diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs new file mode 100644 index 0000000000..0c6cd481c9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs @@ -0,0 +1,22 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class DefaultAzureBlobNameCalculator : IAzureBlobNameCalculator, ITransientDependency + { + protected ICurrentTenant CurrentTenant { get; } + + public DefaultAzureBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs new file mode 100644 index 0000000000..5985a9278d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Azure +{ + public interface IAzureBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs index 7213c8eb78..8941234cbf 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs @@ -2,6 +2,14 @@ namespace Volo.Abp.BlobStoring.Azure { + public class AbpBlobStoringAzureTestCommonBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } + public class AbpBlobStoringAzureTestBase : AbpIntegratedTest { protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs index 4711699726..a45d209b32 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs @@ -6,10 +6,22 @@ using Volo.Abp.Modularity; namespace Volo.Abp.BlobStoring.Azure { + + /// + /// This module will not try to connect to azure. + /// [DependsOn( typeof(AbpBlobStoringAzureModule), typeof(AbpBlobStoringTestModule) )] + public class AbpBlobStoringAzureTestCommonModule : AbpModule + { + + } + + [DependsOn( + typeof(AbpBlobStoringAzureTestCommonModule) + )] public class AbpBlobStoringAzureTestModule : AbpModule { private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; @@ -47,9 +59,9 @@ namespace Volo.Abp.BlobStoring.Azure public override void OnApplicationShutdown(ApplicationShutdownContext context) { - var configuration = context.ServiceProvider.GetRequiredService(); var blobServiceClient = new BlobServiceClient(_connectionString); blobServiceClient.DeleteBlobContainer(_randomContainerName); } } + } diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..4540d208ae --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobNameCalculator_Tests.cs @@ -0,0 +1,57 @@ +using System; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Azure +{ + public class AzureBlobNameCalculator_Tests : AbpBlobStoringAzureTestCommonBase + { + private readonly IAzureBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string AzureContainerName = "/"; + private const string AzureSeparator = "/"; + + public AzureBlobNameCalculator_Tests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{AzureSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{AzureSeparator}{tenantId:D}{AzureSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseAzure(x => + { + x.ContainerName = containerName; + }), + blobName + ); + } + } +} From 3932ea3709a8a404ba365fe617492da39b9c2a08 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 5 Jun 2020 18:37:56 +0800 Subject: [PATCH 6/8] Support CreateContainerIfNotExists. --- .../BlobStoring/Azure/AzureBlobProvider.cs | 45 ++++++++++++++----- .../Azure/AzureBlobProviderConfiguration.cs | 9 ++++ .../AzureBlobProviderConfigurationNames.cs | 1 + .../Azure/AbpBlobStoringAzureTestModule.cs | 7 +-- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index e0b70dc66c..ec854b7be3 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -18,39 +18,49 @@ namespace Volo.Abp.BlobStoring.Azure public override async Task SaveAsync(BlobProviderSaveArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); + var configuration = args.Configuration.GetAzureConfiguration(); - if (!args.OverrideExisting && await BlobExistsAsync(blobClient)) + if (!args.OverrideExisting && await BlobExistsAsync(configuration, blobName)) { throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); } - await blobClient.UploadAsync(args.BlobStream, true); + if (configuration.CreateContainerIfNotExists) + { + await CreateContainerIfNotExists(args.Configuration.GetAzureConfiguration()); + } + + await GetBlobClient(configuration, blobName).UploadAsync(args.BlobStream, true); } public override async Task DeleteAsync(BlobProviderDeleteArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); - return await blobClient.DeleteIfExistsAsync(); + + if (await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName)) + { + return await GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName).DeleteIfExistsAsync(); + } + + return false; } public override async Task ExistsAsync(BlobProviderExistsArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); - return await BlobExistsAsync(blobClient); + return await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName); } public override async Task GetOrNullAsync(BlobProviderGetArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); - if (!await BlobExistsAsync(blobClient)) + + if (!await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName)) { return null; } + var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); var download = await blobClient.DownloadAsync(); var memoryStream = new MemoryStream(); await download.Value.Content.CopyToAsync(memoryStream); @@ -69,9 +79,22 @@ namespace Volo.Abp.BlobStoring.Azure return blobServiceClient.GetBlobContainerClient(configuration.ContainerName); } - private static async Task BlobExistsAsync(BlobBaseClient blobClient) + protected virtual async Task CreateContainerIfNotExists(AzureBlobProviderConfiguration configuration) + { + var blobContainerClient = GetBlobContainerClient(configuration); + await blobContainerClient.CreateIfNotExistsAsync(); + } + + private async Task BlobExistsAsync(AzureBlobProviderConfiguration configuration, string blobName) + { + // Make sure Blob Container exists. + return await ContainerExistsAsync(GetBlobContainerClient(configuration)) && + (await GetBlobClient(configuration, blobName).ExistsAsync()).Value; + } + + private static async Task ContainerExistsAsync(BlobContainerClient blobContainerClient) { - return (await blobClient.ExistsAsync()).Value; + return (await blobContainerClient.ExistsAsync()).Value; } } } diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs index 67f6eca307..a0906ed31f 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs @@ -14,6 +14,15 @@ set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); } + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + private readonly BlobContainerConfiguration _containerConfiguration; public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs index f90f037046..b8fbff19d2 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs @@ -4,5 +4,6 @@ { public const string ConnectionString = "Azure.ConnectionString"; public const string ContainerName = "Azure.ContainerName"; + public const string CreateContainerIfNotExists = "Azure.CreateContainerIfNotExists"; } } diff --git a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs index a45d209b32..5e66be4e19 100644 --- a/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs @@ -38,12 +38,8 @@ namespace Volo.Abp.BlobStoring.Azure })); var configuration = context.Services.GetConfiguration(); - _connectionString = configuration["Azure:ConnectionString"]; - var blobServiceClient = new BlobServiceClient(_connectionString); - blobServiceClient.CreateBlobContainer(_randomContainerName); - Configure(options => { options.Containers.ConfigureAll((containerName, containerConfiguration) => @@ -52,6 +48,7 @@ namespace Volo.Abp.BlobStoring.Azure { azure.ConnectionString = _connectionString; azure.ContainerName = _randomContainerName; + azure.CreateContainerIfNotExists = true; }); }); }); @@ -60,7 +57,7 @@ namespace Volo.Abp.BlobStoring.Azure public override void OnApplicationShutdown(ApplicationShutdownContext context) { var blobServiceClient = new BlobServiceClient(_connectionString); - blobServiceClient.DeleteBlobContainer(_randomContainerName); + blobServiceClient.GetBlobContainerClient(_randomContainerName).DeleteIfExists(); } } From f1f3413274eb0eea8c32e8b891182e1a638fb050 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 10 Jun 2020 11:20:18 +0800 Subject: [PATCH 7/8] Reuse azure configuration. --- .../Abp/BlobStoring/Azure/AzureBlobProvider.cs | 18 +++++++++++------- .../Azure/AzureBlobProviderConfiguration.cs | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index ec854b7be3..af5150c0a9 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -22,12 +22,12 @@ namespace Volo.Abp.BlobStoring.Azure if (!args.OverrideExisting && await BlobExistsAsync(configuration, blobName)) { - throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{configuration.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); } if (configuration.CreateContainerIfNotExists) { - await CreateContainerIfNotExists(args.Configuration.GetAzureConfiguration()); + await CreateContainerIfNotExists(configuration); } await GetBlobClient(configuration, blobName).UploadAsync(args.BlobStream, true); @@ -36,10 +36,11 @@ namespace Volo.Abp.BlobStoring.Azure public override async Task DeleteAsync(BlobProviderDeleteArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetAzureConfiguration(); - if (await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName)) + if (await BlobExistsAsync(configuration, blobName)) { - return await GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName).DeleteIfExistsAsync(); + return await GetBlobClient(configuration, blobName).DeleteIfExistsAsync(); } return false; @@ -48,19 +49,22 @@ namespace Volo.Abp.BlobStoring.Azure public override async Task ExistsAsync(BlobProviderExistsArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - return await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName); + var configuration = args.Configuration.GetAzureConfiguration(); + + return await BlobExistsAsync(configuration, blobName); } public override async Task GetOrNullAsync(BlobProviderGetArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetAzureConfiguration(); - if (!await BlobExistsAsync(args.Configuration.GetAzureConfiguration(), blobName)) + if (!await BlobExistsAsync(configuration, blobName)) { return null; } - var blobClient = GetBlobClient(args.Configuration.GetAzureConfiguration(), blobName); + var blobClient = GetBlobClient(configuration, blobName); var download = await blobClient.DownloadAsync(); var memoryStream = new MemoryStream(); await download.Value.Content.CopyToAsync(memoryStream); diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs index a0906ed31f..8c232b7729 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs @@ -8,6 +8,11 @@ set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString, Check.NotNullOrWhiteSpace(value, nameof(value))); } + /// + /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. + /// Each hyphen must be preceded and followed by a non-hyphen character. + /// The name must also be between 3 and 63 characters long. + /// public string ContainerName { get => _containerConfiguration.GetConfiguration(AzureBlobProviderConfigurationNames.ContainerName); From decdfde0a563437edacc531616f8bc79eac45526 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:08:10 +0800 Subject: [PATCH 8/8] Use ContainerName of the BlobProviderArgs if ContainerName of the AzureBlobProviderConfiguration is not specified. --- .../BlobStoring/Azure/AzureBlobProvider.cs | 52 +++++++++++-------- .../Azure/AzureBlobProviderConfiguration.cs | 1 + 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs index af5150c0a9..1afc3b6a53 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs @@ -1,7 +1,7 @@ -using System.IO; +using System; +using System.IO; using System.Threading.Tasks; using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Specialized; using Volo.Abp.DependencyInjection; namespace Volo.Abp.BlobStoring.Azure @@ -20,27 +20,26 @@ namespace Volo.Abp.BlobStoring.Azure var blobName = AzureBlobNameCalculator.Calculate(args); var configuration = args.Configuration.GetAzureConfiguration(); - if (!args.OverrideExisting && await BlobExistsAsync(configuration, blobName)) + if (!args.OverrideExisting && await BlobExistsAsync(args, blobName)) { - throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{configuration.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); } if (configuration.CreateContainerIfNotExists) { - await CreateContainerIfNotExists(configuration); + await CreateContainerIfNotExists(args); } - await GetBlobClient(configuration, blobName).UploadAsync(args.BlobStream, true); + await GetBlobClient(args, blobName).UploadAsync(args.BlobStream, true); } public override async Task DeleteAsync(BlobProviderDeleteArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var configuration = args.Configuration.GetAzureConfiguration(); - if (await BlobExistsAsync(configuration, blobName)) + if (await BlobExistsAsync(args, blobName)) { - return await GetBlobClient(configuration, blobName).DeleteIfExistsAsync(); + return await GetBlobClient(args, blobName).DeleteIfExistsAsync(); } return false; @@ -49,51 +48,58 @@ namespace Volo.Abp.BlobStoring.Azure public override async Task ExistsAsync(BlobProviderExistsArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var configuration = args.Configuration.GetAzureConfiguration(); - return await BlobExistsAsync(configuration, blobName); + return await BlobExistsAsync(args, blobName); } public override async Task GetOrNullAsync(BlobProviderGetArgs args) { var blobName = AzureBlobNameCalculator.Calculate(args); - var configuration = args.Configuration.GetAzureConfiguration(); - if (!await BlobExistsAsync(configuration, blobName)) + if (!await BlobExistsAsync(args, blobName)) { return null; } - var blobClient = GetBlobClient(configuration, blobName); + var blobClient = GetBlobClient(args, blobName); var download = await blobClient.DownloadAsync(); var memoryStream = new MemoryStream(); await download.Value.Content.CopyToAsync(memoryStream); return memoryStream; } - protected virtual BlobClient GetBlobClient(AzureBlobProviderConfiguration configuration, string blobName) + protected virtual BlobClient GetBlobClient(BlobProviderArgs args, string blobName) { - var blobContainerClient = GetBlobContainerClient(configuration); + var blobContainerClient = GetBlobContainerClient(args); return blobContainerClient.GetBlobClient(blobName); } - protected virtual BlobContainerClient GetBlobContainerClient(AzureBlobProviderConfiguration configuration) + protected virtual BlobContainerClient GetBlobContainerClient(BlobProviderArgs args) { + var configuration = args.Configuration.GetAzureConfiguration(); var blobServiceClient = new BlobServiceClient(configuration.ConnectionString); - return blobServiceClient.GetBlobContainerClient(configuration.ContainerName); + return blobServiceClient.GetBlobContainerClient(GetContainerName(args)); } - protected virtual async Task CreateContainerIfNotExists(AzureBlobProviderConfiguration configuration) + protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args) { - var blobContainerClient = GetBlobContainerClient(configuration); + var blobContainerClient = GetBlobContainerClient(args); await blobContainerClient.CreateIfNotExistsAsync(); } - private async Task BlobExistsAsync(AzureBlobProviderConfiguration configuration, string blobName) + private async Task BlobExistsAsync(BlobProviderArgs args, string blobName) { // Make sure Blob Container exists. - return await ContainerExistsAsync(GetBlobContainerClient(configuration)) && - (await GetBlobClient(configuration, blobName).ExistsAsync()).Value; + return await ContainerExistsAsync(GetBlobContainerClient(args)) && + (await GetBlobClient(args, blobName).ExistsAsync()).Value; + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAzureConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.ContainerName; } private static async Task ContainerExistsAsync(BlobContainerClient blobContainerClient) diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs index 8c232b7729..7ab338794b 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs @@ -12,6 +12,7 @@ /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. /// Each hyphen must be preceded and followed by a non-hyphen character. /// The name must also be between 3 and 63 characters long. + /// If this parameter is not specified, the ContainerName of the will be used. /// public string ContainerName {