Browse Source

chore: resolve conversation https://github.com/abpframework/abp/pull/4735

pull/4735/head
shaoxiaoxu 6 years ago
parent
commit
30f36aca8f
  1. 2
      framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml
  2. 3
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs
  3. 5
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs
  4. 58
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  5. 39
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  6. 1
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs
  7. 30
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs
  8. 66
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs
  9. 7
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Properties/serviceDependencies.json
  10. 7
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Properties/serviceDependencies.local.json
  11. 4
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj
  12. 18
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs
  13. 35
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs
  14. 8
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs
  15. 7
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/MyTestContainer.cs

2
framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml

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

3
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs

@ -3,9 +3,6 @@ using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Aliyun
{
/// <summary>
/// https://help.aliyun.com/document_detail/31817.html
/// </summary>
[DependsOn(
typeof(AbpBlobStoringModule),
typeof(AbpCachingModule)

5
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs

@ -9,8 +9,6 @@ namespace Volo.Abp.BlobStoring.Aliyun
/// Container names can contain only letters, numbers, and the dash (-) character
/// they can't start or end with the dash (-) character
/// Container names must be from 3 through 63 characters long
/// 只允许小写字母、数字、短横线(-),且不能以短横线开头或结尾
/// 3~63 个字符
/// </summary>
public virtual string NormalizeContainerName(string containerName)
{
@ -42,9 +40,6 @@ namespace Volo.Abp.BlobStoring.Aliyun
containerName = containerName.Substring(0, 63);
}
// can't start or end with the dash (-) character
containerName = containerName.Trim('-');
return containerName;
}

58
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs

@ -1,4 +1,5 @@
using Aliyun.OSS;
using System;
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
@ -29,30 +30,34 @@ namespace Volo.Abp.BlobStoring.Aliyun
return OssClientFactory.Create(aliyunConfig);
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
if (!args.OverrideExisting && await ExistsAsync(new BlobProviderExistsArgs(args.ContainerName, args.Configuration, args.BlobName)))
var aliyunConfig = args.Configuration.GetAliyunConfiguration();
var ossClient = GetOssClient(aliyunConfig);
if (!args.OverrideExisting && BlobExistsAsync(ossClient, containerName, blobName))
{
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}
var aliyunConfig = args.Configuration.GetAliyunConfiguration();
var OssClient = GetOssClient(aliyunConfig);
if (aliyunConfig.CreateContainerIfNotExists)
{
if (!OssClient.DoesBucketExist(args.ContainerName))
if (!ossClient.DoesBucketExist(containerName))
{
OssClient.CreateBucket(args.ContainerName);
ossClient.CreateBucket(containerName);
}
}
OssClient.PutObject(args.ContainerName, blobName, args.BlobStream);
ossClient.PutObject(containerName, blobName, args.BlobStream);
return Task.CompletedTask;
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
var OssClient = GetOssClient(args.Configuration);
var result = OssClient.DeleteObject(args.ContainerName, blobName);
var ossClient = GetOssClient(args.Configuration);
var result = ossClient.DeleteObject(containerName, blobName);
//TODO: undifend delete flag
//https://help.aliyun.com/document_detail/91924.html
return Task.FromResult(true);
@ -60,22 +65,39 @@ namespace Volo.Abp.BlobStoring.Aliyun
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
var OssClient = GetOssClient(args.Configuration);
return Task.FromResult(OssClient.DoesObjectExist(args.ContainerName, blobName));
var ossClient = GetOssClient(args.Configuration);
return Task.FromResult(BlobExistsAsync(ossClient, containerName, blobName));
}
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
var OssClient = GetOssClient(args.Configuration);
if (!await ExistsAsync(new BlobProviderExistsArgs(args.ContainerName, args.Configuration, args.BlobName)))
var ossClient = GetOssClient(args.Configuration);
if (!BlobExistsAsync(ossClient, containerName, blobName))
{
return null;
}
var result = OssClient.GetObject(args.ContainerName, blobName);
return result.Content;
var result = ossClient.GetObject(containerName, blobName);
return Task.FromResult(result.Content);
}
private static string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAliyunConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.ContainerName;
}
private bool BlobExistsAsync(IOss ossClient,string containerName, string blobName)
{
// Make sure Blob Container exists.
return ossClient.DoesBucketExist(containerName) &&
ossClient.DoesObjectExist(containerName, blobName);
}
}
}

