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] 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()
+ {
+
+ }
+ }
+}