mirror of https://github.com/abpframework/abp.git
committed by
GitHub
17 changed files with 467 additions and 1 deletions
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.BlobStoring.Azure |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpBlobStoringModule))] |
||||
|
public class AbpBlobStoringAzureModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -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}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.BlobStoring.Azure |
||||
|
{ |
||||
|
public interface IAzureBlobNameCalculator |
||||
|
{ |
||||
|
string Calculate(BlobProviderArgs args); |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
*/ |
||||
|
} |
||||
@ -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…
Reference in new issue