39
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs

@ -4,8 +4,6 @@ namespace Volo.Abp.BlobStoring.Aliyun
{
/// <summary>
/// Sub-account access to OSS or STS temporary authorization to access OSS
/// 子账号/STS临时授权访问OSS
/// https://help.aliyun.com/document_detail/100624.html
/// </summary>
public class AliyunBlobProviderConfiguration
{
@ -21,20 +19,12 @@ namespace Volo.Abp.BlobStoring.Aliyun
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeySecret, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// https://help.aliyun.com/document_detail/31837.html
/// eg: https://oss-cn-beijing.aliyuncs.com
/// </summary>
public string Endpoint
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.Endpoint);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// STS https://help.aliyun.com/document_detail/66053.html
/// eg:cn-beijing
/// </summary>
public string RegionId
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.RegionId);
@ -42,7 +32,7 @@ namespace Volo.Abp.BlobStoring.Aliyun
}
/// <summary>
/// eg:acs:ram::$accountID:role/$roleName
/// acs:ram::$accountID:role/$roleName
/// </summary>
public string RoleArn
{
@ -52,7 +42,6 @@ namespace Volo.Abp.BlobStoring.Aliyun
/// <summary>
/// The name used to identify the temporary access credentials, it is recommended to use different application users to distinguish.
/// 用来标识临时访问凭证的名称,建议使用不同的应用程序用户来区分。
/// </summary>
public string RoleSessionName
{
@ -62,7 +51,6 @@ namespace Volo.Abp.BlobStoring.Aliyun
/// <summary>
/// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600.
/// 设置临时访问凭证的有效期,单位是s,最小为900,最大为3600。
/// </summary>
public int DurationSeconds
{
@ -70,12 +58,27 @@ namespace Volo.Abp.BlobStoring.Aliyun
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DurationSeconds, value);
}
/// <summary>
/// If policy is empty, the user will get all permissions under this role
/// </summary>
public string Policy
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.Policy);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Policy, 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>(AliyunBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// Default value: false.
/// </summary>
@ -92,15 +95,11 @@ namespace Volo.Abp.BlobStoring.Aliyun
_containerConfiguration = containerConfiguration;
}
public string ToOssKeyString()
{
Uri uPoint = new Uri(Endpoint);
return $"memorycache:aliyun:id:{AccessKeyId},sec:{AccessKeySecret},ept:{uPoint.Host.ToLower()}";
}
public string ToOssWithStsKeyString()
public string ToKeyString()
{
return ToOssKeyString() + $",rid:{RegionId},ra:{RoleArn},rsn:{RoleSessionName},pl:{Policy}";
Uri uPoint = new Uri(Endpoint);
return $"blobstoring:aliyun:id:{AccessKeyId},sec:{AccessKeySecret},ept:{uPoint.Host.ToLower()},rid:{RegionId},ra:{RoleArn},rsn:{RoleSessionName},pl:{Policy}";
}
}
}

1
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs

@ -10,6 +10,7 @@
public const string RoleSessionName = "Aliyun.RoleSessionName";
public const string DurationSeconds = "Aliyun.DurationSeconds";
public const string Policy = "Aliyun.Policy";
public const string ContainerName = "Aliyun:ContainerName";
public const string CreateContainerIfNotExists = "Aliyun.CreateContainerIfNotExists";
}
}

30
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Caching;
namespace Volo.Abp.BlobStoring.Aliyun
{
[Serializable]
[CacheName("AssumeRoleCredentials")]
public class AssumeRoleCredentialsCacheItem
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public string SecurityToken { get; set; }
public AssumeRoleCredentialsCacheItem()
{
}
public AssumeRoleCredentialsCacheItem(string accessKeyId,string accessKeySecret,string securityToken)
{
AccessKeyId = accessKeyId;
AccessKeySecret = accessKeySecret;
SecurityToken = securityToken;
}
}
}

