Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions ManagedCode.Storage.Azure/AzureBlobStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using ManagedCode.Storage.Azure.Options;
using ManagedCode.Storage.Core;
using ManagedCode.Storage.Core.Models;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ManagedCode.Storage.Azure
{
public class AzureBlobStorage : IBlobStorage
{
private readonly BlobContainerClient _blobContainerClient;

public AzureBlobStorage(AzureBlobStorageConnectionOptions connectionOptions)
{
_blobContainerClient = new BlobContainerClient(
connectionOptions.ConnectionString,
connectionOptions.Container
);

_blobContainerClient.CreateIfNotExists(PublicAccessType.BlobContainer);
}

public void Dispose()
{
}

#region Delete

public async Task DeleteAsync(string blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);
await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken);
}

public async Task DeleteAsync(Blob blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);
await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken);
}

public async Task DeleteAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default)
{
foreach (var blobName in blobs)
{
await DeleteAsync(blobName, cancellationToken);
}
}

public async Task DeleteAsync(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default)
{
foreach (var blob in blobs)
{
await DeleteAsync(blob, cancellationToken);
}
}

#endregion

#region Download

public async Task<Stream> DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);
var res = await blobClient.DownloadStreamingAsync();

return res.Value.Content;
}

public async Task<Stream> DownloadAsStreamAsync(Blob blob, CancellationToken cancellationToken = default)
{
return await DownloadAsStreamAsync(blob.Name, cancellationToken);
}

public async Task<LocalFile> DownloadAsync(string blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);
var localFile = new LocalFile();

await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);

return localFile;
}

public async Task<LocalFile> DownloadAsync(Blob blob, CancellationToken cancellationToken = default)
{
return await DownloadAsync(blob.Name, cancellationToken);
}

#endregion

#region Exists

public async Task<bool> ExistsAsync(string blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);

return await blobClient.ExistsAsync(cancellationToken);
}

public async Task<bool> ExistsAsync(Blob blob, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);

return await blobClient.ExistsAsync(cancellationToken);
}

public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<string> blobs,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
foreach(var blob in blobs)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);
yield return await blobClient.ExistsAsync(cancellationToken);
}
}

public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<Blob> blobs,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
foreach (var blob in blobs)
{
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);
yield return await blobClient.ExistsAsync(cancellationToken);
}
}

#endregion

#region Get

public async Task<Blob> GetBlobAsync(string blob, CancellationToken cancellationToken = default)
{
await Task.Yield();

var blobClient = _blobContainerClient.GetBlobClient(blob);

return new Blob()
{
Name = blobClient.Name,
Uri = blobClient.Uri
};
}

public IAsyncEnumerable<Blob> GetBlobsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}

public IAsyncEnumerable<Blob> GetBlobListAsync(CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}

#endregion

#region Upload

public async Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);
await blobClient.UploadAsync(dataStream, cancellationToken);
}

public async Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default)
{
var blobClient = _blobContainerClient.GetBlobClient(blob);

using (var fs = new FileStream(pathToFile, FileMode.Open, FileAccess.Read))
{
await blobClient.UploadAsync(fs, cancellationToken);
}
}

public async Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default)
{
await UploadAsync(blob.Name, dataStream, append, cancellationToken);
}

public async Task UploadAsync(Blob blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default)
{
await UploadAsync(blob.Name, pathToFile, append, cancellationToken);
}

#endregion
}
}
12 changes: 12 additions & 0 deletions ManagedCode.Storage.Azure/Builders/AzureProviderBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using ManagedCode.Storage.Core.Builders;
using Microsoft.Extensions.DependencyInjection;

namespace ManagedCode.Storage.Azure.Builders
{
public class AzureProviderBuilder : ProviderBuilder
{
public AzureProviderBuilder(IServiceCollection serviceCollection) : base(serviceCollection) {}


}
}
8 changes: 8 additions & 0 deletions ManagedCode.Storage.Azure/EnumeratorCacellationAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace ManagedCode.Storage.Azure
{
internal class EnumeratorCacellationAttribute : Attribute
{
}
}
29 changes: 29 additions & 0 deletions ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using ManagedCode.Storage.Core.Builders;
using ManagedCode.Storage.Core.Helpers;
using ManagedCode.Storage.Core;
using ManagedCode.Storage.Azure.Options;

