Browse Source

Merge pull request #4105 from abpframework/blob-storing

Implemented blob storing module
pull/4112/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
4edf45045f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      framework/Volo.Abp.sln
  2. 3
      framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xml
  3. 30
      framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xsd
  4. 21
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj
  5. 13
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemModule.cs
  6. 32
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/DefaultBlogFilePathCalculator.cs
  7. 24
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs
  8. 69
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs
  9. 28
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfiguration.cs
  10. 8
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfigurationNames.cs
  11. 7
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/IBlogFilePathCalculator.cs
  12. 3
      framework/src/Volo.Abp.BlobStoring/FodyWeavers.xml
  13. 30
      framework/src/Volo.Abp.BlobStoring/FodyWeavers.xsd
  14. 23
      framework/src/Volo.Abp.BlobStoring/Volo.Abp.BlobStoring.csproj
  15. 28
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringModule.cs
  16. 12
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringOptions.cs
  17. 198
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs
  18. 71
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs
  19. 27
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationExtensions.cs
  20. 11
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationProviderExtensions.cs
  21. 61
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurations.cs
  22. 43
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs
  23. 26
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs
  24. 41
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerNameAttribute.cs
  25. 36
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs
  26. 16
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs
  27. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderDeleteArgs.cs
  28. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderExistsArgs.cs
  29. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderGetArgs.cs
  30. 34
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSaveArgs.cs
  31. 15
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSelectorExtensions.cs
  32. 20
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobContainerConfigurationProvider.cs
  33. 49
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobProviderSelector.cs
  34. 8
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs
  35. 88
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainer.cs
  36. 7
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs
  37. 20
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs
  38. 17
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProvider.cs
  39. 10
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProviderSelector.cs
  40. 9
      framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/ProxyHelper.cs
  41. 8
      framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs
  42. 9
      framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
  43. 17
      framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo.Abp.BlobStoring.FileSystem.Tests.csproj
  44. 27
      framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemTestModule.cs
  45. 12
      framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringTestBase.cs
  46. 71
      framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/BlogFilePathCalculator_Tests.cs
  47. 8
      framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/TestObjects/TestContainer.cs
  48. 17
      framework/test/Volo.Abp.BlobStoring.Tests/Volo.Abp.BlobStoring.Tests.csproj
  49. 41
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringOptions_Tests.cs
  50. 12
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringTestBase.cs
  51. 43
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringTestModule.cs
  52. 76
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainerFactory_Tests.cs
  53. 25
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainerNameAttribute_Tests.cs
  54. 32
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Injection_Tests.cs
  55. 31
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobProviderSelector_Tests.cs
  56. 33
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeBlobProvider1.cs
  57. 33
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeBlobProvider2.cs
  58. 19
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeProviders.cs
  59. 7
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer1.cs
  60. 8
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer2.cs
  61. 7
      framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer3.cs
  62. 2
      nupkg/common.ps1

28
framework/Volo.Abp.sln

@ -291,6 +291,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}"
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}"
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -865,6 +873,22 @@ Global
{79323211-E658-493E-9863-035AA4C3F913}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79323211-E658-493E-9863-035AA4C3F913}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79323211-E658-493E-9863-035AA4C3F913}.Release|Any CPU.Build.0 = Release|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Release|Any CPU.Build.0 = Release|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Release|Any CPU.Build.0 = Release|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Release|Any CPU.Build.0 = Release|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1012,6 +1036,10 @@ Global
{B64FCE08-E9D2-4984-BF12-FE199F257416} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{8B758716-DCC9-4223-8421-5588D1597487} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{79323211-E658-493E-9863-035AA4C3F913} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{68443D4A-1608-4039-B995-7AF4CF82E9F8} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

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

21
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring.FileSystem</AssemblyName>
<PackageId>Volo.Abp.BlobStoring.FileSystem</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" />
</ItemGroup>
</Project>

13
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemModule.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.FileSystem
{
[DependsOn(
typeof(AbpBlobStoringModule)
)]
public class AbpBlobStoringFileSystemModule : AbpModule
{
}
}

32
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/DefaultBlogFilePathCalculator.cs

@ -0,0 +1,32 @@
using System.IO;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.FileSystem
{
public class DefaultBlogFilePathCalculator : IBlogFilePathCalculator, ITransientDependency
{
public virtual string Calculate(BlobProviderArgs args)
{
var fileSystemConfiguration = args.Configuration.GetFileSystemConfiguration();
var blobPath = fileSystemConfiguration.BasePath;
if (args.TenantId == null)
{
blobPath = Path.Combine(blobPath, "host");
}
else
{
blobPath = Path.Combine(blobPath, "tenants", args.TenantId.Value.ToString("D"));
}
if (fileSystemConfiguration.AppendContainerNameToBasePath)
{
blobPath = Path.Combine(blobPath, args.ContainerName);
}
blobPath = Path.Combine(blobPath, args.BlobName);
return blobPath;
}
}
}