66
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs

@ -3,23 +3,22 @@ using Aliyun.Acs.Core.Auth.Sts;
using Aliyun.Acs.Core.Http;
using Aliyun.Acs.Core.Profile;
using Aliyun.OSS;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Generic;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Aliyun
{
/// <summary>
/// Sub-account access to OSS or STS temporary authorization to access OSS
/// 子账号/STS临时授权访问OSS
/// STS:https://help.aliyun.com/document_detail/28756.html
/// </summary>
public class DefaultOssClientFactory : IOssClientFactory, ITransientDependency
{
private readonly IMemoryCache _cache;
private readonly IDistributedCache<AssumeRoleCredentialsCacheItem> _cache;
public DefaultOssClientFactory(
IMemoryCache cache)
IDistributedCache<AssumeRoleCredentialsCacheItem> cache)
{
_cache = cache;
}
@ -29,45 +28,40 @@ namespace Volo.Abp.BlobStoring.Aliyun
//Sub-account
if (aliyunConfig.DurationSeconds <= 0)
{
var key = aliyunConfig.ToOssKeyString();
var iOssClient = _cache.Get<IOss>(key);
if (iOssClient != null)
{
return iOssClient;
}
iOssClient = new OssClient(aliyunConfig.Endpoint, aliyunConfig.AccessKeyId, aliyunConfig.AccessKeySecret);
_cache.Set(key, iOssClient);
return iOssClient;
return new OssClient(aliyunConfig.Endpoint, aliyunConfig.AccessKeyId, aliyunConfig.AccessKeySecret);
}
else
{
//STS temporary authorization to access OSS
var key = aliyunConfig.ToOssWithStsKeyString();
var iOssClient = _cache.Get<IOss>(key);
if (iOssClient != null)
var key = aliyunConfig.ToKeyString();
var cacheItem = _cache.Get(key);
if (cacheItem == null)
{
return iOssClient;
}
IClientProfile profile = DefaultProfile.GetProfile(
IClientProfile profile = DefaultProfile.GetProfile(
aliyunConfig.RegionId,
aliyunConfig.AccessKeyId,
aliyunConfig.AccessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
AssumeRoleRequest request = new AssumeRoleRequest
{
AcceptFormat = FormatType.JSON,
//eg:acs:ram::$accountID:role/$roleName
RoleArn = aliyunConfig.RoleArn,
RoleSessionName = aliyunConfig.RoleSessionName,
//Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. default 3600
DurationSeconds = aliyunConfig.DurationSeconds,
//Set additional permission policy of Token; when acquiring Token, further reduce the permission of Token by setting an additional permission policy
Policy = aliyunConfig.Policy.IsNullOrEmpty() ? null : aliyunConfig.Policy,
};
var response = client.GetAcsResponse(request);
iOssClient = new OssClient(aliyunConfig.Endpoint, response.Credentials.AccessKeyId, response.Credentials.AccessKeySecret, response.Credentials.SecurityToken);
_cache.Set(key, iOssClient, TimeSpan.FromSeconds(aliyunConfig.DurationSeconds));
return iOssClient;
DefaultAcsClient client = new DefaultAcsClient(profile);
AssumeRoleRequest request = new AssumeRoleRequest
{
AcceptFormat = FormatType.JSON,
//eg:acs:ram::$accountID:role/$roleName
RoleArn = aliyunConfig.RoleArn,
RoleSessionName = aliyunConfig.RoleSessionName,
//Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. default 3600
DurationSeconds = aliyunConfig.DurationSeconds,
//Set additional permission policy of Token; when acquiring Token, further reduce the permission of Token by setting an additional permission policy
Policy = aliyunConfig.Policy.IsNullOrEmpty() ? null : aliyunConfig.Policy,
};
var response = client.GetAcsResponse(request);
cacheItem = new AssumeRoleCredentialsCacheItem(response.Credentials.AccessKeyId, response.Credentials.AccessKeySecret, response.Credentials.SecurityToken);
_cache.Set(key, cacheItem, new DistributedCacheEntryOptions()
{
//Subtract 10 seconds of network request time.
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(aliyunConfig.DurationSeconds - 10)
});
}
return new OssClient(aliyunConfig.Endpoint, cacheItem.AccessKeyId, cacheItem.AccessKeySecret, cacheItem.SecurityToken);
}
}
}

