diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index e02d40b1e0..77bb8a2531 100644
--- a/framework/Volo.Abp.sln
+++ b/framework/Volo.Abp.sln
@@ -289,21 +289,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR.Tests", "test\Volo.Abp.AspNetCore.SignalR.Tests\Volo.Abp.AspNetCore.SignalR.Tests.csproj", "{8B758716-DCC9-4223-8421-5588D1597487}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.csproj", "{79323211-E658-493E-9863-035AA4C3F913}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.csproj", "{79323211-E658-493E-9863-035AA4C3F913}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring", "src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj", "{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring", "src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj", "{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Tests", "test\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj", "{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Tests", "test\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj", "{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem", "src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.csproj", "{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.FileSystem", "src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.csproj", "{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem.Tests", "test\Volo.Abp.BlobStoring.FileSystem.Tests\Volo.Abp.BlobStoring.FileSystem.Tests.csproj", "{68443D4A-1608-4039-B995-7AF4CF82E9F8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.FileSystem.Tests", "test\Volo.Abp.BlobStoring.FileSystem.Tests\Volo.Abp.BlobStoring.FileSystem.Tests.csproj", "{68443D4A-1608-4039-B995-7AF4CF82E9F8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Minio", "src\Volo.Abp.BlobStoring.Minio\Volo.Abp.BlobStoring.Minio.csproj", "{658D7EDE-A057-4256-96B6-083D3C2B9704}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Minio.Tests", "test\Volo.Abp.BlobStoring.Minio.Tests\Volo.Abp.BlobStoring.Minio.Tests.csproj", "{36D4B268-FD3A-4655-A41B-D56D68476C83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -907,6 +911,14 @@ Global
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {658D7EDE-A057-4256-96B6-083D3C2B9704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {658D7EDE-A057-4256-96B6-083D3C2B9704}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {658D7EDE-A057-4256-96B6-083D3C2B9704}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {658D7EDE-A057-4256-96B6-083D3C2B9704}.Release|Any CPU.Build.0 = Release|Any CPU
+ {36D4B268-FD3A-4655-A41B-D56D68476C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36D4B268-FD3A-4655-A41B-D56D68476C83}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36D4B268-FD3A-4655-A41B-D56D68476C83}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36D4B268-FD3A-4655-A41B-D56D68476C83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1061,6 +1073,8 @@ Global
{75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA}
+ {658D7EDE-A057-4256-96B6-083D3C2B9704} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {36D4B268-FD3A-4655-A41B-D56D68476C83} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml
new file mode 100644
index 0000000000..bc5a74a236
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xsd
new file mode 100644
index 0000000000..3f3946e282
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj
new file mode 100644
index 0000000000..1c080067ed
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.BlobStoring.Minio
+ Volo.Abp.BlobStoring.Minio
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs
new file mode 100644
index 0000000000..00874dfe01
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ [DependsOn(typeof(AbpBlobStoringModule))]
+ public class AbpBlobStoringMinioModule : AbpModule
+ {
+
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs
new file mode 100644
index 0000000000..b6d35671e2
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs
@@ -0,0 +1,22 @@
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.MultiTenancy;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public class DefaultMinioBlobNameCalculator : IMinioBlobNameCalculator, ITransientDependency
+ {
+ protected ICurrentTenant CurrentTenant { get; }
+
+ public DefaultMinioBlobNameCalculator(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}";
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs
new file mode 100644
index 0000000000..9cf7503648
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public interface IMinioBlobNameCalculator
+ {
+ string Calculate(BlobProviderArgs args);
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs
new file mode 100644
index 0000000000..3465914156
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public static class MinioBlobContainerConfigurationExtensions
+ {
+ public static MinioBlobProviderConfiguration GetMinioConfiguration(
+ this BlobContainerConfiguration containerConfiguration)
+ {
+ return new MinioBlobProviderConfiguration(containerConfiguration);
+ }
+
+ public static BlobContainerConfiguration UseMinio(
+ this BlobContainerConfiguration containerConfiguration,
+ Action minioConfigureAction)
+ {
+ containerConfiguration.ProviderType = typeof(MinioBlobProvider);
+
+ minioConfigureAction(new MinioBlobProviderConfiguration(containerConfiguration));
+
+ return containerConfiguration;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs
new file mode 100644
index 0000000000..a958635f60
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs
@@ -0,0 +1,159 @@
+using Minio;
+using Minio.Exceptions;
+using System;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public class MinioBlobProvider : BlobProviderBase, ITransientDependency
+ {
+ protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; }
+
+ public MinioBlobProvider(IMinioBlobNameCalculator minioBlobNameCalculator)
+ {
+ MinioBlobNameCalculator = minioBlobNameCalculator;
+ }
+
+ public override async Task SaveAsync(BlobProviderSaveArgs args)
+ {
+ var blobName = MinioBlobNameCalculator.Calculate(args);
+ var configuration = args.Configuration.GetMinioConfiguration();
+
+ 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.CreateBucketIfNotExists)
+ {
+ await CreateBucketIfNotExists(args);
+ }
+
+ await GetMinioClient(args).PutObjectAsync(GetContainerName(args), blobName, args.BlobStream, args.BlobStream.Length);
+ }
+
+ public override async Task DeleteAsync(BlobProviderDeleteArgs args)
+ {
+ var blobName = MinioBlobNameCalculator.Calculate(args);
+
+ if (await BlobExistsAsync(args, blobName))
+ {
+ var client = GetMinioClient(args);
+ await client.RemoveObjectAsync(GetContainerName(args), blobName);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override async Task ExistsAsync(BlobProviderExistsArgs args)
+ {
+ var blobName = MinioBlobNameCalculator.Calculate(args);
+
+ return await BlobExistsAsync(args, blobName);
+ }
+
+ public override async Task GetOrNullAsync(BlobProviderGetArgs args)
+ {
+ var blobName = MinioBlobNameCalculator.Calculate(args);
+
+ if (!await BlobExistsAsync(args, blobName))
+ {
+ return null;
+ }
+ try
+ {
+ var client = GetMinioClient(args);
+
+ var stat = await client.StatObjectAsync(GetContainerName(args), blobName);
+
+ MemoryStream returnStream = new MemoryStream();
+
+ await client.GetObjectAsync(GetContainerName(args), blobName,
+ (stream) =>
+ {
+ if (stream != null)
+ {
+ stream.CopyTo(returnStream);
+
+ // returnStream = new MemoryStream(stream.GetAllBytes());
+
+ }
+ });
+
+ return returnStream;
+ }
+ catch (MinioException ex)
+ {
+
+ }
+ return null;
+
+ }
+
+
+ private MinioClient GetMinioClient(BlobProviderArgs args)
+ {
+ var configuration = args.Configuration.GetMinioConfiguration();
+ var client = new MinioClient(configuration.EndPoint, configuration.AccessKey, configuration.SecretKey);
+ if (configuration.WithSSL)
+ {
+ client.WithSSL();
+ }
+
+ return client;
+
+ }
+
+
+
+ protected virtual async Task CreateBucketIfNotExists(BlobProviderArgs args)
+ {
+ var client = GetMinioClient(args);
+ var containerName = GetContainerName(args);
+ if (!await client.BucketExistsAsync(containerName))
+ {
+ await client.MakeBucketAsync(containerName);
+ }
+
+ }
+
+ private async Task BlobExistsAsync(BlobProviderArgs args, string blobName)
+ {
+ // Make sure Blob Container exists.
+ if (await ContainerExistsAsync(args))
+ {
+ try
+ {
+ await GetMinioClient(args).StatObjectAsync(GetContainerName(args), blobName);
+ return true;
+ }
+ catch (MinioException ex)
+ {
+
+ }
+ }
+ return false;
+
+ }
+ private static string GetContainerName(BlobProviderArgs args)
+ {
+ var configuration = args.Configuration.GetMinioConfiguration();
+
+ //minio bucket name must be lower
+ return configuration.BucketName.IsNullOrWhiteSpace()
+ ? args.ContainerName.ToLower()
+ : configuration.BucketName.ToLower();
+ }
+
+ private async Task ContainerExistsAsync(BlobProviderArgs args)
+ {
+ var client = GetMinioClient(args);
+
+ return await client.BucketExistsAsync(GetContainerName(args));
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs
new file mode 100644
index 0000000000..03e951a984
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs
@@ -0,0 +1,65 @@
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public class MinioBlobProviderConfiguration
+ {
+ public string BucketName
+ {
+ get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.BucketName);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.BucketName, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+
+ ///
+ /// endPoint is an URL, domain name, IPv4 address or IPv6 address.
+ ///
+ public string EndPoint
+ {
+ get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.EndPoint);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.EndPoint, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+
+ ///
+ /// accessKey is like user-id that uniquely identifies your account.This field is optional and can be omitted for anonymous access.
+ ///
+ public string AccessKey
+ {
+ get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.AccessKey);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.AccessKey, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+
+ ///
+ /// secretKey is the password to your account.This field is optional and can be omitted for anonymous access.
+ ///
+ public string SecretKey
+ {
+ get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.SecretKey);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.SecretKey, Check.NotNullOrWhiteSpace(value, nameof(value)));
+ }
+
+ ///
+ ///connect to to MinIO Client object to use https instead of http
+ ///
+ public bool WithSSL
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(MinioBlobProviderConfigurationNames.WithSSL, false);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.WithSSL, value);
+ }
+
+
+
+ ///
+ ///Default value: false.
+ ///
+ public bool CreateBucketIfNotExists
+ {
+ get => _containerConfiguration.GetConfigurationOrDefault(MinioBlobProviderConfigurationNames.CreateBucketIfNotExists, false);
+ set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.CreateBucketIfNotExists, value);
+ }
+
+ private readonly BlobContainerConfiguration _containerConfiguration;
+
+ public MinioBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
+ {
+ _containerConfiguration = containerConfiguration;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs
new file mode 100644
index 0000000000..3c12d98390
--- /dev/null
+++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs
@@ -0,0 +1,12 @@
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public static class MinioBlobProviderConfigurationNames
+ {
+ public const string BucketName = "Minio.BucketName";
+ public const string EndPoint = "Minio.EndPoint";
+ public const string AccessKey = "Minio.AccessKey";
+ public const string SecretKey = "Minio.SecretKey";
+ public const string WithSSL = "Minio.WithSSL";
+ public const string CreateBucketIfNotExists = "Minio.CreateBucketIfNotExists";
+ }
+}
diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj
new file mode 100644
index 0000000000..49f7050734
--- /dev/null
+++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netcoreapp3.1
+
+ 9f0d2c00-80c1-435b-bfab-2c39c8249091
+
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs
new file mode 100644
index 0000000000..c15f093e58
--- /dev/null
+++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs
@@ -0,0 +1,20 @@
+using Volo.Abp.Testing;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public class AbpBlobStoringMinioTestCommonBase : AbpIntegratedTest
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+ }
+
+ public class AbpBlobStoringMinioTestBase : AbpIntegratedTest
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs
new file mode 100644
index 0000000000..cfe7b5b4cf
--- /dev/null
+++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Minio;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+
+ [DependsOn(
+ typeof(AbpBlobStoringMinioModule),
+ typeof(AbpBlobStoringTestModule)
+ )]
+ public class AbpBlobStoringMinioTestCommonModule : AbpModule
+ {
+
+ }
+
+ [DependsOn(
+ typeof(AbpBlobStoringMinioTestCommonModule)
+ )]
+ public class AbpBlobStoringMinioTestModule : AbpModule
+ {
+ private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091";
+
+ private string _endPoint;
+ private string _accessKey;
+ private string _secretKey;
+
+
+ private readonly string _randomContainerName = "abp-minio-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();
+ _endPoint = configuration["Minio:EndPoint"];
+ _accessKey = configuration["Minio:AccessKey"];
+ _secretKey = configuration["Minio:SecretKey"];
+
+ Configure(options =>
+ {
+ options.Containers.ConfigureAll((containerName, containerConfiguration) =>
+ {
+ containerConfiguration.UseMinio(minio =>
+ {
+ minio.EndPoint = _endPoint;
+ minio.AccessKey = _accessKey;
+ minio.SecretKey = _secretKey;
+ minio.WithSSL = false;
+ minio.BucketName = _randomContainerName;
+ minio.CreateBucketIfNotExists = true;
+ });
+ });
+ });
+ }
+
+ public async override void OnApplicationShutdown(ApplicationShutdownContext context)
+ {
+ var minioClient = new MinioClient(_endPoint, _accessKey, _secretKey);
+ if (await minioClient.BucketExistsAsync(_randomContainerName))
+ {
+ var observables =minioClient.ListObjectsAsync(_randomContainerName,null,true);
+ var objectNames = new List();
+ IDisposable subscription = observables.Subscribe(
+ async item => await minioClient.RemoveObjectAsync(_randomContainerName, item.Key),
+ async () => await minioClient.RemoveBucketAsync(_randomContainerName)
+ );
+
+ }
+
+ }
+ }
+
+}
diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs
new file mode 100644
index 0000000000..5c38b098d5
--- /dev/null
+++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs
@@ -0,0 +1,16 @@
+using Xunit;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+
+ ////Please set the correct connection string in secrets.json and continue the test.
+
+ //public class MinioBlobContainer_Tests : BlobContainer_Tests
+ //{
+ // public MinioBlobContainer_Tests()
+ // {
+
+ // }
+ //}
+
+}
diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs
new file mode 100644
index 0000000000..712c849965
--- /dev/null
+++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs
@@ -0,0 +1,57 @@
+using System;
+using Shouldly;
+using Volo.Abp.MultiTenancy;
+using Xunit;
+
+namespace Volo.Abp.BlobStoring.Minio
+{
+ public class MinioBlobNameCalculator_Tests : AbpBlobStoringMinioTestCommonBase
+ {
+ private readonly IMinioBlobNameCalculator _calculator;
+ private readonly ICurrentTenant _currentTenant;
+
+ private const string MinioContainerName = "/";
+ private const string MinioSeparator = "/";
+
+ public MinioBlobNameCalculator_Tests()
+ {
+ _calculator = GetRequiredService();
+ _currentTenant = GetRequiredService();
+ }
+
+ [Fact]
+ public void Default_Settings()
+ {
+ _calculator.Calculate(
+ GetArgs("my-container", "my-blob")
+ ).ShouldBe($"host{MinioSeparator}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{MinioSeparator}{tenantId:D}{MinioSeparator}my-blob");
+ }
+ }
+
+ private static BlobProviderArgs GetArgs(
+ string containerName,
+ string blobName)
+ {
+ return new BlobProviderGetArgs(
+ containerName,
+ new BlobContainerConfiguration().UseMinio(x =>
+ {
+ x.BucketName = containerName;
+ }),
+ blobName
+ );
+ }
+ }
+}