24
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs

@ -0,0 +1,24 @@
using System;
namespace Volo.Abp.BlobStoring.FileSystem
{
public static class FileSystemBlobContainerConfigurationExtensions
{
public static FileSystemBlobProviderConfiguration GetFileSystemConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new FileSystemBlobProviderConfiguration(containerConfiguration);
}
public static BlobContainerConfiguration UseFileSystem(
this BlobContainerConfiguration containerConfiguration,
Action<FileSystemBlobProviderConfiguration> fileSystemConfigureAction)
{
containerConfiguration.ProviderType = typeof(FileSystemBlobProvider);
fileSystemConfigureAction(new FileSystemBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}
}

69
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs

@ -0,0 +1,69 @@
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.IO;
namespace Volo.Abp.BlobStoring.FileSystem
{
//TODO: What if the file is being used on create, delete or read?
public class FileSystemBlobProvider : BlobProviderBase, ITransientDependency
{
protected IBlogFilePathCalculator FilePathCalculator { get; }
public FileSystemBlobProvider(IBlogFilePathCalculator filePathCalculator)
{
FilePathCalculator = filePathCalculator;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath));
var fileMode = args.OverrideExisting
? FileMode.Create
: FileMode.CreateNew;
using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write))
{
//TODO: Truely implement this (like this? http://writeasync.net/?p=2621 or https://www.infoworld.com/article/2995387/how-to-perform-asynchronous-file-operations-in-c.html)
await args.BlobStream.CopyToAsync(
fileStream,
81920, //this is already the default value, but needed to set to be able to pass the cancellationToken
args.CancellationToken
);
await fileStream.FlushAsync();
}
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
return Task.FromResult(FileHelper.DeleteIfExists(filePath));
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
return Task.FromResult(File.Exists(filePath));
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
if (!File.Exists(filePath))
{
return Task.FromResult<Stream>(null);
}
return Task.FromResult<Stream>(File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
}
}

28
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfiguration.cs