7
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Properties/serviceDependencies.json

@ -1,7 +0,0 @@
{
"dependencies": {
"secrets1": {
"type": "secrets"
}
}
}

7
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Properties/serviceDependencies.local.json

@ -1,7 +0,0 @@
{
"dependencies": {
"secrets1": {
"type": "secrets.user"
}
}
}

4
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj

@ -17,4 +17,8 @@
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

18
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs

@ -21,6 +21,9 @@ namespace Volo.Abp.BlobStoring.Aliyun
{
private const string UserSecretsId = "fe9a87da-3584-40e0-a06c-aa499936015d";
private AliyunBlobProviderConfiguration _configuration;
private readonly string _randomContainerName = "abp-aliyun-test-container-" + Guid.NewGuid().ToString("N");
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder =>
@ -43,19 +46,28 @@ namespace Volo.Abp.BlobStoring.Aliyun
{
aliyun.AccessKeyId = _accessKeyId;
aliyun.AccessKeySecret = _accessKeySecret;
aliyun.Endpoint = _endpoint;//eg:https://oss-cn-beijing.aliyuncs.com
aliyun.Endpoint = _endpoint;
//STS
aliyun.RegionId = _regionId;//eg:cn-beijing
aliyun.RoleArn = _roleArn;//eg:acs:ram::1320235309887297:role/role-oss-xxxxx
aliyun.RegionId = _regionId;
aliyun.RoleArn = _roleArn;
aliyun.RoleSessionName = Guid.NewGuid().ToString("N");
aliyun.DurationSeconds = 3600;
aliyun.Policy = String.Empty;
//Other
aliyun.CreateContainerIfNotExists = true;
aliyun.ContainerName = _randomContainerName;
_configuration = aliyun;
});
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
var ossClientFactory = context.ServiceProvider.GetService<IOssClientFactory>();
var ossClient = ossClientFactory.Create(_configuration);
ossClient.DeleteBucket(_randomContainerName);
}
}
}

35
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs

@ -5,30 +5,13 @@ using Xunit;
namespace Volo.Abp.BlobStoring.Aliyun
{
//public class AliyunBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringAliyunTestModule>
//{
// private readonly IBlobContainer<MyTestContainer> _blobContainer;
// private readonly string _blobname = "my-blob";
// private readonly string _stringContent = "my-blob-content";
// public AliyunBlobContainer_Tests()
// {
// _blobContainer = GetRequiredService<IBlobContainer<MyTestContainer>>();
// }
// [Fact]
// public async void BlobContainer_Test()
// {
// var streamContent = Encoding.Default.GetBytes(_stringContent);
// await _blobContainer.SaveAsync(_blobname, streamContent, true);
// var streamData = await _blobContainer.GetAsync(_blobname);
// var tfExist = await _blobContainer.ExistsAsync(_blobname);
// if (tfExist)
// {
// await _blobContainer.DeleteAsync(_blobname);
// }
// StreamReader reader = new StreamReader(streamData);
// var stringData = reader.ReadToEnd();// Encoding.Default.GetString(bytes);
// stringData.ShouldBe(_stringContent);
// }
//}
/*
//Please set the correct connection string in secrets.json and continue the test.
public class AliyunBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringAliyunTestModule>
{
public AliyunBlobContainer_Tests()
{
}
}
*/
}

8
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs

@ -54,13 +54,5 @@ namespace Volo.Abp.BlobStoring.Aliyun
filename.Length.ShouldBeLessThanOrEqualTo(63);
}
[Fact]
public void NormalizeContainerName_Trim()
{
var filename = "-abpabpabpabp-";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.ShouldBe("abpabpabpabp");
}
}
}

7
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/MyTestContainer.cs

@ -1,7 +0,0 @@
namespace Volo.Abp.BlobStoring.Aliyun
{
[BlobContainerName("my-container")]
public class MyTestContainer
{
}
}
Loading…
Cancel
Save