namespace ManagedCode.Storage.Azure.Extensions
{
public static class ProviderExtensions
{
public static ProviderBuilder AddAzureBlobStorage<TAzureStorage>(
this ProviderBuilder providerBuilder,
Action<AzureBlobStorageConnectionOptions> action)
where TAzureStorage : IBlobStorage
{
var connectionOptions = new AzureBlobStorageConnectionOptions();
action.Invoke(connectionOptions);

var implementationType = TypeHelpers.GetImplementationType<TAzureStorage, AzureBlobStorage, AzureBlobStorageConnectionOptions>();
providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => Activator.CreateInstance(implementationType, connectionOptions));

// Because of AzureBlobStorage does not inherits TAzureStorage, DI complains on unability of casting
// providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => new AzureBlobStorage(connectionOptions));

return providerBuilder;
}
}
}
1 change: 1 addition & 0 deletions ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ManagedCode.Storage.Azure.Options
{
public class AzureBlobStorageConnectionOptions
{
public string ConnectionString { get; set; }
public string Container { get; set; }
}
}
14 changes: 14 additions & 0 deletions ManagedCode.Storage.Core/Builders/ProviderBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;

namespace ManagedCode.Storage.Core.Builders
{
public class ProviderBuilder
{
public IServiceCollection ServiceCollection { get; }

public ProviderBuilder(IServiceCollection serviceCollection)
{
ServiceCollection = serviceCollection;
}
}
}
13 changes: 13 additions & 0 deletions ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using ManagedCode.Storage.Core.Builders;

namespace ManagedCode.Storage.Core.Extensions
{
public static class ServiceCollectionExtensions
{
public static ProviderBuilder AddManagedCodeStorage(this IServiceCollection serviceCollection)
{
return new ProviderBuilder(serviceCollection);
}
}
}
49 changes: 49 additions & 0 deletions ManagedCode.Storage.Core/Helpers/TypeHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ManagedCode.Storage.Core.Helpers
{
public static class TypeHelpers
{
public static Type GetImplementationType<TAbstraction, TImplementation, TOptions>()
where TAbstraction : IBlobStorage
{
var typeSignature = typeof(TAbstraction).Name;
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ManagedCodeCModule");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);

tb.SetParent(typeof(TImplementation));
tb.AddInterfaceImplementation(typeof(TAbstraction));

var newConstructor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
new Type[] { typeof(TOptions) });

var baseConstructors = typeof(TImplementation).GetConstructors(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);

var emitter = newConstructor.GetILGenerator();
emitter.Emit(OpCodes.Nop);

// Load `this` and call base constructor with arguments
emitter.Emit(OpCodes.Ldarg_0);
emitter.Emit(OpCodes.Ldarg, 1);
emitter.Emit(OpCodes.Call, baseConstructors[0]);

emitter.Emit(OpCodes.Ret);

return tb.CreateType();
}
}
}
17 changes: 5 additions & 12 deletions ManagedCode.Storage.Core/IBlobStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ManagedCode.Storage.Core.Models;

namespace ManagedCode.Storage.Core
{
public interface IStorage : IDisposable
public interface IBlobStorage : IDisposable
{
IAsyncEnumerable<Blob> GetBlobListAsync(CancellationToken cancellationToken = default);
IAsyncEnumerable<Blob> GetBlob(string blob, CancellationToken cancellationToken = default);
IAsyncEnumerable<Blob> GetBlob(Blob blob, CancellationToken cancellationToken = default);
IAsyncEnumerable<Blob> GetBlob(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
IAsyncEnumerable<Blob> GetBlob(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default);

IAsyncEnumerable<Blob> GetBlobsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
Task<Blob> GetBlobAsync(string blob, CancellationToken cancellationToken = default);

Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default);
Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default);
Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default);
Expand All @@ -33,11 +32,5 @@ public interface IStorage : IDisposable
Task<bool> ExistsAsync(Blob blob, CancellationToken cancellationToken = default);
IAsyncEnumerable<bool> ExistsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
IAsyncEnumerable<bool> ExistsAsync(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default);

}

public class Blob
{
public string Path { get; set; }
}
}
1 change: 1 addition & 0 deletions ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
</ItemGroup>

Expand Down
10 changes: 10 additions & 0 deletions ManagedCode.Storage.Core/Models/Blob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace ManagedCode.Storage.Core.Models
{
public class Blob
{
public string Name { get; set; }
public Uri Uri { get; set; }
}
}
Loading