@ -0,0 +1,28 @@
namespace Volo.Abp.BlobStoring.FileSystem
{
public class FileSystemBlobProviderConfiguration
{
public string BasePath
{
get => _containerConfiguration.GetConfiguration<string>(FileSystemBlobProviderConfigurationNames.BasePath);
set => _containerConfiguration.SetConfiguration(FileSystemBlobProviderConfigurationNames.BasePath, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// Default value: true.
/// </summary>
public bool AppendContainerNameToBasePath
{
get => _containerConfiguration.GetConfigurationOrDefault(FileSystemBlobProviderConfigurationNames.AppendContainerNameToBasePath, true);
set => _containerConfiguration.SetConfiguration(FileSystemBlobProviderConfigurationNames.AppendContainerNameToBasePath, value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public FileSystemBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
}

8
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfigurationNames.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring.FileSystem
{
public static class FileSystemBlobProviderConfigurationNames
{
public const string BasePath = "FileSystem.BasePath";
public const string AppendContainerNameToBasePath = "FileSystem.AppendContainerNameToBasePath";
}
}

7
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/IBlogFilePathCalculator.cs

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

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

23
framework/src/Volo.Abp.BlobStoring/Volo.Abp.BlobStoring.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring</AssemblyName>
<PackageId>Volo.Abp.BlobStoring</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.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup>
</Project>

28
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringModule.cs

@ -0,0 +1,28 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
[DependsOn(
typeof(AbpMultiTenancyModule),
typeof(AbpThreadingModule)
)]
public class AbpBlobStoringModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(
typeof(IBlobContainer<>),
typeof(BlobContainer<>)
);
context.Services.AddTransient(
typeof(IBlobContainer),
serviceProvider => serviceProvider
.GetRequiredService<IBlobContainer<DefaultContainer>>()
);
}
}
}

12
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringOptions.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.BlobStoring
{
public class AbpBlobStoringOptions
{
public BlobContainerConfigurations Containers { get; }
public AbpBlobStoringOptions()
{
Containers = new BlobContainerConfigurations();
}
}
}

198
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs

@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
public class BlobContainer<TContainer> : IBlobContainer<TContainer>
where TContainer : class
{
private readonly IBlobContainer _container;
public BlobContainer(IBlobContainerFactory blobContainerFactory)
{
_container = blobContainerFactory.Create<TContainer>();
}
public Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default)
{
return _container.SaveAsync(
name,
stream,
overrideExisting,
cancellationToken
);
}
public Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.DeleteAsync(
name,
cancellationToken
);
}
public Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.ExistsAsync(
name,
cancellationToken
);
}
public Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.GetAsync(
name,
cancellationToken
);
}
public Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.GetOrNullAsync(
name,
cancellationToken
);
}
}
public class BlobContainer : IBlobContainer
{
protected string ContainerName { get; }
protected BlobContainerConfiguration Configuration { get; }
protected IBlobProvider Provider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public BlobContainer(
string containerName,
BlobContainerConfiguration configuration,
IBlobProvider provider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider)
{
ContainerName = containerName;
Configuration = configuration;
Provider = provider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
}
public virtual Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default)
{
return Provider.SaveAsync(
new BlobProviderSaveArgs(
ContainerName,
Configuration,
name,
stream,
overrideExisting,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.DeleteAsync(
new BlobProviderDeleteArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.ExistsAsync(
new BlobProviderExistsArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default)
{
var stream = Provider.GetOrNullAsync(
new BlobProviderGetArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
if (stream == null)
{
//TODO: Consider to throw some type of "not found" exception and handle on the HTTP status side
throw new AbpException($"Could not found the requested BLOB '{name}' in the container '{ContainerName}'!");
}
return stream;
}
public virtual Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.GetOrNullAsync(
new BlobProviderGetArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
protected virtual Guid? GetTenantIdOrNull()
{
if (!Configuration.IsMultiTenant)
{
return null;
}
return CurrentTenant.Id;
}
}
}

71
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerConfiguration
{
/// <summary>
/// The provider to be used to store BLOBs of this container.
/// </summary>
public Type ProviderType { get; set; }
/// <summary>
/// Indicates whether this container is multi-tenant or not.
///
/// If this is <code>false</code> and your application is multi-tenant,
/// then the container is shared by all tenants in the system.
///
/// This can be <code>true</code> even if your application is not multi-tenant.
///
/// Default: true.
/// </summary>
public bool IsMultiTenant { get; set; } = true;
[NotNull] private readonly Dictionary<string, object> _properties;
[CanBeNull] private readonly BlobContainerConfiguration _fallbackConfiguration;
public BlobContainerConfiguration(BlobContainerConfiguration fallbackConfiguration = null)
{
_fallbackConfiguration = fallbackConfiguration;
_properties = new Dictionary<string, object>();
}
[CanBeNull]
public T GetConfigurationOrDefault<T>(string name, T defaultValue = default)
{
return (T) GetConfigurationOrNull(name, defaultValue);
}
[CanBeNull]
public object GetConfigurationOrNull(string name, object defaultValue = null)
{
return _properties.GetOrDefault(name) ??
_fallbackConfiguration?.GetConfigurationOrNull(name, defaultValue) ??
defaultValue;
}
[NotNull]
public BlobContainerConfiguration SetConfiguration([NotNull] string name, [CanBeNull] object value)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNull(value, nameof(value));
_properties[name] = value;
return this;
}
[NotNull]
public BlobContainerConfiguration ClearConfiguration([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
_properties.Remove(name);
return this;
}
}
}

27
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationExtensions.cs

@ -0,0 +1,27 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerConfigurationExtensions
{
public static T GetConfiguration<T>(
[NotNull] this BlobContainerConfiguration containerConfiguration,
[NotNull] string name)
{
return (T) containerConfiguration.GetConfiguration(name);
}
public static object GetConfiguration(
[NotNull] this BlobContainerConfiguration containerConfiguration,
[NotNull] string name)
{
var value = containerConfiguration.GetConfigurationOrNull(name);
if (value == null)
{
throw new AbpException($"Could not find the configuration value for '{name}'!");
}
return value;
}
}
}

11
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationProviderExtensions.cs

@ -0,0 +1,11 @@
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerConfigurationProviderExtensions
{
public static BlobContainerConfiguration Get<TContainer>(
this IBlobContainerConfigurationProvider configurationProvider)
{
return configurationProvider.Get(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
}
}

61
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurations.cs

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerConfigurations
{
public BlobContainerConfiguration Default { get; }
private readonly Dictionary<string, BlobContainerConfiguration> _containers;
public BlobContainerConfigurations()
{
Default = new BlobContainerConfiguration();
_containers = new Dictionary<string, BlobContainerConfiguration>();
}
public BlobContainerConfigurations Configure<TContainer>(
Action<BlobContainerConfiguration> configureAction)
{
return Configure(
BlobContainerNameAttribute.GetContainerName<TContainer>(),
configureAction
);
}
public BlobContainerConfigurations Configure(
[NotNull] string name,
[NotNull] Action<BlobContainerConfiguration> configureAction)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNull(configureAction, nameof(configureAction));
configureAction(_containers.GetOrAdd(name, () => new BlobContainerConfiguration(Default)));
return this;
}
public BlobContainerConfigurations ConfigureDefault(Action<BlobContainerConfiguration> configureAction)
{
configureAction(Default);
return this;
}
[NotNull]
public BlobContainerConfiguration GetConfiguration<TContainer>()
{
return GetConfiguration(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
[NotNull]
public BlobContainerConfiguration GetConfiguration([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
return _containers.GetOrDefault(name) ??
Default;
}
}
}

43
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs

@ -0,0 +1,43 @@
using System.Threading;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerFactory : IBlobContainerFactory, ITransientDependency
{
protected IBlobProviderSelector ProviderSelector { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public BlobContainerFactory(
IBlobContainerConfigurationProvider configurationProvider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider,
IBlobProviderSelector providerSelector)
{
ConfigurationProvider = configurationProvider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
ProviderSelector = providerSelector;
}
public virtual IBlobContainer Create(string name, CancellationToken cancellationToken = default)
{
var configuration = ConfigurationProvider.Get(name);
return new BlobContainer(
name,
configuration,
ProviderSelector.Get(name),
CurrentTenant,
CancellationTokenProvider
);
}
}
}

26
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs

@ -0,0 +1,26 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerFactoryExtensions
{
/// <summary>
/// Gets a named container.
/// </summary>
/// <param name="blobContainerFactory">The blob container manager</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// The container object.
/// </returns>
public static IBlobContainer Create<TContainer>(
this IBlobContainerFactory blobContainerFactory,
CancellationToken cancellationToken = default
)
{
return blobContainerFactory.Create(
BlobContainerNameAttribute.GetContainerName<TContainer>(),
cancellationToken
);
}
}
}

41
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerNameAttribute.cs

@ -0,0 +1,41 @@
using System;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerNameAttribute : Attribute
{
[NotNull]
public string Name { get; }
public BlobContainerNameAttribute([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Name = name;
}
public virtual string GetName(Type type)
{
return Name;
}
public static string GetContainerName<T>()
{
return GetContainerName(typeof(T));
}
public static string GetContainerName(Type type)
{
var nameAttribute = type.GetCustomAttribute<BlobContainerNameAttribute>();
if (nameAttribute == null)
{
return type.FullName;
}
return nameAttribute.GetName(type);
}
}
}

36
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs

@ -0,0 +1,36 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public abstract class BlobProviderArgs
{
[NotNull]
public string ContainerName { get; }
[NotNull]
public BlobContainerConfiguration Configuration { get; }
[NotNull]
public string BlobName { get; }
public CancellationToken CancellationToken { get; }
public Guid? TenantId { get; }
protected BlobProviderArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
{
ContainerName = Check.NotNullOrWhiteSpace(containerName, nameof(containerName));
Configuration = Check.NotNull(configuration, nameof(configuration));
BlobName = Check.NotNullOrWhiteSpace(blobName, nameof(blobName));
TenantId = tenantId;
CancellationToken = cancellationToken;
}
}
}

16
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs

@ -0,0 +1,16 @@
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public abstract class BlobProviderBase : IBlobProvider
{
public abstract Task SaveAsync(BlobProviderSaveArgs args);
public abstract Task<bool> DeleteAsync(BlobProviderDeleteArgs args);
public abstract Task<bool> ExistsAsync(BlobProviderExistsArgs args);
public abstract Task<Stream> GetOrNullAsync(BlobProviderGetArgs args);
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderDeleteArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderDeleteArgs : BlobProviderArgs
{
public BlobProviderDeleteArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderExistsArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderExistsArgs : BlobProviderArgs
{
public BlobProviderExistsArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderGetArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderGetArgs : BlobProviderArgs
{
public BlobProviderGetArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

34
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSaveArgs.cs

@ -0,0 +1,34 @@
using System;
using System.IO;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderSaveArgs : BlobProviderArgs
{
[NotNull]
public Stream BlobStream { get; }
public bool OverrideExisting { get; }
public BlobProviderSaveArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[NotNull] Stream blobStream,
bool overrideExisting = false,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
BlobStream = Check.NotNull(blobStream, nameof(blobStream));
OverrideExisting = overrideExisting;
}
}
}

15
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSelectorExtensions.cs

@ -0,0 +1,15 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public static class BlobProviderSelectorExtensions
{
public static IBlobProvider Get<TContainer>(
[NotNull] this IBlobProviderSelector selector)
{
Check.NotNull(selector, nameof(selector));
return selector.Get(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
}
}

20
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobContainerConfigurationProvider.cs

@ -0,0 +1,20 @@
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring
{
public class DefaultBlobContainerConfigurationProvider : IBlobContainerConfigurationProvider, ITransientDependency
{
protected AbpBlobStoringOptions Options { get; }
public DefaultBlobContainerConfigurationProvider(IOptions<AbpBlobStoringOptions> options)
{
Options = options.Value;
}
public virtual BlobContainerConfiguration Get(string name)
{
return Options.Containers.GetConfiguration(name);
}
}
}

49
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobProviderSelector.cs

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.BlobStoring
{
public class DefaultBlobProviderSelector : IBlobProviderSelector, ITransientDependency
{
protected IEnumerable<IBlobProvider> BlobProviders { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public DefaultBlobProviderSelector(
IBlobContainerConfigurationProvider configurationProvider,
IEnumerable<IBlobProvider> blobProviders)
{
ConfigurationProvider = configurationProvider;
BlobProviders = blobProviders;
}
[NotNull]
public virtual IBlobProvider Get([NotNull] string containerName)
{
Check.NotNull(containerName, nameof(containerName));
var configuration = ConfigurationProvider.Get(containerName);
if (!BlobProviders.Any())
{
throw new AbpException("No BLOB Storage provider was registered! At least one provider must be registered to be able to use the Blog Storing System.");
}
foreach (var provider in BlobProviders)
{
if (ProxyHelper.GetUnProxiedType(provider).IsAssignableTo(configuration.ProviderType))
{
return provider;
}
}
throw new AbpException(
$"Could not find the BLOB Storage provider with the type ({configuration.ProviderType.AssemblyQualifiedName}) configured for the container {containerName} and no default provider was set."
);
}
}
}

8
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring
{
[BlobContainerName("Default")]
public class DefaultContainer
{
}
}

88
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainer.cs

@ -0,0 +1,88 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainer<TContainer> : IBlobContainer
where TContainer: class
{
}
public interface IBlobContainer
{
/// <summary>
/// Saves a blob <see cref="Stream"/> to the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="stream">A stream for the blob</param>
/// <param name="overrideExisting">
/// Set <code>true</code> to override if there is already a blob in the container with the given name.
/// If set to <code>false</code> (default), throws exception if there is already a blob in the container with the given name.
/// </param>
/// <param name="cancellationToken">Cancellation token</param>
Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// Deletes a blob from the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// Returns true if actually deleted the blob.
/// Returns false if the blob with the given <paramref name="name"/> was not exists.
/// </returns>
Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Checks if a blob does exists in the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Gets a blob from the container.
/// It actually gets a <see cref="Stream"/> to read the blob data.
/// It throws exception if there is no blob with the given <paramref name="name"/>.
/// Use <see cref="GetOrNullAsync"/> if you want to get <code>null</code> if there is no blob with the given <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// A <see cref="Stream"/> to read the blob data.
/// </returns>
Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Gets a blob from the container, or returns null if there is no blob with the given <paramref name="name"/>.
/// It actually gets a <see cref="Stream"/> to read the blob data.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// A <see cref="Stream"/> to read the blob data.
/// </returns>
Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default
);
//TODO: Create shortcut extension methods: GetAsArraryAsync, GetAsStringAsync(encoding) (and null versions)
}
}

7
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainerConfigurationProvider
{
BlobContainerConfiguration Get(string name);
}
}

20
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs

@ -0,0 +1,20 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainerFactory
{
/// <summary>
/// Gets a named container.
/// </summary>
/// <param name="name">The name of the container</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// The container object.
/// </returns>
IBlobContainer Create(
string name,
CancellationToken cancellationToken = default
);
}
}

17
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProvider.cs

@ -0,0 +1,17 @@
using System;
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public interface IBlobProvider
{
Task SaveAsync(BlobProviderSaveArgs args);
Task<bool> DeleteAsync(BlobProviderDeleteArgs args);
Task<bool> ExistsAsync(BlobProviderExistsArgs args);
Task<Stream> GetOrNullAsync(BlobProviderGetArgs args);
}
}

10
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProviderSelector.cs

@ -0,0 +1,10 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public interface IBlobProviderSelector
{
[NotNull]
IBlobProvider Get([NotNull] string containerName);
}
}

