Browse Source

Merge pull request #4735 from Aprite/aprite/add-blobstoring-aliyun-from-dev

Add aliyun blob provider
pull/4783/head
liangshiwei 6 years ago
committed by GitHub
parent
commit
1093cdc909
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 73
      docs/en/Blob-Storing-Aliyun.md
  2. 4
      docs/en/docs-nav.json
  3. 72
      docs/zh-Hans/Blob-Storing-Aliyun.md
  4. 4
      docs/zh-Hans/docs-nav.json
  5. 14
      framework/Volo.Abp.sln
  6. 3
      framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml
  7. 30
      framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xsd
  8. 27
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo.Abp.BlobStoring.Aliyun.csproj
  9. 14
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs
  10. 25
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainerConfigurationExtensions.cs
  11. 51
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs
  12. 107
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs
  13. 105
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs
  14. 16
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs
  15. 30
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AssumeRoleCredentialsCacheItem.cs
  16. 22
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNameCalculator.cs
  17. 68
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs
  18. 7
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IAliyunBlobNameCalculator.cs
  19. 9
      framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IOssClientFactory.cs
  20. 24
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj
  21. 19
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestBase.cs
  22. 84
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs
  23. 17
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs
  24. 55
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNameCalculator_Tests.cs
  25. 58
      framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs
  26. 1
      nupkg/common.ps1

73
docs/en/Blob-Storing-Aliyun.md

