Skip to content

Commit

Permalink
Merge pull request #6708 from totpero/blob-storing
Browse files Browse the repository at this point in the history
Blob storing feature
  • Loading branch information
ismcagdas committed Nov 10, 2023
2 parents 3fb1a74 + 21d7268 commit 4b8eeb0
Show file tree
Hide file tree
Showing 110 changed files with 3,202 additions and 15 deletions.
42 changes: 42 additions & 0 deletions Abp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abp.ZeroCore.NHibernate", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abp.HtmlSanitizer", "src\Abp.HtmlSanitizer\Abp.HtmlSanitizer.csproj", "{BC2B3FA8-40D1-49AF-AD73-703A163F1BAD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring", "src\Abp.BlobStoring\Abp.BlobStoring.csproj", "{0A5E11F6-02FC-4244-90C2-888209506694}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.FileSystem", "src\Abp.BlobStoring.FileSystem\Abp.BlobStoring.FileSystem.csproj", "{31BCFB0E-8930-49DE-9587-DA83B3968CFB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.Azure", "src\Abp.BlobStoring.Azure\Abp.BlobStoring.Azure.csproj", "{E262671E-572A-41A6-8FF5-E77FEF6F22DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.Tests", "test\Abp.BlobStoring.Tests\Abp.BlobStoring.Tests.csproj", "{83033E58-88EB-4746-A15D-02E3DC1BB03A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.FileSystem.Tests", "test\Abp.BlobStoring.FileSystem.Tests\Abp.BlobStoring.FileSystem.Tests.csproj", "{3B7382DA-99D4-48AA-B8B9-16D905D60BFF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.BlobStoring.Azure.Tests", "test\Abp.BlobStoring.Azure.Tests\Abp.BlobStoring.Azure.Tests.csproj", "{168BE615-DA0F-42FA-98F4-6694034DBF8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -499,6 +511,30 @@ Global
{BC2B3FA8-40D1-49AF-AD73-703A163F1BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC2B3FA8-40D1-49AF-AD73-703A163F1BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC2B3FA8-40D1-49AF-AD73-703A163F1BAD}.Release|Any CPU.Build.0 = Release|Any CPU
{0A5E11F6-02FC-4244-90C2-888209506694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A5E11F6-02FC-4244-90C2-888209506694}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A5E11F6-02FC-4244-90C2-888209506694}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A5E11F6-02FC-4244-90C2-888209506694}.Release|Any CPU.Build.0 = Release|Any CPU
{31BCFB0E-8930-49DE-9587-DA83B3968CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31BCFB0E-8930-49DE-9587-DA83B3968CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31BCFB0E-8930-49DE-9587-DA83B3968CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31BCFB0E-8930-49DE-9587-DA83B3968CFB}.Release|Any CPU.Build.0 = Release|Any CPU
{E262671E-572A-41A6-8FF5-E77FEF6F22DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E262671E-572A-41A6-8FF5-E77FEF6F22DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E262671E-572A-41A6-8FF5-E77FEF6F22DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E262671E-572A-41A6-8FF5-E77FEF6F22DE}.Release|Any CPU.Build.0 = Release|Any CPU
{83033E58-88EB-4746-A15D-02E3DC1BB03A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83033E58-88EB-4746-A15D-02E3DC1BB03A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83033E58-88EB-4746-A15D-02E3DC1BB03A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83033E58-88EB-4746-A15D-02E3DC1BB03A}.Release|Any CPU.Build.0 = Release|Any CPU
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF}.Release|Any CPU.Build.0 = Release|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{168BE615-DA0F-42FA-98F4-6694034DBF8A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -584,6 +620,12 @@ Global
{DE3D4F5B-70A2-488B-A478-2C73FFE36D46} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{01F71E3D-5010-43AA-870A-ADED860FF73B} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{BC2B3FA8-40D1-49AF-AD73-703A163F1BAD} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{0A5E11F6-02FC-4244-90C2-888209506694} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{31BCFB0E-8930-49DE-9587-DA83B3968CFB} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{E262671E-572A-41A6-8FF5-E77FEF6F22DE} = {DFF0464B-5402-4DD6-86F5-2AEC1163B232}
{83033E58-88EB-4746-A15D-02E3DC1BB03A} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
{3B7382DA-99D4-48AA-B8B9-16D905D60BFF} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
{168BE615-DA0F-42FA-98F4-6694034DBF8A} = {1E6B9E8D-D5C1-4AD7-89F9-7179FEFD7C01}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5A1CB38E-F77D-4A40-B3A9-9A70C3F3BC6D}
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