9
framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/ProxyHelper.cs

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.DynamicProxy
@ -6,6 +7,7 @@ namespace Volo.Abp.DynamicProxy
public static class ProxyHelper
{
private const string ProxyNamespace = "Castle.Proxies";
/// <summary>
/// Returns dynamic proxy target object if this is a proxied object, otherwise returns the given object.
/// It supports Castle Dynamic Proxies.
@ -28,5 +30,10 @@ namespace Volo.Abp.DynamicProxy
return targetField.GetValue(obj);
}
public static Type GetUnProxiedType(object obj)
{
return UnProxy(obj).GetType();
}
}
}

8
framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs

@ -16,6 +16,14 @@ namespace Volo.Abp.IO
Directory.CreateDirectory(directory);
}
}
public static void CreateIfNotExists(DirectoryInfo directory)
{
if (!directory.Exists)
{
directory.Create();
}
}
public static bool IsSubDirectoryOf([NotNull] string parentDirectoryPath, [NotNull] string childDirectoryPath)
{

9
framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs

@ -17,12 +17,15 @@ namespace Volo.Abp.IO
/// Checks and deletes given file if it does exists.
/// </summary>
/// <param name="filePath">Path of the file</param>
public static void DeleteIfExists(string filePath)
public static bool DeleteIfExists(string filePath)
{
if (File.Exists(filePath))
if (!File.Exists(filePath))
{
File.Delete(filePath);
return false;
}
File.Delete(filePath);
return true;
}
/// <summary>

17
framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo.Abp.BlobStoring.FileSystem.Tests.csproj

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.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" />
</ItemGroup>
</Project>

27
framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemTestModule.cs

@ -0,0 +1,27 @@
using System;
using System.IO;
using Volo.Abp.BlobStoring.FileSystem.TestObjects;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.FileSystem
{
[DependsOn(
typeof(AbpBlobStoringFileSystemModule)
)]
public class AbpBlobStoringFileSystemTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
});
});
});
}
}
}