@ -0,0 +1,73 @@
# BLOB Storing Aliyun Provider
BLOB Storing Aliyun Provider can store BLOBs in [Aliyun Blob storage](https://help.aliyun.com/product/31815.html).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aliyun BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aliyun` package.
* Run `abp add-package Volo.Abp.BlobStoring.Aliyun` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAliyunModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the Aliyun storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAliyun(aliyun =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **AccessKeyId** ([NotNull]string): AccessKey is the key to access the Alibaba Cloud API. It has full permissions for the account. Please keep it safe! Recommend to follow [Alibaba Cloud security best practicess](https://help.aliyun.com/document_detail/102600.html),Use RAM sub-user AccessKey to call API.
* **AccessKeySecret** ([NotNull]string): Same as above.
* **Endpoint** ([NotNull]string): Endpoint is the external domain name of OSS. See the [document](https://help.aliyun.com/document_detail/31837.html) for details.
* **RegionId** (string): Access address of STS service. See the [document](https://help.aliyun.com/document_detail/66053.html) for details.
* **RoleArn** ([NotNull]string): STS required role ARN. See the [document](https://help.aliyun.com/document_detail/100624.html) for details.
* **RoleSessionName** ([NotNull]string): Used to identify the temporary access credentials, it is recommended to use different application users to distinguish.
* **Policy** (string): Additional permission restrictions. See the [document](https://help.aliyun.com/document_detail/100680.html) for details.
* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. **note**: Using subaccounts operated OSS,if the value is 0.
* **ContainerName** (string): You can specify the container name in Aliyun. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aliyun has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://help.aliyun.com/knowledge_detail/39668.html):
* Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character.
* Container names Must start and end with lowercase letters and numbers.
* Container names must be from **3** through **63** characters long.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aliyun, `AliyunBlobProvider` will try to create it.
## Aliyun Blob Name Calculator
Aliyun Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `AliyunBlobProvider` is the main service that implements the Aliyun BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AliyunBlobProvider` class).
* `IAliyunBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAliyunBlobNameCalculator` by default.
* `IOssClientFactory` is used create OSS client. It is implemented by the `DefaultOssClientFactory` by default. You can override/replace it,if you want customize.

4
docs/en/docs-nav.json

@ -216,6 +216,10 @@
"text": "Azure Provider",
"path": "Blob-Storing-Azure.md"
},
{
"text": "Aliyun Provider",
"path": "Blob-Storing-Aliyun.md"
},
{
"text": "Create a Custom Provider",
"path": "Blob-Storing-Custom-Provider.md"

72
docs/zh-Hans/Blob-Storing-Aliyun.md

@ -0,0 +1,72 @@
# BLOB Storing Aliyun提供程序
BLOB存储Aliyun提供程序可以将BLOB存储在[Aliyun Blob storage](https://help.aliyun.com/product/31815.html)中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Aliyun提供程序.
## 安装
使用ABP CLI添加[Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.Aliyun` 包的 `.csproj` 文件目录打开命令行.
* 运行 `Volo.Abp.BlobStoring.Aliyun` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Aliyun` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAliyunModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用Aliyun存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAliyun(aliyun =>
{
aliyun.AccessKeyId = "your aliyun access key id";
aliyun.AccessKeySecret = "your aliyun access key secret";
aliyun.Endpoint = "your oss endpoint";
aliyun.RegionId = "your sts region id";
aliyun.RoleArn = "the arn of ram role";
aliyun.RoleSessionName = "the name of the certificate";
aliyun.Policy = "policy";
aliyun.DurationSeconds = "expiration date";
aliyun.ContainerName = "your aliyun container name";
aliyun.CreateContainerIfNotExists = false;
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **AccessKeyId** ([NotNull]string): 云账号AccessKey是访问阿里云API的密钥,具有该账户完全的权限,请你务必妥善保管!强烈建议遵循[阿里云安全最佳实践](https://help.aliyun.com/document_detail/102600.html),使用RAM子用户AccessKey来进行API调用.
* **AccessKeySecret** ([NotNull]string): 同上.
* **Endpoint** ([NotNull]string): Endpoint表示OSS对外服务的访问域名. [访问域名和数据中心](https://help.aliyun.com/document_detail/31837.html)
* **RegionId** (string): STS服务的接入地址,每个地址的功能都相同,请尽量在同地域进行调用. [接入地址](https://help.aliyun.com/document_detail/66053.html)
* **RoleArn** ([NotNull]string): STS所需角色ARN. [STS临时授权访问OSS](https://help.aliyun.com/document_detail/100624.html)
* **RoleSessionName** ([NotNull]string): 用来标识临时访问凭证的名称,建议使用不同的应用程序用户来区分.
* **Policy** (string): 在扮演角色的时候额外添加的权限限制. 请参见[基于RAM Policy的权限控制](https://help.aliyun.com/document_detail/100680.html).
* **DurationSeconds** (int): 设置临时访问凭证的有效期,单位是s,最小为900,最大为3600. **注**:为0则使用子账号操作OSS.
* **ContainerName** (string): 你可以在aliyun中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Aliyun有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://help.aliyun.com/knowledge_detail/39668.html):
* 只能包含小写字母,数字和短横线(-)
* 必须以小写字母和数字开头和结尾
* Bucket名称的长度限制在**3**到**63**个字符之间
* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果aliyun中不存在容器, `AliyunBlobProvider` 将尝试创建它.
## Aliyun BLOB 名称计算器
Aliyun BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串.
* 追加 BLOB 名称.
## 其他服务
* `AliyunBlobProvider` 是实现Aliyun BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AliyunBlobProvider` 类).
* `IAliyunBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAliyunBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它.
* `IOssClientFactory` 服务用于生成OSS客户端. 默认实现是 `DefaultOssClientFactory` . 如果你想自定义OSS客户端生成,可以替换/覆盖它.

4
docs/zh-Hans/docs-nav.json

@ -210,6 +210,10 @@
"text": "Azure提供程序",
"path": "Blob-Storing-Azure.md"
},
{
"text": "Aliyun提供程序",
"path": "Blob-Storing-Aliyun.md"
},
{
"text": "创建自定义提供程序",
"path": "Blob-Storing-Custom-Provider.md"

14
framework/Volo.Abp.sln

@ -315,6 +315,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Caching.StackExcha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Caching.StackExchangeRedis.Tests", "test\Volo.Abp.Caching.StackExchangeRedis.Tests\Volo.Abp.Caching.StackExchangeRedis.Tests.csproj", "{60D0E384-965E-4F81-9D71-B28F419254FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun", "src\Volo.Abp.BlobStoring.Aliyun\Volo.Abp.BlobStoring.Aliyun.csproj", "{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun.Tests", "test\Volo.Abp.BlobStoring.Aliyun.Tests\Volo.Abp.BlobStoring.Aliyun.Tests.csproj", "{8E49687A-E69F-49F2-8DB0-428D0883A937}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -937,6 +941,14 @@ Global
{60D0E384-965E-4F81-9D71-B28F419254FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.Build.0 = Release|Any CPU
{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Release|Any CPU.Build.0 = Release|Any CPU
{8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1096,6 +1108,8 @@ Global
{1738845A-5348-4EB8-B736-CD1D22A808B4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{60D0E384-965E-4F81-9D71-B28F419254FC} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

3
framework/src/Volo.Abp.BlobStoring.Aliyun/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.Aliyun/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>

27
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo.Abp.BlobStoring.Aliyun.csproj

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring.Aliyun</AssemblyName>
<PackageId>Volo.Abp.BlobStoring.Aliyun</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="aliyun-net-sdk-sts" Version="3.0.4" />
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
</ItemGroup>
</Project>

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

@ -0,0 +1,14 @@
using Volo.Abp.Caching;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Aliyun
{
[DependsOn(
typeof(AbpBlobStoringModule),
typeof(AbpCachingModule)
)]
public class AbpBlobStoringAliyunModule: AbpModule
{
}
}

25
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainerConfigurationExtensions.cs

@ -0,0 +1,25 @@
using System;
namespace Volo.Abp.BlobStoring.Aliyun
{
public static class AliyunBlobContainerConfigurationExtensions
{
public static AliyunBlobProviderConfiguration GetAliyunConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new AliyunBlobProviderConfiguration(containerConfiguration);
}
public static BlobContainerConfiguration UseAliyun(
this BlobContainerConfiguration containerConfiguration,
Action<AliyunBlobProviderConfiguration> aliyunConfigureAction)
{
containerConfiguration.ProviderType = typeof(AliyunBlobProvider);
containerConfiguration.NamingNormalizers.TryAdd<AliyunBlobNamingNormalizer>();
aliyunConfigureAction(new AliyunBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}
}

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

@ -0,0 +1,51 @@
using System.Text.RegularExpressions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class AliyunBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency
{
/// <summary>
/// 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
/// </summary>
public virtual string NormalizeContainerName(string containerName)
{
// All letters in a container name must be lowercase.
containerName = containerName.ToLower();
// Container names can contain only letters, numbers, and the dash (-) character.
containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty);
// Every dash (-) character must be immediately preceded and followed by a letter or number;
// consecutive dashes are not permitted in container names.
// Container names must start or end with a letter or number
containerName = Regex.Replace(containerName, "-{2,}", "-");
containerName = Regex.Replace(containerName, "^-", string.Empty);
containerName = Regex.Replace(containerName, "-$", string.Empty);
// Container names must be from 3 through 63 characters long.
if (containerName.Length < 3)
{
var length = containerName.Length;
for (var i = 0; i < 3 - length; i++)
{
containerName += "0";
}
}
if (containerName.Length > 63)
{
containerName = containerName.Substring(0, 63);
}
return containerName;
}
public virtual string NormalizeBlobName(string blobName)
{
return blobName;
}
}
}

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

@ -0,0 +1,107 @@
using Aliyun.OSS;
using System;
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class AliyunBlobProvider : BlobProviderBase, ITransientDependency
{
protected IOssClientFactory OssClientFactory { get; }
protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; }
public AliyunBlobProvider(
IOssClientFactory ossClientFactory,
IAliyunBlobNameCalculator aliyunBlobNameCalculator)
{
OssClientFactory = ossClientFactory;
AliyunBlobNameCalculator = aliyunBlobNameCalculator;
}
protected virtual IOss GetOssClient(BlobContainerConfiguration blobContainerConfiguration)
{
var aliyunConfig = blobContainerConfiguration.GetAliyunConfiguration();
return OssClientFactory.Create(aliyunConfig);
}
protected virtual IOss GetOssClient(AliyunBlobProviderConfiguration aliyunConfig)
{
return OssClientFactory.Create(aliyunConfig);
}
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
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 '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}
if (aliyunConfig.CreateContainerIfNotExists)
{
if (!ossClient.DoesBucketExist(containerName))
{
ossClient.CreateBucket(containerName);
}
}
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);
if(!BlobExistsAsync(ossClient, containerName, blobName))
{
return Task.FromResult(false);
}
ossClient.DeleteObject(containerName, blobName);
return Task.FromResult(true);
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
var ossClient = GetOssClient(args.Configuration);
return Task.FromResult(BlobExistsAsync(ossClient, containerName, blobName));
}
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var containerName = GetContainerName(args);
var blobName = AliyunBlobNameCalculator.Calculate(args);
var ossClient = GetOssClient(args.Configuration);
if (!BlobExistsAsync(ossClient, containerName, blobName))
{
return null;
}
var result = ossClient.GetObject(containerName, blobName);
var memoryStream = new MemoryStream();
await result.Content.CopyToAsync(memoryStream);
return memoryStream;
}
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);
}
}
}

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

@ -0,0 +1,105 @@
using System;
namespace Volo.Abp.BlobStoring.Aliyun
{
/// <summary>
/// Sub-account access to OSS or STS temporary authorization to access OSS
/// </summary>
public class AliyunBlobProviderConfiguration
{
public string AccessKeyId
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.AccessKeyId);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeyId, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
public string AccessKeySecret
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.AccessKeySecret);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeySecret, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
public string Endpoint
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.Endpoint);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
public string RegionId
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.RegionId);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RegionId, value);
}
/// <summary>
/// acs:ram::$accountID:role/$roleName
/// </summary>
public string RoleArn
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.RoleArn);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RoleArn, value);
}
/// <summary>
/// The name used to identify the temporary access credentials, it is recommended to use different application users to distinguish.
/// </summary>
public string RoleSessionName
{
get => _containerConfiguration.GetConfiguration<string>(AliyunBlobProviderConfigurationNames.RoleSessionName);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RoleSessionName, value);
}
/// <summary>
/// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600.
/// </summary>
public int DurationSeconds
{
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.DurationSeconds, 0);
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>
public bool CreateContainerIfNotExists
{
get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateContainerIfNotExists, false);
set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateContainerIfNotExists, value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
public string ToKeyString()
{
Uri uPoint = new Uri(Endpoint);
return $"blobstoring:aliyun:id:{AccessKeyId},sec:{AccessKeySecret},ept:{uPoint.Host.ToLower()},rid:{RegionId},ra:{RoleArn},rsn:{RoleSessionName},pl:{Policy}";
}
}
}

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

@ -0,0 +1,16 @@
namespace Volo.Abp.BlobStoring.Aliyun
{
public class AliyunBlobProviderConfigurationNames
{
public const string AccessKeyId = "Aliyun.AccessKeyId";
public const string AccessKeySecret = "Aliyun.AccessKeySecret";
public const string Endpoint = "Aliyun.Endpoint";
public const string RegionId = "Aliyun.RegionId";
public const string RoleArn = "Aliyun.RoleArn";
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;
}
}
}

22
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNameCalculator.cs

@ -0,0 +1,22 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class DefaultAliyunBlobNameCalculator: IAliyunBlobNameCalculator, ITransientDependency
{
protected ICurrentTenant CurrentTenant { get; }
public DefaultAliyunBlobNameCalculator(ICurrentTenant currentTenant)
{
CurrentTenant = currentTenant;
}
public virtual string Calculate(BlobProviderArgs args)
{
return CurrentTenant.Id == null
? $"host/{args.BlobName}"
: $"tenants/{CurrentTenant.Id.Value:D}/{args.BlobName}";
}
}
}

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

@ -0,0 +1,68 @@
using Aliyun.Acs.Core;
using Aliyun.Acs.Core.Auth.Sts;
using Aliyun.Acs.Core.Http;
using Aliyun.Acs.Core.Profile;
using Aliyun.OSS;
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
/// </summary>
public class DefaultOssClientFactory : IOssClientFactory, ITransientDependency
{
protected IDistributedCache<AssumeRoleCredentialsCacheItem> Cache { get; }
public DefaultOssClientFactory(
IDistributedCache<AssumeRoleCredentialsCacheItem> cache)
{
Cache = cache;
}
public virtual IOss Create(AliyunBlobProviderConfiguration aliyunConfig)
{
//Sub-account
if (aliyunConfig.DurationSeconds <= 0)
{
return new OssClient(aliyunConfig.Endpoint, aliyunConfig.AccessKeyId, aliyunConfig.AccessKeySecret);
}
else
{
//STS temporary authorization to access OSS
var key = aliyunConfig.ToKeyString();
var cacheItem = Cache.Get(key);
if (cacheItem == null)
{
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);
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/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IAliyunBlobNameCalculator.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring.Aliyun
{
public interface IAliyunBlobNameCalculator
{
string Calculate(BlobProviderArgs args);
}
}

9
framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IOssClientFactory.cs

@ -0,0 +1,9 @@
using Aliyun.OSS;
namespace Volo.Abp.BlobStoring.Aliyun
{
public interface IOssClientFactory
{
IOss Create(AliyunBlobProviderConfiguration args);
}
}

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

@ -0,0 +1,24 @@
<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.Aliyun\Volo.Abp.BlobStoring.Aliyun.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.1.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<ProjectReference Include="..\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

19
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestBase.cs

@ -0,0 +1,19 @@
using Volo.Abp.Testing;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class AbpBlobStoringAliyunTestCommonBase : AbpIntegratedTest<AbpBlobStoringAliyunTestCommonModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
public class AbpBlobStoringAliyunTestBase : AbpIntegratedTest<AbpBlobStoringAliyunTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

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

@ -0,0 +1,84 @@
using Aliyun.OSS;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Aliyun
{
[DependsOn(
typeof(AbpBlobStoringAliyunModule),
typeof(AbpBlobStoringTestModule)
)]
public class AbpBlobStoringAliyunTestCommonModule : AbpModule
{
}
[DependsOn(
typeof(AbpBlobStoringAliyunTestCommonModule)
)]
public class AbpBlobStoringAliyunTestModule : AbpModule
{
private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091";
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 =>
{
builder.AddUserSecrets(UserSecretsId);
}));
var configuration = context.Services.GetConfiguration();
var accessKeyId = configuration["Aliyun:AccessKeyId"];
var accessKeySecret = configuration["Aliyun:AccessKeySecret"];
var endpoint = configuration["Aliyun:Endpoint"];
var regionId = configuration["Aliyun:RegionId"];
var roleArn = configuration["Aliyun:RoleArn"];
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
containerConfiguration.UseAliyun(aliyun =>
{
aliyun.AccessKeyId = accessKeyId;
aliyun.AccessKeySecret = accessKeySecret;
aliyun.Endpoint = endpoint;
//STS
aliyun.RegionId = regionId;
aliyun.RoleArn = roleArn;
aliyun.RoleSessionName = Guid.NewGuid().ToString("N");
aliyun.DurationSeconds = 0;
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);
if(ossClient.DoesBucketExist(_randomContainerName))
{
var objects = ossClient.ListObjects(_randomContainerName);
if (objects.ObjectSummaries.Any())
{
ossClient.DeleteObjects(new DeleteObjectsRequest(_randomContainerName,
objects.ObjectSummaries.Select(o => o.Key).ToList()));
}
ossClient.DeleteBucket(_randomContainerName);
}
}
}
}

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

@ -0,0 +1,17 @@
using Shouldly;
using System.IO;
using System.Text;
using Xunit;
namespace Volo.Abp.BlobStoring.Aliyun
{
/*
//Please set the correct connection string in secrets.json and continue the test.
public class AliyunBlobContainer_Tests : BlobContainer_Tests<AbpBlobStoringAliyunTestModule>
{
public AliyunBlobContainer_Tests()
{
}
}
*/
}

55
framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNameCalculator_Tests.cs

@ -0,0 +1,55 @@
using Shouldly;
using System;
using Volo.Abp.MultiTenancy;
using Xunit;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class AliyunBlobNameCalculator_Tests: AbpBlobStoringAliyunTestCommonBase
{
private readonly IAliyunBlobNameCalculator _calculator;
private readonly ICurrentTenant _currentTenant;
private const string AliyunSeparator = "/";
public AliyunBlobNameCalculator_Tests()
{
_calculator = GetRequiredService<IAliyunBlobNameCalculator>();
_currentTenant = GetRequiredService<ICurrentTenant>();
}
[Fact]
public void Default_Settings()
{
_calculator.Calculate(
GetArgs("my-container", "my-blob")
).ShouldBe($"host{AliyunSeparator}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{AliyunSeparator}{tenantId:D}{AliyunSeparator}my-blob");
}
}
private static BlobProviderArgs GetArgs(
string containerName,
string blobName)
{
return new BlobProviderGetArgs(
containerName,
new BlobContainerConfiguration().UseAliyun(x =>
{
}),
blobName
);
}
}
}

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

@ -0,0 +1,58 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.BlobStoring.Aliyun
{
public class DefaultAliyunBlobNamingNormalizerProvider_Tests: AbpBlobStoringAliyunTestCommonBase
{
private readonly IBlobNamingNormalizer _blobNamingNormalizer;
public DefaultAliyunBlobNamingNormalizerProvider_Tests()
{
_blobNamingNormalizer = GetRequiredService<IBlobNamingNormalizer>();
}
[Fact]
public void NormalizeContainerName_Lowercase()
{
var filename = "ThisIsMyContainerName";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.ShouldBe("thisismycontainername");
}
[Fact]
public void NormalizeContainerName_Only_Letters_Numbers_Dash()
{
var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.ShouldBe("this-is-my-container-name");
}
[Fact]
public void NormalizeContainerName_Dash()
{
var filename = "-this--is----my-container----name-";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.ShouldBe("this-is-my-container-name");
}
[Fact]
public void NormalizeContainerName_Min_Length()
{
var filename = "a";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.Length.ShouldBeGreaterThanOrEqualTo(3);
}
[Fact]
public void NormalizeContainerName_Max_Length()
{
var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp";
filename = _blobNamingNormalizer.NormalizeContainerName(filename);
filename.Length.ShouldBeLessThanOrEqualTo(63);
}
}
}

1
nupkg/common.ps1

@ -58,6 +58,7 @@ $projects = (
"framework/src/Volo.Abp.BackgroundWorkers.Quartz",
"framework/src/Volo.Abp.BlobStoring",
"framework/src/Volo.Abp.BlobStoring.FileSystem",
"framework/src/Volo.Abp.BlobStoring.Aliyun",
"framework/src/Volo.Abp.BlobStoring.Azure",
"framework/src/Volo.Abp.BlobStoring.Minio",
"framework/src/Volo.Abp.Caching",

Loading…
Cancel
Save