Browse Source

Merge pull request #4200 from abpframework/maliming/BlobStoringAzureProvider

Implement Blob Storing Azure Provider.
pull/4321/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
87cb32955b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      framework/Volo.Abp.sln
  2. 3
      framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml
  3. 30
      framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd
  4. 22
      framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj
  5. 10
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs
  6. 24
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs
  7. 110
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs
  8. 39
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs
  9. 9
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs
  10. 22
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs
  11. 7
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs
  12. 2
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs
  13. 19
      framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj
  14. 20
      framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs
  15. 64
      framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs
  16. 16
      framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs
  17. 57
      framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobNameCalculator_Tests.cs

14
framework/Volo.Abp.sln

@ -301,6 +301,10 @@ 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
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
@ -895,6 +899,14 @@ 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
{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
@ -1047,6 +1059,8 @@ 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}
{A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

3
framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

22
framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring.Azure</AssemblyName>
<PackageId>Volo.Abp.BlobStoring.Azure</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.3" />
</ItemGroup>
</Project>

10
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs

@ -0,0 +1,10 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Azure
{
[DependsOn(typeof(AbpBlobStoringModule))]
public class AbpBlobStoringAzureModule : AbpModule
{
}
}

24
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<AzureBlobProviderConfiguration> azureConfigureAction)
{
containerConfiguration.ProviderType = typeof(AzureBlobProvider);
azureConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}
}

110
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs

@ -0,0 +1,110 @@
using System;
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
{
protected IAzureBlobNameCalculator AzureBlobNameCalculator { get; }
public AzureBlobProvider(IAzureBlobNameCalculator azureBlobNameCalculator)
{
AzureBlobNameCalculator = azureBlobNameCalculator;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
var configuration = args.Configuration.GetAzureConfiguration();
if (!args.OverrideExisting && await BlobExistsAsync(args, blobName))
{
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(args);
}
await GetBlobClient(args, blobName).UploadAsync(args.BlobStream, true);
}
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
if (await BlobExistsAsync(args, blobName))
{
return await GetBlobClient(args, blobName).DeleteIfExistsAsync();
}
return false;
}
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
return await BlobExistsAsync(args, blobName);
}
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
if (!await BlobExistsAsync(args, blobName))
{
return null;
}
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(BlobProviderArgs args, string blobName)
{
var blobContainerClient = GetBlobContainerClient(args);
return blobContainerClient.GetBlobClient(blobName);
}
protected virtual BlobContainerClient GetBlobContainerClient(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
var blobServiceClient = new BlobServiceClient(configuration.ConnectionString);
return blobServiceClient.GetBlobContainerClient(GetContainerName(args));
}
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args)
{
var blobContainerClient = GetBlobContainerClient(args);
await blobContainerClient.CreateIfNotExistsAsync();
}
private async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName)
{
// Make sure Blob Container exists.
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<bool> ContainerExistsAsync(BlobContainerClient blobContainerClient)
{
return (await blobContainerClient.ExistsAsync()).Value;
}
}
}

39
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs

@ -0,0 +1,39 @@
namespace Volo.Abp.BlobStoring.Azure
{
public class AzureBlobProviderConfiguration
{
public string ConnectionString
{
get => _containerConfiguration.GetConfiguration<string>(AzureBlobProviderConfigurationNames.ConnectionString);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// 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 <see cref="BlobProviderArgs"/> will be used.
/// </summary>
public string ContainerName
{
get => _containerConfiguration.GetConfiguration<string>(AzureBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// Default value: false.
/// </summary>
public bool CreateContainerIfNotExists
{
get => _containerConfiguration.GetConfigurationOrDefault(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, false);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
}

9
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.BlobStoring.Azure
{
public static class AzureBlobProviderConfigurationNames
{
public const string ConnectionString = "Azure.ConnectionString";
public const string ContainerName = "Azure.ContainerName";
public const string CreateContainerIfNotExists = "Azure.CreateContainerIfNotExists";
}
}

22
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}";
}
}
}

7
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);
}
}

2
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs

@ -28,4 +28,4 @@ namespace Volo.Abp.BlobStoring
CancellationToken = cancellationToken;
}
}
}
}

19
framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo.Abp.BlobStoring.Azure.Tests.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
<UserSecretsId>9f0d2c00-80c1-435b-bfab-2c39c8249091</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" />
</ItemGroup>
</Project>

20
framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestBase.cs

@ -0,0 +1,20 @@
using Volo.Abp.Testing;
namespace Volo.Abp.BlobStoring.Azure
{
public class AbpBlobStoringAzureTestCommonBase : AbpIntegratedTest<AbpBlobStoringAzureTestCommonModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
public class AbpBlobStoringAzureTestBase : AbpIntegratedTest<AbpBlobStoringAzureTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

64
framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureTestModule.cs

@ -0,0 +1,64 @@
using System;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Azure
{
/// <summary>
/// This module will not try to connect to azure.
/// </summary>
[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";
private string _connectionString;
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(UserSecretsId);
}));
var configuration = context.Services.GetConfiguration();
_connectionString = configuration["Azure:ConnectionString"];
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
containerConfiguration.UseAzure(azure =>
{
azure.ConnectionString = _connectionString;
azure.ContainerName = _randomContainerName;
azure.CreateContainerIfNotExists = true;
});
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
var blobServiceClient = new BlobServiceClient(_connectionString);
blobServiceClient.GetBlobContainerClient(_randomContainerName).DeleteIfExists();
}
}
}

16
framework/test/Volo.Abp.BlobStoring.Azure.Tests/Volo/Abp/BlobStoring/Azure/AzureBlobContainer_Tests.cs

@ -0,0 +1,16 @@
using Xunit;
namespace Volo.Abp.BlobStoring.Azure
{
/*
//Please set the correct connection string in secrets.json and continue the test.
public class AzureBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringAzureTestModule>
{
public AzureBlobContainer_Tests()
{
}
}
*/
}

57
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<IAzureBlobNameCalculator>();
_currentTenant = GetRequiredService<ICurrentTenant>();
}
[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
);
}
}
}
Loading…
Cancel
Save