12
framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringTestBase.cs

@ -0,0 +1,12 @@
using Volo.Abp.Testing;
namespace Volo.Abp.BlobStoring.FileSystem
{
public abstract class AbpBlobStoringFileSystemTestBase : AbpIntegratedTest<AbpBlobStoringFileSystemTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

71
framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/BlogFilePathCalculator_Tests.cs

@ -0,0 +1,71 @@
using System;
using System.IO;
using Shouldly;
using Xunit;
namespace Volo.Abp.BlobStoring.FileSystem
{
public class BlogFilePathCalculator_Tests : AbpBlobStoringFileSystemTestBase
{
private readonly IBlogFilePathCalculator _calculator;
public BlogFilePathCalculator_Tests()
{
_calculator = GetRequiredService<IBlogFilePathCalculator>();
}
[Fact]
public void Default_Settings()
{
var separator = Path.DirectorySeparatorChar;
_calculator.Calculate(
GetArgs($"C:{separator}my-files", "my-container", "my-blob")
).ShouldBe($"C:{separator}my-files{separator}host{separator}my-container{separator}my-blob");
}
[Fact]
public void Default_Settings_With_TenantId()
{
var separator = Path.DirectorySeparatorChar;
var tenantId = Guid.NewGuid();
_calculator.Calculate(
GetArgs($"C:{separator}my-files", "my-container", "my-blob", tenantId: tenantId)
).ShouldBe($"C:{separator}my-files{separator}tenants{separator}{tenantId:D}{separator}my-container{separator}my-blob");
}
[Fact]
public void AppendContainerNameToBasePath_Set_To_False()
{
var separator = Path.DirectorySeparatorChar;
_calculator.Calculate(
GetArgs($"C:{separator}my-files", "my-container", "my-blob", appendContainerNameToBasePath: false)
).ShouldBe($"C:{separator}my-files{separator}host{separator}my-blob");
}
private BlobProviderArgs GetArgs(
string basePath,
string containerName,
string blobName,
Guid? tenantId = null,
bool? appendContainerNameToBasePath = null)
{
return new BlobProviderGetArgs(
containerName,
new BlobContainerConfiguration()
.UseFileSystem(fs =>
{
fs.BasePath = basePath;
if (appendContainerNameToBasePath.HasValue)
{
fs.AppendContainerNameToBasePath = appendContainerNameToBasePath.Value;
}
}),
blobName,
tenantId
);
}
}
}

8
framework/test/Volo.Abp.BlobStoring.FileSystem.Tests/Volo/Abp/BlobStoring/FileSystem/TestObjects/TestContainer.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring.FileSystem.TestObjects
{
[BlobContainerName("TestContainer")]
public class TestContainer
{
}
}

17
framework/test/Volo.Abp.BlobStoring.Tests/Volo.Abp.BlobStoring.Tests.csproj

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
</Project>

41
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringOptions_Tests.cs

@ -0,0 +1,41 @@
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.BlobStoring.Fakes;
using Volo.Abp.BlobStoring.TestObjects;
using Xunit;
namespace Volo.Abp.BlobStoring
{
public class AbpBlobStoringOptions_Tests : AbpBlobStoringTestBase
{
private readonly IBlobContainerConfigurationProvider _configurationProvider;
public AbpBlobStoringOptions_Tests()
{
_configurationProvider = GetRequiredService<IBlobContainerConfigurationProvider>();
}
[Fact]
public void Should_Property_Set_And_Get_Options_For_Different_Containers()
{
var testContainer1Config = _configurationProvider.Get<TestContainer1>();
testContainer1Config.ProviderType.ShouldBe(typeof(FakeBlobProvider1));
testContainer1Config.GetConfigurationOrDefault<string>("TestConfig1").ShouldBe("TestValue1");
testContainer1Config.GetConfigurationOrDefault<string>("TestConfigDefault").ShouldBe("TestValueDefault");
var testContainer2Config = _configurationProvider.Get<TestContainer2>();
testContainer2Config.ProviderType.ShouldBe(typeof(FakeBlobProvider2));
testContainer2Config.GetConfigurationOrNull("TestConfig2").ShouldBe("TestValue2");
testContainer2Config.GetConfigurationOrNull("TestConfigDefault").ShouldBe("TestValueDefault");
}
[Fact]
public void Should_Fallback_To_Default_Configuration_If_Not_Specialized()
{
var config = _configurationProvider.Get<TestContainer3>();
config.ProviderType.ShouldBe(typeof(FakeBlobProvider1));
config.GetConfigurationOrNull("TestConfigDefault").ShouldBe("TestValueDefault");
}
}
}

12
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringTestBase.cs

@ -0,0 +1,12 @@
using Volo.Abp.Testing;
namespace Volo.Abp.BlobStoring
{
public abstract class AbpBlobStoringTestBase : AbpIntegratedTest<AbpBlobStoringTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

43
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/AbpBlobStoringTestModule.cs

@ -0,0 +1,43 @@
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Volo.Abp.Autofac;
using Volo.Abp.BlobStoring.Fakes;
using Volo.Abp.BlobStoring.TestObjects;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring
{
[DependsOn(
typeof(AbpBlobStoringModule),
typeof(AbpTestBaseModule),
typeof(AbpAutofacModule)
)]
public class AbpBlobStoringTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton<IBlobProvider>(Substitute.For<FakeBlobProvider1>());
context.Services.AddSingleton<IBlobProvider>(Substitute.For<FakeBlobProvider2>());
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers
.ConfigureDefault(container =>
{
container.SetConfiguration("TestConfigDefault", "TestValueDefault");
container.ProviderType = typeof(FakeBlobProvider1);
})
.Configure<TestContainer1>(container =>
{
container.SetConfiguration("TestConfig1", "TestValue1");
container.ProviderType = typeof(FakeBlobProvider1);
})
.Configure<TestContainer2>(container =>
{
container.SetConfiguration("TestConfig2", "TestValue2");
container.ProviderType = typeof(FakeBlobProvider2);
});
});
}
}
}

