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