Expand Down
3 changes: 3 additions & 0 deletions nupkg/pack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ $projects = (
"Abp.AspNetCore.TestBase",
"Abp.AspNetCore.PerRequestRedisCache",
"Abp.AutoMapper",
"Abp.BlobStoring",
"Abp.BlobStoring.Azure",
"Abp.BlobStoring.FileSystem",
"Abp.Castle.Log4Net",
"Abp.Dapper",
"Abp.EntityFramework",
Expand Down
39 changes: 39 additions & 0 deletions src/Abp.BlobStoring.Azure/Abp.BlobStoring.Azure.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<Import Project="..\..\configureawait.props" />

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>Abp.BlobStoring.Azure</AssemblyName>
<PackageId>Abp.BlobStoring.Azure</PackageId>
<PackageTags>asp.net;asp.net mvc;boilerplate;application framework;web framework;framework;domain driven design;blob storing; azure</PackageTags>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<RootNamespace>Abp</RootNamespace>
<Description>Abp.BlobStoring.Azure</Description>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Abp.BlobStoring\Abp.BlobStoring.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="Fody" Version="6.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="1.1.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Reflection;
using Abp.Modules;

namespace Abp.BlobStoring.Azure
{
[DependsOn(typeof(AbpBlobStoringModule))]
public class AbpBlobStoringAzureModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Abp.BlobStoring.Azure
{
public static class AzureBlobContainerConfigurationExtensions
{
public static AzureBlobProviderConfiguration GetAzureConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new AzureBlobProviderConfiguration(containerConfiguration);
}

public static BlobContainerConfiguration UseAzure(
this BlobContainerConfiguration containerConfiguration,
Action<AzureBlobProviderConfiguration> azureConfigureAction)
{
containerConfiguration.ProviderType = typeof(AzureBlobProvider);
containerConfiguration.NamingNormalizers.TryAdd<AzureBlobNamingNormalizer>();

azureConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration));

return containerConfiguration;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Globalization;
using System.Text.RegularExpressions;
using Abp.Localization;
using Abp.Dependency;

namespace Abp.BlobStoring.Azure
{
public class AzureBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency
{
/// <summary>
///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
/// </summary>
public virtual string NormalizeContainerName(string containerName)
{
using (CultureInfoHelper.Use(CultureInfo.InvariantCulture))
{
// All letters in a container name must be lowercase.
containerName = containerName.ToLower();

// Container names must be from 3 through 63 characters long.
if (containerName.Length > 63)
{
containerName = containerName.Substring(0, 63);
}

// 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";
}
}

return containerName;
}
}

/// <summary>
///https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names
/// </summary>
public virtual string NormalizeBlobName(string blobName)
{
return blobName;
}
}
}
112 changes: 112 additions & 0 deletions src/Abp.BlobStoring.Azure/BlobStoring/Azure/AzureBlobProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using Abp.Dependency;
using Abp.Extensions;
using Azure.Storage.Blobs;
using System.IO;
using System.Threading.Tasks;

namespace Abp.BlobStoring.Azure
{
public class AzureBlobProvider : BlobProviderBase, ITransientDependency
{
protected IAzureBlobNameCalculator AzureBlobNameCalculator { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }

public AzureBlobProvider(
IAzureBlobNameCalculator azureBlobNameCalculator,
IBlobNormalizeNamingService blobNormalizeNamingService)
{
AzureBlobNameCalculator = azureBlobNameCalculator;
BlobNormalizeNamingService = blobNormalizeNamingService;
}

public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
var configuration = args.Configuration.GetAzureConfiguration();

if (!args.OverrideExisting && await BlobExistsAsync(args, blobName))
{
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}

if (configuration.CreateContainerIfNotExists)
{
await CreateContainerIfNotExists(args);
}

await GetBlobClient(args, blobName).UploadAsync(args.BlobStream, true);
}

public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);

if (await BlobExistsAsync(args, blobName))
{
return await GetBlobClient(args, blobName).DeleteIfExistsAsync();
}

return false;
}

public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);

return await BlobExistsAsync(args, blobName);
}

public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);

if (!await BlobExistsAsync(args, blobName))
{
return null;
}

var blobClient = GetBlobClient(args, blobName);
var download = await blobClient.DownloadAsync();
return await TryCopyToMemoryStreamAsync(download.Value.Content, args.CancellationToken);
}

protected virtual BlobClient GetBlobClient(BlobProviderArgs args, string blobName)
{
var blobContainerClient = GetBlobContainerClient(args);
return blobContainerClient.GetBlobClient(blobName);
}

protected virtual BlobContainerClient GetBlobContainerClient(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
var blobServiceClient = new BlobServiceClient(configuration.ConnectionString);
return blobServiceClient.GetBlobContainerClient(GetContainerName(args));
}

protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args)
{
var blobContainerClient = GetBlobContainerClient(args);
await blobContainerClient.CreateIfNotExistsAsync();
}

protected virtual async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName)
{
// Make sure Blob Container exists.
return await ContainerExistsAsync(GetBlobContainerClient(args)) &&
(await GetBlobClient(args, blobName).ExistsAsync()).Value;
}

protected virtual string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName);
}

protected virtual async Task<bool> ContainerExistsAsync(BlobContainerClient blobContainerClient)
{
return (await blobContainerClient.ExistsAsync()).Value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Abp.BlobStoring.Azure
{
public class AzureBlobProviderConfiguration
{
public string ConnectionString
{
get => _containerConfiguration.GetConfiguration<string>(AzureBlobProviderConfigurationNames.ConnectionString);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString, Check.NotNullOrWhiteSpace(value, nameof(value)));
}

/// <summary>
/// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number.
/// Each hyphen must be preceded and followed by a non-hyphen character.
/// The name must also be between 3 and 63 characters long.
/// If this parameter is not specified, the ContainerName of the <see cref="BlobProviderArgs"/> will be used.
/// </summary>
public string ContainerName
{
get => _containerConfiguration.GetConfigurationOrDefault<string>(AzureBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, value);
}

/// <summary>
/// Default value: false.
/// </summary>
public bool CreateContainerIfNotExists
{
get => _containerConfiguration.GetConfigurationOrDefault(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, false);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, value);
}

private readonly BlobContainerConfiguration _containerConfiguration;

public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Abp.BlobStoring.Azure
{
public static class AzureBlobProviderConfigurationNames
{
public const string ConnectionString = "Azure.ConnectionString";
public const string ContainerName = "Azure.ContainerName";
public const string CreateContainerIfNotExists = "Azure.CreateContainerIfNotExists";
}
}

0 comments on commit 4b8eeb0

Please sign in to comment.