76
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainerFactory_Tests.cs

@ -0,0 +1,76 @@
using System.Threading.Tasks;
using NSubstitute;
using Volo.Abp.BlobStoring.Fakes;
using Volo.Abp.BlobStoring.TestObjects;
using Xunit;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerFactory_Tests : AbpBlobStoringTestBase
{
private readonly IBlobContainerFactory _factory;
private readonly FakeProviders _fakeProviders;
public BlobContainerFactory_Tests()
{
_factory = GetRequiredService<IBlobContainerFactory>();
_fakeProviders = GetRequiredService<FakeProviders>();
}
[Fact]
public async Task Should_Create_Containers_With_Configured_Providers()
{
// TestContainer1 with FakeBlobProvider1
await _fakeProviders.Provider1
.DidNotReceiveWithAnyArgs()
.ExistsAsync(default);
await _factory
.Create<TestContainer1>()
.ExistsAsync("TestBlob1");
await _fakeProviders.Provider1
.Received(1)
.ExistsAsync(Arg.Is<BlobProviderExistsArgs>(args =>
args.ContainerName == BlobContainerNameAttribute.GetContainerName<TestContainer1>() &&
args.BlobName == "TestBlob1"
)
);
// TestContainer2 with FakeBlobProvider2
await _fakeProviders.Provider2
.DidNotReceiveWithAnyArgs()
.ExistsAsync(default);
await _factory
.Create<TestContainer2>()
.ExistsAsync("TestBlob2");
await _fakeProviders.Provider2
.Received(1)
.ExistsAsync(Arg.Is<BlobProviderExistsArgs>(args =>
args.ContainerName == BlobContainerNameAttribute.GetContainerName<TestContainer2>() &&
args.BlobName == "TestBlob2"
)
);
// TestContainer3 with FakeBlobProvider1 (default provider)
_fakeProviders.Provider1.ClearReceivedCalls();
await _factory
.Create<TestContainer3>()
.ExistsAsync("TestBlob3");
await _fakeProviders.Provider1
.Received(1)
.ExistsAsync(Arg.Is<BlobProviderExistsArgs>(t =>
t.ContainerName == BlobContainerNameAttribute.GetContainerName<TestContainer3>() &&
t.BlobName == "TestBlob3"
)
);
}
}
}

