From 9d0da607bebbf076111541337bc45ff241db4788 Mon Sep 17 00:00:00 2001 From: dudchenko610 Date: Tue, 18 Jan 2022 21:43:06 +0200 Subject: [PATCH 1/5] added provider extensions --- ManagedCode.Storage.Azure/AzureBlobStorage.cs | 128 ++++++++++++++++++ .../AzureBlobStorageConnectionOptions.cs | 7 + .../Extensions/ProviderExtensions.cs | 21 +++ .../ManagedCode.Storage.Azure.csproj | 1 + .../Builders/ProviderBuilder.cs | 14 ++ .../Extensions/ServiceCollectionExtensions.cs | 13 ++ ManagedCode.Storage.Core/IBlobStorage.cs | 9 +- .../ManagedCode.Storage.Core.csproj | 1 + ManagedCode.Storage.Core/Models/Blob.cs | 7 + .../Azure/DependencyInjectionTests.cs | 8 ++ 10 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 ManagedCode.Storage.Azure/AzureBlobStorage.cs create mode 100644 ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs create mode 100644 ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs create mode 100644 ManagedCode.Storage.Core/Builders/ProviderBuilder.cs create mode 100644 ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs create mode 100644 ManagedCode.Storage.Core/Models/Blob.cs create mode 100644 ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs diff --git a/ManagedCode.Storage.Azure/AzureBlobStorage.cs b/ManagedCode.Storage.Azure/AzureBlobStorage.cs new file mode 100644 index 00000000..cbc1c5b4 --- /dev/null +++ b/ManagedCode.Storage.Azure/AzureBlobStorage.cs @@ -0,0 +1,128 @@ +using ManagedCode.Storage.Core; +using ManagedCode.Storage.Core.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace ManagedCode.Storage.Azure +{ + public class AzureBlobStorage : IBlobStorage + { + public AzureBlobStorage() + { + + } + + public Task DeleteAsync(string blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DeleteAsync(Blob blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public Task DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DownloadAsStreamAsync(Blob blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DownloadAsync(string blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task DownloadAsync(Blob blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task ExistsAsync(string blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task ExistsAsync(Blob blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable GetBlob(string blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable GetBlob(Blob blob, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public IAsyncEnumerable GetBlobListAsync(CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + + public Task UploadAsync(Blob blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs b/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs new file mode 100644 index 00000000..be92cc70 --- /dev/null +++ b/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs @@ -0,0 +1,7 @@ +namespace ManagedCode.Storage.Azure +{ + public class AzureBlobStorageConnectionOptions + { + public string ConnectionString { get; set; } + } +} diff --git a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs new file mode 100644 index 00000000..b544488b --- /dev/null +++ b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Azure.Storage.Blobs; +using ManagedCode.Storage.Core.Builders; + +namespace ManagedCode.Storage.Azure.Extensions +{ + public static class ProviderExtensions + { + public static ProviderBuilder AddAzureBlobStorage(this ProviderBuilder providerBuilder, Action action) + { + var connectionOptions = new AzureBlobStorageConnectionOptions(); + action.Invoke(connectionOptions); + + var blobServiceClient = new BlobServiceClient(connectionOptions.ConnectionString); + providerBuilder.ServiceCollection.AddSingleton(blobServiceClient); + + return providerBuilder; + } + } +} diff --git a/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj b/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj index f47e2998..38f7aa65 100644 --- a/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj +++ b/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj @@ -26,6 +26,7 @@ + diff --git a/ManagedCode.Storage.Core/Builders/ProviderBuilder.cs b/ManagedCode.Storage.Core/Builders/ProviderBuilder.cs new file mode 100644 index 00000000..54617039 --- /dev/null +++ b/ManagedCode.Storage.Core/Builders/ProviderBuilder.cs @@ -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; + } + } +} diff --git a/ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs b/ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..9968f322 --- /dev/null +++ b/ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs @@ -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); + } + } +} diff --git a/ManagedCode.Storage.Core/IBlobStorage.cs b/ManagedCode.Storage.Core/IBlobStorage.cs index aec839ca..7a6519dc 100644 --- a/ManagedCode.Storage.Core/IBlobStorage.cs +++ b/ManagedCode.Storage.Core/IBlobStorage.cs @@ -3,10 +3,11 @@ 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 GetBlobListAsync(CancellationToken cancellationToken = default); IAsyncEnumerable GetBlob(string blob, CancellationToken cancellationToken = default); @@ -33,11 +34,5 @@ public interface IStorage : IDisposable Task ExistsAsync(Blob blob, CancellationToken cancellationToken = default); IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default); IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default); - - } - - public class Blob - { - public string Path { get; set; } } } \ No newline at end of file diff --git a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj index 3b2037fc..90f17e82 100644 --- a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj +++ b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/ManagedCode.Storage.Core/Models/Blob.cs b/ManagedCode.Storage.Core/Models/Blob.cs new file mode 100644 index 00000000..e3b2bd36 --- /dev/null +++ b/ManagedCode.Storage.Core/Models/Blob.cs @@ -0,0 +1,7 @@ +namespace ManagedCode.Storage.Core.Models +{ + public class Blob + { + public string Path { get; set; } + } +} diff --git a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs new file mode 100644 index 00000000..1cd216bd --- /dev/null +++ b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs @@ -0,0 +1,8 @@ + +namespace ManagedCode.Storage.Tests.Azure +{ + public class DependencyInjectionTests + { + + } +} From 2c5a175defebe0ea5a46de9d97bd3657dc2831be Mon Sep 17 00:00:00 2001 From: dudchenko610 Date: Wed, 19 Jan 2022 12:05:20 +0200 Subject: [PATCH 2/5] defined base approach to connect blob services --- ManagedCode.Storage.Azure/AzureBlobStorage.cs | 6 +-- .../AzureBlobStorageConnectionOptions.cs | 1 + .../Extensions/ProviderExtensions.cs | 29 ++++++++++++-- .../IAzureBlobStorage.cs | 8 ++++ .../ManagedCode.Storage.Core.csproj | 4 ++ .../Azure/DependencyInjectionTests.cs | 38 ++++++++++++++++++- .../Azure/IDocumentStorage.cs | 8 ++++ .../Azure/IPhotoStorage.cs | 8 ++++ .../ManagedCode.Storage.Tests.csproj | 1 + 9 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 ManagedCode.Storage.Azure/IAzureBlobStorage.cs create mode 100644 ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs create mode 100644 ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs diff --git a/ManagedCode.Storage.Azure/AzureBlobStorage.cs b/ManagedCode.Storage.Azure/AzureBlobStorage.cs index cbc1c5b4..67df6012 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorage.cs +++ b/ManagedCode.Storage.Azure/AzureBlobStorage.cs @@ -8,11 +8,11 @@ namespace ManagedCode.Storage.Azure { - public class AzureBlobStorage : IBlobStorage + public class AzureBlobStorage : IAzureBlobStorage { - public AzureBlobStorage() + public AzureBlobStorage(/*AzureBlobStorageConnectionOptions connectionOptions*/) { - + // var blobServiceClient = new BlobServiceClient(connectionOptions.ConnectionString); } public Task DeleteAsync(string blob, CancellationToken cancellationToken = default) diff --git a/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs b/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs index be92cc70..87f40c51 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs +++ b/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs @@ -3,5 +3,6 @@ public class AzureBlobStorageConnectionOptions { public string ConnectionString { get; set; } + public string Container { get; set; } } } diff --git a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs index b544488b..dcea8f14 100644 --- a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs +++ b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs @@ -2,19 +2,42 @@ using Microsoft.Extensions.DependencyInjection; using Azure.Storage.Blobs; using ManagedCode.Storage.Core.Builders; +using System.Reflection.Emit; +using System.Reflection; namespace ManagedCode.Storage.Azure.Extensions { public static class ProviderExtensions { - public static ProviderBuilder AddAzureBlobStorage(this ProviderBuilder providerBuilder, Action action) + public static ProviderBuilder AddAzureBlobStorage( + this ProviderBuilder providerBuilder, + Action action) + where TAzureStorage : IAzureBlobStorage { var connectionOptions = new AzureBlobStorageConnectionOptions(); action.Invoke(connectionOptions); - var blobServiceClient = new BlobServiceClient(connectionOptions.ConnectionString); - providerBuilder.ServiceCollection.AddSingleton(blobServiceClient); + var typeSignature = typeof(TAzureStorage).Name; + var an = new AssemblyName(typeSignature); + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); + TypeBuilder tb = moduleBuilder.DefineType(typeSignature, + TypeAttributes.Public | + TypeAttributes.Class | + TypeAttributes.AutoClass | + TypeAttributes.AnsiClass | + TypeAttributes.BeforeFieldInit | + TypeAttributes.AutoLayout, + null); + tb.SetParent(typeof(AzureBlobStorage)); + tb.AddInterfaceImplementation(typeof(TAzureStorage)); + + ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); + Type implType = tb.CreateType(); + + providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), implType); + return providerBuilder; } } diff --git a/ManagedCode.Storage.Azure/IAzureBlobStorage.cs b/ManagedCode.Storage.Azure/IAzureBlobStorage.cs new file mode 100644 index 00000000..8dc143d8 --- /dev/null +++ b/ManagedCode.Storage.Azure/IAzureBlobStorage.cs @@ -0,0 +1,8 @@ +using ManagedCode.Storage.Core; + +namespace ManagedCode.Storage.Azure +{ + public interface IAzureBlobStorage : IBlobStorage + { + } +} diff --git a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj index 90f17e82..51d826e7 100644 --- a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj +++ b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj @@ -30,4 +30,8 @@ + + + + diff --git a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs index 1cd216bd..285f5702 100644 --- a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs +++ b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs @@ -1,8 +1,44 @@ - +using ManagedCode.Storage.Core.Extensions; +using ManagedCode.Storage.Azure.Extensions; +using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; + namespace ManagedCode.Storage.Tests.Azure { public class DependencyInjectionTests { + private IPhotoStorage _photoStorage; + private IDocumentStorage _documentStorage; + + public DependencyInjectionTests() + { + + } + + [Fact] + public void WhenDIInitialized() + { + var services = new ServiceCollection(); + + services.AddManagedCodeStorage() + .AddAzureBlobStorage(opt => { + opt.ConnectionString = ""; + opt.Container = "photos"; + }) + .AddAzureBlobStorage(opt => { + opt.ConnectionString = ""; + opt.Container = "documents"; + }); + + var provider = services.BuildServiceProvider(); + + _photoStorage = provider.GetService(); + _documentStorage = provider.GetService(); + _photoStorage.Should().NotBeNull(); + _documentStorage.Should().NotBeNull(); + } } } diff --git a/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs b/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs new file mode 100644 index 00000000..38a92074 --- /dev/null +++ b/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs @@ -0,0 +1,8 @@ +using ManagedCode.Storage.Azure; + +namespace ManagedCode.Storage.Tests.Azure +{ + public interface IDocumentStorage : IAzureBlobStorage + { + } +} diff --git a/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs b/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs new file mode 100644 index 00000000..876696f6 --- /dev/null +++ b/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs @@ -0,0 +1,8 @@ +using ManagedCode.Storage.Azure; + +namespace ManagedCode.Storage.Tests.Azure +{ + public interface IPhotoStorage : IAzureBlobStorage + { + } +} diff --git a/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj b/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj index 89712318..35846b37 100644 --- a/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj +++ b/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj @@ -9,6 +9,7 @@ + From f906c67b253de35362f25419cd6971022ba4599b Mon Sep 17 00:00:00 2001 From: dudchenko610 Date: Wed, 19 Jan 2022 12:55:30 +0200 Subject: [PATCH 3/5] finished first working vesrion of library binding --- ManagedCode.Storage.Azure/AzureBlobStorage.cs | 4 +- .../Extensions/ProviderExtensions.cs | 30 +++--------- .../IAzureBlobStorage.cs | 8 --- .../Helpers/TypeHelpers.cs | 49 +++++++++++++++++++ .../ManagedCode.Storage.Core.csproj | 4 -- .../Azure/IDocumentStorage.cs | 3 +- .../Azure/IPhotoStorage.cs | 3 +- 7 files changed, 62 insertions(+), 39 deletions(-) delete mode 100644 ManagedCode.Storage.Azure/IAzureBlobStorage.cs create mode 100644 ManagedCode.Storage.Core/Helpers/TypeHelpers.cs diff --git a/ManagedCode.Storage.Azure/AzureBlobStorage.cs b/ManagedCode.Storage.Azure/AzureBlobStorage.cs index 67df6012..590e0891 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorage.cs +++ b/ManagedCode.Storage.Azure/AzureBlobStorage.cs @@ -8,9 +8,9 @@ namespace ManagedCode.Storage.Azure { - public class AzureBlobStorage : IAzureBlobStorage + public class AzureBlobStorage : IBlobStorage { - public AzureBlobStorage(/*AzureBlobStorageConnectionOptions connectionOptions*/) + public AzureBlobStorage(AzureBlobStorageConnectionOptions connectionOptions) { // var blobServiceClient = new BlobServiceClient(connectionOptions.ConnectionString); } diff --git a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs index dcea8f14..1fb17428 100644 --- a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs +++ b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs @@ -1,9 +1,8 @@ using System; using Microsoft.Extensions.DependencyInjection; -using Azure.Storage.Blobs; using ManagedCode.Storage.Core.Builders; -using System.Reflection.Emit; -using System.Reflection; +using ManagedCode.Storage.Core.Helpers; +using ManagedCode.Storage.Core; namespace ManagedCode.Storage.Azure.Extensions { @@ -12,32 +11,17 @@ public static class ProviderExtensions public static ProviderBuilder AddAzureBlobStorage( this ProviderBuilder providerBuilder, Action action) - where TAzureStorage : IAzureBlobStorage + where TAzureStorage : IBlobStorage { var connectionOptions = new AzureBlobStorageConnectionOptions(); action.Invoke(connectionOptions); - var typeSignature = typeof(TAzureStorage).Name; - var an = new AssemblyName(typeSignature); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); - TypeBuilder tb = moduleBuilder.DefineType(typeSignature, - TypeAttributes.Public | - TypeAttributes.Class | - TypeAttributes.AutoClass | - TypeAttributes.AnsiClass | - TypeAttributes.BeforeFieldInit | - TypeAttributes.AutoLayout, - null); + var implementationType = TypeHelpers.GetImplementationType(); + providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => Activator.CreateInstance(implementationType, connectionOptions)); - tb.SetParent(typeof(AzureBlobStorage)); - tb.AddInterfaceImplementation(typeof(TAzureStorage)); + // Because of AzureBlobStorage does not inherits TAzureStorage, DI complains on unability of casting + // providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => new AzureBlobStorage(connectionOptions)); - ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); - Type implType = tb.CreateType(); - - providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), implType); - return providerBuilder; } } diff --git a/ManagedCode.Storage.Azure/IAzureBlobStorage.cs b/ManagedCode.Storage.Azure/IAzureBlobStorage.cs deleted file mode 100644 index 8dc143d8..00000000 --- a/ManagedCode.Storage.Azure/IAzureBlobStorage.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ManagedCode.Storage.Core; - -namespace ManagedCode.Storage.Azure -{ - public interface IAzureBlobStorage : IBlobStorage - { - } -} diff --git a/ManagedCode.Storage.Core/Helpers/TypeHelpers.cs b/ManagedCode.Storage.Core/Helpers/TypeHelpers.cs new file mode 100644 index 00000000..f91c8fd6 --- /dev/null +++ b/ManagedCode.Storage.Core/Helpers/TypeHelpers.cs @@ -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() + 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(); + } + } +} diff --git a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj index 51d826e7..90f17e82 100644 --- a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj +++ b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj @@ -30,8 +30,4 @@ - - - - diff --git a/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs b/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs index 38a92074..ae2952ca 100644 --- a/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs +++ b/ManagedCode.Storage.Tests/Azure/IDocumentStorage.cs @@ -1,8 +1,9 @@ using ManagedCode.Storage.Azure; +using ManagedCode.Storage.Core; namespace ManagedCode.Storage.Tests.Azure { - public interface IDocumentStorage : IAzureBlobStorage + public interface IDocumentStorage : IBlobStorage { } } diff --git a/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs b/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs index 876696f6..49e35665 100644 --- a/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs +++ b/ManagedCode.Storage.Tests/Azure/IPhotoStorage.cs @@ -1,8 +1,9 @@ using ManagedCode.Storage.Azure; +using ManagedCode.Storage.Core; namespace ManagedCode.Storage.Tests.Azure { - public interface IPhotoStorage : IAzureBlobStorage + public interface IPhotoStorage : IBlobStorage { } } From 319aaf227408db49c364006b6dd1e0bcf4ae253d Mon Sep 17 00:00:00 2001 From: dudchenko610 Date: Wed, 19 Jan 2022 17:27:20 +0200 Subject: [PATCH 4/5] implemented azure blob operations except list fetching --- ManagedCode.Storage.Azure/AzureBlobStorage.cs | 151 ++++++++++++------ .../EnumeratorCacellationAttribute.cs | 8 + .../Extensions/ProviderExtensions.cs | 1 + .../AzureBlobStorageConnectionOptions.cs | 2 +- ManagedCode.Storage.Core/IBlobStorage.cs | 8 +- ManagedCode.Storage.Core/Models/Blob.cs | 7 +- .../Azure/DependencyInjectionTests.cs | 22 ++- 7 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 ManagedCode.Storage.Azure/EnumeratorCacellationAttribute.cs rename ManagedCode.Storage.Azure/{ => Options}/AzureBlobStorageConnectionOptions.cs (77%) diff --git a/ManagedCode.Storage.Azure/AzureBlobStorage.cs b/ManagedCode.Storage.Azure/AzureBlobStorage.cs index 590e0891..9ae1ffc9 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorage.cs +++ b/ManagedCode.Storage.Azure/AzureBlobStorage.cs @@ -1,8 +1,11 @@ -using ManagedCode.Storage.Core; +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; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -10,92 +13,134 @@ namespace ManagedCode.Storage.Azure { public class AzureBlobStorage : IBlobStorage { + private readonly BlobContainerClient _blobContainerClient; + public AzureBlobStorage(AzureBlobStorageConnectionOptions connectionOptions) { - // var blobServiceClient = new BlobServiceClient(connectionOptions.ConnectionString); - } + _blobContainerClient = new BlobContainerClient( + connectionOptions.ConnectionString, + connectionOptions.Container + ); - public Task DeleteAsync(string blob, CancellationToken cancellationToken = default) - { - throw new System.NotImplementedException(); + _blobContainerClient.CreateIfNotExists(PublicAccessType.BlobContainer); } - public Task DeleteAsync(Blob blob, CancellationToken cancellationToken = default) + public void Dispose() { - throw new System.NotImplementedException(); } - public Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) - { - throw new System.NotImplementedException(); - } + #region Delete - public Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + public async Task DeleteAsync(string blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken); } - public void Dispose() + public async Task DeleteAsync(Blob blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob.Name); + await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken); } - public Task DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default) + public async Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + foreach (var blobName in blobs) + { + await DeleteAsync(blobName, cancellationToken); + } } - public Task DownloadAsStreamAsync(Blob blob, CancellationToken cancellationToken = default) + public async Task DeleteAsync(IEnumerable blobs, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + foreach (var blob in blobs) + { + await DeleteAsync(blob, cancellationToken); + } } - public Task DownloadAsync(string blob, CancellationToken cancellationToken = default) + #endregion + + public async Task DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + var res = await blobClient.DownloadStreamingAsync(); + + return res.Value.Content; } - public Task DownloadAsync(Blob blob, CancellationToken cancellationToken = default) + public async Task DownloadAsStreamAsync(Blob blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + return await DownloadAsStreamAsync(blob.Name, cancellationToken); } - public Task ExistsAsync(string blob, CancellationToken cancellationToken = default) + public async Task DownloadAsync(string blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + var localFile = new LocalFile(); + + await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken); + + return localFile; } - public Task ExistsAsync(Blob blob, CancellationToken cancellationToken = default) + public async Task DownloadAsync(Blob blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + return await DownloadAsync(blob.Name, cancellationToken); } - public IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + #region Exists + + public async Task ExistsAsync(string blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + + return await blobClient.ExistsAsync(cancellationToken); } - public IAsyncEnumerable ExistsAsync(IEnumerable blobs, CancellationToken cancellationToken = default) + public async Task ExistsAsync(Blob blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob.Name); + + return await blobClient.ExistsAsync(cancellationToken); } - public IAsyncEnumerable GetBlob(string blob, CancellationToken cancellationToken = default) + public async IAsyncEnumerable ExistsAsync(IEnumerable blobs, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + foreach(var blob in blobs) + { + var blobClient = _blobContainerClient.GetBlobClient(blob); + yield return await blobClient.ExistsAsync(cancellationToken); + } } - public IAsyncEnumerable GetBlob(Blob blob, CancellationToken cancellationToken = default) + public async IAsyncEnumerable ExistsAsync(IEnumerable blobs, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + foreach (var blob in blobs) + { + var blobClient = _blobContainerClient.GetBlobClient(blob.Name); + yield return await blobClient.ExistsAsync(cancellationToken); + } } - public IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default) + #endregion + + public async Task GetBlobAsync(string blob, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + await Task.Yield(); + + var blobClient = _blobContainerClient.GetBlobClient(blob); + + return new Blob() + { + Name = blobClient.Name, + Uri = blobClient.Uri + }; } - public IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default) + public IAsyncEnumerable GetBlobsAsync(IEnumerable blobs, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } @@ -105,24 +150,34 @@ public IAsyncEnumerable GetBlobListAsync(CancellationToken cancellationTok throw new System.NotImplementedException(); } - public Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) + #region Upload + + public async Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + await blobClient.UploadAsync(dataStream, cancellationToken); } - public Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) + public async Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + var blobClient = _blobContainerClient.GetBlobClient(blob); + + using (var fs = new FileStream(pathToFile, FileMode.Open, FileAccess.Read)) + { + await blobClient.UploadAsync(fs, cancellationToken); + } } - public Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) + public async Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + await UploadAsync(blob.Name, dataStream, append, cancellationToken); } - public Task UploadAsync(Blob blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) + public async Task UploadAsync(Blob blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + await UploadAsync(blob.Name, pathToFile, append, cancellationToken); } + + #endregion } } diff --git a/ManagedCode.Storage.Azure/EnumeratorCacellationAttribute.cs b/ManagedCode.Storage.Azure/EnumeratorCacellationAttribute.cs new file mode 100644 index 00000000..350ce132 --- /dev/null +++ b/ManagedCode.Storage.Azure/EnumeratorCacellationAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace ManagedCode.Storage.Azure +{ + internal class EnumeratorCacellationAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs index 1fb17428..1e99dd3b 100644 --- a/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs +++ b/ManagedCode.Storage.Azure/Extensions/ProviderExtensions.cs @@ -3,6 +3,7 @@ using ManagedCode.Storage.Core.Builders; using ManagedCode.Storage.Core.Helpers; using ManagedCode.Storage.Core; +using ManagedCode.Storage.Azure.Options; namespace ManagedCode.Storage.Azure.Extensions { diff --git a/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs b/ManagedCode.Storage.Azure/Options/AzureBlobStorageConnectionOptions.cs similarity index 77% rename from ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs rename to ManagedCode.Storage.Azure/Options/AzureBlobStorageConnectionOptions.cs index 87f40c51..fe646d7c 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorageConnectionOptions.cs +++ b/ManagedCode.Storage.Azure/Options/AzureBlobStorageConnectionOptions.cs @@ -1,4 +1,4 @@ -namespace ManagedCode.Storage.Azure +namespace ManagedCode.Storage.Azure.Options { public class AzureBlobStorageConnectionOptions { diff --git a/ManagedCode.Storage.Core/IBlobStorage.cs b/ManagedCode.Storage.Core/IBlobStorage.cs index 7a6519dc..fbe69e8e 100644 --- a/ManagedCode.Storage.Core/IBlobStorage.cs +++ b/ManagedCode.Storage.Core/IBlobStorage.cs @@ -10,11 +10,9 @@ namespace ManagedCode.Storage.Core public interface IBlobStorage : IDisposable { IAsyncEnumerable GetBlobListAsync(CancellationToken cancellationToken = default); - IAsyncEnumerable GetBlob(string blob, CancellationToken cancellationToken = default); - IAsyncEnumerable GetBlob(Blob blob, CancellationToken cancellationToken = default); - IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default); - IAsyncEnumerable GetBlob(IEnumerable blobs, CancellationToken cancellationToken = default); - + IAsyncEnumerable GetBlobsAsync(IEnumerable blobs, CancellationToken cancellationToken = default); + Task 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); diff --git a/ManagedCode.Storage.Core/Models/Blob.cs b/ManagedCode.Storage.Core/Models/Blob.cs index e3b2bd36..f4798036 100644 --- a/ManagedCode.Storage.Core/Models/Blob.cs +++ b/ManagedCode.Storage.Core/Models/Blob.cs @@ -1,7 +1,10 @@ -namespace ManagedCode.Storage.Core.Models +using System; + +namespace ManagedCode.Storage.Core.Models { public class Blob { - public string Path { get; set; } + public string Name { get; set; } + public Uri Uri { get; set; } } } diff --git a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs index 285f5702..90efe111 100644 --- a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs +++ b/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs @@ -13,22 +13,16 @@ public class DependencyInjectionTests private IDocumentStorage _documentStorage; public DependencyInjectionTests() - { - - } - - [Fact] - public void WhenDIInitialized() { var services = new ServiceCollection(); services.AddManagedCodeStorage() .AddAzureBlobStorage(opt => { - opt.ConnectionString = ""; + opt.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=storagestudying;AccountKey=4Y4IBrITEoWYMGe0gNju9wvUQrWi//1VvPIDN2dYWccWKy9uuKWnMBXxQlmcy3Q9UIU70ZJiy8ULD9QITxyeTQ==;EndpointSuffix=core.windows.net"; opt.Container = "photos"; }) .AddAzureBlobStorage(opt => { - opt.ConnectionString = ""; + opt.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=storagestudying;AccountKey=4Y4IBrITEoWYMGe0gNju9wvUQrWi//1VvPIDN2dYWccWKy9uuKWnMBXxQlmcy3Q9UIU70ZJiy8ULD9QITxyeTQ==;EndpointSuffix=core.windows.net"; opt.Container = "documents"; }); @@ -36,9 +30,21 @@ public void WhenDIInitialized() _photoStorage = provider.GetService(); _documentStorage = provider.GetService(); + } + [Fact] + public void WhenDIInitialized() + { _photoStorage.Should().NotBeNull(); _documentStorage.Should().NotBeNull(); } + + [Fact] + public async Task WhenSingleBlobExistsIsCalled() + { + var result = await _photoStorage.ExistsAsync("34.png"); + + result.Should().BeTrue(); + } } } From 7fac388b846e8f7cf6471e05226fc27c118bf119 Mon Sep 17 00:00:00 2001 From: dudchenko610 Date: Wed, 19 Jan 2022 17:54:53 +0200 Subject: [PATCH 5/5] tested azure blob storage functionality except list fetching --- ManagedCode.Storage.Azure/AzureBlobStorage.cs | 8 ++++ .../Builders/AzureProviderBuilder.cs | 12 +++++ ...InjectionTests.cs => AzureStorageTests.cs} | 45 ++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 ManagedCode.Storage.Azure/Builders/AzureProviderBuilder.cs rename ManagedCode.Storage.Tests/Azure/{DependencyInjectionTests.cs => AzureStorageTests.cs} (58%) diff --git a/ManagedCode.Storage.Azure/AzureBlobStorage.cs b/ManagedCode.Storage.Azure/AzureBlobStorage.cs index 9ae1ffc9..633fa5b1 100644 --- a/ManagedCode.Storage.Azure/AzureBlobStorage.cs +++ b/ManagedCode.Storage.Azure/AzureBlobStorage.cs @@ -61,6 +61,8 @@ public async Task DeleteAsync(IEnumerable blobs, CancellationToken cancell #endregion + #region Download + public async Task DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default) { var blobClient = _blobContainerClient.GetBlobClient(blob); @@ -89,6 +91,8 @@ public async Task DownloadAsync(Blob blob, CancellationToken cancella return await DownloadAsync(blob.Name, cancellationToken); } + #endregion + #region Exists public async Task ExistsAsync(string blob, CancellationToken cancellationToken = default) @@ -127,6 +131,8 @@ public async IAsyncEnumerable ExistsAsync(IEnumerable blobs, #endregion + #region Get + public async Task GetBlobAsync(string blob, CancellationToken cancellationToken = default) { await Task.Yield(); @@ -150,6 +156,8 @@ public IAsyncEnumerable GetBlobListAsync(CancellationToken cancellationTok throw new System.NotImplementedException(); } + #endregion + #region Upload public async Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default) diff --git a/ManagedCode.Storage.Azure/Builders/AzureProviderBuilder.cs b/ManagedCode.Storage.Azure/Builders/AzureProviderBuilder.cs new file mode 100644 index 00000000..a4db7567 --- /dev/null +++ b/ManagedCode.Storage.Azure/Builders/AzureProviderBuilder.cs @@ -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) {} + + + } +} diff --git a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs b/ManagedCode.Storage.Tests/Azure/AzureStorageTests.cs similarity index 58% rename from ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs rename to ManagedCode.Storage.Tests/Azure/AzureStorageTests.cs index 90efe111..1cf8947d 100644 --- a/ManagedCode.Storage.Tests/Azure/DependencyInjectionTests.cs +++ b/ManagedCode.Storage.Tests/Azure/AzureStorageTests.cs @@ -4,15 +4,17 @@ using System.Threading.Tasks; using Xunit; using FluentAssertions; +using System.IO; +using System.Text; namespace ManagedCode.Storage.Tests.Azure { - public class DependencyInjectionTests + public class AzureStorageTests { private IPhotoStorage _photoStorage; private IDocumentStorage _documentStorage; - public DependencyInjectionTests() + public AzureStorageTests() { var services = new ServiceCollection(); @@ -46,5 +48,44 @@ public async Task WhenSingleBlobExistsIsCalled() result.Should().BeTrue(); } + + [Fact] + public async Task WhenDownloadAsyncIsCalled() + { + var stream = await _documentStorage.DownloadAsStreamAsync("a.txt"); + using var sr = new StreamReader(stream, Encoding.UTF8); + + string content = sr.ReadToEnd(); + + content.Should().NotBeNull(); + } + + [Fact] + public async Task WhenDownloadAsyncToFileIsCalled() + { + var tempFile = await _documentStorage.DownloadAsync("a.txt"); + using var sr = new StreamReader(tempFile.FileStream, Encoding.UTF8); + + string content = sr.ReadToEnd(); + + content.Should().NotBeNull(); + } + + [Fact] + public async Task WhenUploadAsyncIsCalled() + { + var lineToUpload = "some crazy text"; + + var byteArray = Encoding.ASCII.GetBytes(lineToUpload); + var stream = new MemoryStream(byteArray); + + await _documentStorage.UploadAsync("b.txt", stream); + } + + [Fact] + public async Task WhenDeleteAsyncIsCalled() + { + await _documentStorage.DeleteAsync("a.txt"); + } } }