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