25
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainerNameAttribute_Tests.cs

@ -0,0 +1,25 @@
using Shouldly;
using Volo.Abp.BlobStoring.TestObjects;
using Xunit;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerNameAttribute_Tests
{
[Fact]
public void Should_Get_Specified_Name()
{
BlobContainerNameAttribute
.GetContainerName<TestContainer2>()
.ShouldBe("Test2");
}
[Fact]
public void Should_Get_Full_Class_Name_If_Not_Specified()
{
BlobContainerNameAttribute
.GetContainerName<TestContainer1>()
.ShouldBe(typeof(TestContainer1).FullName);
}
}
}

32
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Injection_Tests.cs

@ -0,0 +1,32 @@
using Shouldly;
using Volo.Abp.BlobStoring.TestObjects;
using Xunit;
namespace Volo.Abp.BlobStoring
{
public class BlobContainer_Injection_Tests : AbpBlobStoringTestBase
{
[Fact]
public void Should_Inject_DefaultContainer_For_Non_Generic_Interface()
{
GetRequiredService<IBlobContainer>()
.ShouldBeOfType<BlobContainer<DefaultContainer>>();
}
[Fact]
public void Should_Inject_Specified_Container_For_Generic_Interface()
{
GetRequiredService<IBlobContainer<DefaultContainer>>()
.ShouldBeOfType<BlobContainer<DefaultContainer>>();
GetRequiredService<IBlobContainer<TestContainer1>>()
.ShouldBeOfType<BlobContainer<TestContainer1>>();
GetRequiredService<IBlobContainer<TestContainer2>>()
.ShouldBeOfType<BlobContainer<TestContainer2>>();
GetRequiredService<IBlobContainer<TestContainer3>>()
.ShouldBeOfType<BlobContainer<TestContainer3>>();
}
}
}

31
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobProviderSelector_Tests.cs

@ -0,0 +1,31 @@
using Shouldly;
using Volo.Abp.BlobStoring.Fakes;
using Volo.Abp.BlobStoring.TestObjects;
using Volo.Abp.DynamicProxy;
using Xunit;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderSelector_Tests : AbpBlobStoringTestBase
{
private readonly IBlobProviderSelector _selector;
public BlobProviderSelector_Tests()
{
_selector = GetRequiredService<IBlobProviderSelector>();
}
[Fact]
public void Should_Select_Default_Provider_If_Not_Configured()
{
_selector.Get<TestContainer3>().ShouldBeAssignableTo<FakeBlobProvider1>();
}
[Fact]
public void Should_Select_Configured_Provider()
{
_selector.Get<TestContainer1>().ShouldBeAssignableTo<FakeBlobProvider1>();
_selector.Get<TestContainer2>().ShouldBeAssignableTo<FakeBlobProvider2>();
}
}
}

33
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeBlobProvider1.cs

@ -0,0 +1,33 @@
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring.Fakes
{
public class FakeBlobProvider1 : IBlobProvider
{
public virtual Task SaveAsync(BlobProviderSaveArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<Stream> GetAsync(BlobProviderGetArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
throw new System.NotImplementedException();
}
}
}

33
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeBlobProvider2.cs

@ -0,0 +1,33 @@
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring.Fakes
{
public class FakeBlobProvider2 : IBlobProvider
{
public virtual Task SaveAsync(BlobProviderSaveArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<Stream> GetAsync(BlobProviderGetArgs args)
{
throw new System.NotImplementedException();
}
public virtual Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
throw new System.NotImplementedException();
}
}
}

19
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/Fakes/FakeProviders.cs

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Fakes
{
public class FakeProviders : ISingletonDependency
{
public FakeBlobProvider1 Provider1 { get; }
public FakeBlobProvider2 Provider2 { get; }
public FakeProviders(IEnumerable<IBlobProvider> providers)
{
Provider1 = providers.OfType<FakeBlobProvider1>().Single();
Provider2 = providers.OfType<FakeBlobProvider2>().Single();
}
}
}

7
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer1.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring.TestObjects
{
public class TestContainer1
{
}
}

8
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer2.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring.TestObjects
{
[BlobContainerName("Test2")]
public class TestContainer2
{
}
}

7
framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/TestObjects/TestContainer3.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring.TestObjects
{
public class TestContainer3
{
}
}

2
nupkg/common.ps1

@ -55,6 +55,8 @@ $projects = (
"framework/src/Volo.Abp.BackgroundJobs.Quartz",
"framework/src/Volo.Abp.BackgroundWorkers",
"framework/src/Volo.Abp.BackgroundWorkers.Quartz",
"framework/src/Volo.Abp.BlobStoring",
"framework/src/Volo.Abp.BlobStoring.FileSystem",
"framework/src/Volo.Abp.Caching",
"framework/src/Volo.Abp.Castle.Core",
"framework/src/Volo.Abp.Cli.Core",

Loading…
Cancel
Save