Skip to content

Commit cf70787

Browse files
authored
Merge pull request #1 from dudchenko610/azure-blobs
Azure blobs
2 parents 4f1e803 + 7fac388 commit cf70787

File tree

16 files changed

+451
-12
lines changed

16 files changed

+451
-12
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using Azure.Storage.Blobs;
2+
using Azure.Storage.Blobs.Models;
3+
using ManagedCode.Storage.Azure.Options;
4+
using ManagedCode.Storage.Core;
5+
using ManagedCode.Storage.Core.Models;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Runtime.CompilerServices;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
namespace ManagedCode.Storage.Azure
13+
{
14+
public class AzureBlobStorage : IBlobStorage
15+
{
16+
private readonly BlobContainerClient _blobContainerClient;
17+
18+
public AzureBlobStorage(AzureBlobStorageConnectionOptions connectionOptions)
19+
{
20+
_blobContainerClient = new BlobContainerClient(
21+
connectionOptions.ConnectionString,
22+
connectionOptions.Container
23+
);
24+
25+
_blobContainerClient.CreateIfNotExists(PublicAccessType.BlobContainer);
26+
}
27+
28+
public void Dispose()
29+
{
30+
}
31+
32+
#region Delete
33+
34+
public async Task DeleteAsync(string blob, CancellationToken cancellationToken = default)
35+
{
36+
var blobClient = _blobContainerClient.GetBlobClient(blob);
37+
await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken);
38+
}
39+
40+
public async Task DeleteAsync(Blob blob, CancellationToken cancellationToken = default)
41+
{
42+
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);
43+
await blobClient.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken);
44+
}
45+
46+
public async Task DeleteAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default)
47+
{
48+
foreach (var blobName in blobs)
49+
{
50+
await DeleteAsync(blobName, cancellationToken);
51+
}
52+
}
53+
54+
public async Task DeleteAsync(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default)
55+
{
56+
foreach (var blob in blobs)
57+
{
58+
await DeleteAsync(blob, cancellationToken);
59+
}
60+
}
61+
62+
#endregion
63+
64+
#region Download
65+
66+
public async Task<Stream> DownloadAsStreamAsync(string blob, CancellationToken cancellationToken = default)
67+
{
68+
var blobClient = _blobContainerClient.GetBlobClient(blob);
69+
var res = await blobClient.DownloadStreamingAsync();
70+
71+
return res.Value.Content;
72+
}
73+
74+
public async Task<Stream> DownloadAsStreamAsync(Blob blob, CancellationToken cancellationToken = default)
75+
{
76+
return await DownloadAsStreamAsync(blob.Name, cancellationToken);
77+
}
78+
79+
public async Task<LocalFile> DownloadAsync(string blob, CancellationToken cancellationToken = default)
80+
{
81+
var blobClient = _blobContainerClient.GetBlobClient(blob);
82+
var localFile = new LocalFile();
83+
84+
await blobClient.DownloadToAsync(localFile.FileStream, cancellationToken);
85+
86+
return localFile;
87+
}
88+
89+
public async Task<LocalFile> DownloadAsync(Blob blob, CancellationToken cancellationToken = default)
90+
{
91+
return await DownloadAsync(blob.Name, cancellationToken);
92+
}
93+
94+
#endregion
95+
96+
#region Exists
97+
98+
public async Task<bool> ExistsAsync(string blob, CancellationToken cancellationToken = default)
99+
{
100+
var blobClient = _blobContainerClient.GetBlobClient(blob);
101+
102+
return await blobClient.ExistsAsync(cancellationToken);
103+
}
104+
105+
public async Task<bool> ExistsAsync(Blob blob, CancellationToken cancellationToken = default)
106+
{
107+
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);
108+
109+
return await blobClient.ExistsAsync(cancellationToken);
110+
}
111+
112+
public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<string> blobs,
113+
[EnumeratorCancellation] CancellationToken cancellationToken = default)
114+
{
115+
foreach(var blob in blobs)
116+
{
117+
var blobClient = _blobContainerClient.GetBlobClient(blob);
118+
yield return await blobClient.ExistsAsync(cancellationToken);
119+
}
120+
}
121+
122+
public async IAsyncEnumerable<bool> ExistsAsync(IEnumerable<Blob> blobs,
123+
[EnumeratorCancellation] CancellationToken cancellationToken = default)
124+
{
125+
foreach (var blob in blobs)
126+
{
127+
var blobClient = _blobContainerClient.GetBlobClient(blob.Name);
128+
yield return await blobClient.ExistsAsync(cancellationToken);
129+
}
130+
}
131+
132+
#endregion
133+
134+
#region Get
135+
136+
public async Task<Blob> GetBlobAsync(string blob, CancellationToken cancellationToken = default)
137+
{
138+
await Task.Yield();
139+
140+
var blobClient = _blobContainerClient.GetBlobClient(blob);
141+
142+
return new Blob()
143+
{
144+
Name = blobClient.Name,
145+
Uri = blobClient.Uri
146+
};
147+
}
148+
149+
public IAsyncEnumerable<Blob> GetBlobsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default)
150+
{
151+
throw new System.NotImplementedException();
152+
}
153+
154+
public IAsyncEnumerable<Blob> GetBlobListAsync(CancellationToken cancellationToken = default)
155+
{
156+
throw new System.NotImplementedException();
157+
}
158+
159+
#endregion
160+
161+
#region Upload
162+
163+
public async Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default)
164+
{
165+
var blobClient = _blobContainerClient.GetBlobClient(blob);
166+
await blobClient.UploadAsync(dataStream, cancellationToken);
167+
}
168+
169+
public async Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default)
170+
{
171+
var blobClient = _blobContainerClient.GetBlobClient(blob);
172+
173+
using (var fs = new FileStream(pathToFile, FileMode.Open, FileAccess.Read))
174+
{
175+
await blobClient.UploadAsync(fs, cancellationToken);
176+
}
177+
}
178+
179+
public async Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default)
180+
{
181+
await UploadAsync(blob.Name, dataStream, append, cancellationToken);
182+
}
183+
184+
public async Task UploadAsync(Blob blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default)
185+
{
186+
await UploadAsync(blob.Name, pathToFile, append, cancellationToken);
187+
}
188+
189+
#endregion
190+
}
191+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using ManagedCode.Storage.Core.Builders;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace ManagedCode.Storage.Azure.Builders
5+
{
6+
public class AzureProviderBuilder : ProviderBuilder
7+
{
8+
public AzureProviderBuilder(IServiceCollection serviceCollection) : base(serviceCollection) {}
9+
10+
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace ManagedCode.Storage.Azure
4+
{
5+
internal class EnumeratorCacellationAttribute : Attribute
6+
{
7+
}
8+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using ManagedCode.Storage.Core.Builders;
4+
using ManagedCode.Storage.Core.Helpers;
5+
using ManagedCode.Storage.Core;
6+
using ManagedCode.Storage.Azure.Options;
7+
8+
namespace ManagedCode.Storage.Azure.Extensions
9+
{
10+
public static class ProviderExtensions
11+
{
12+
public static ProviderBuilder AddAzureBlobStorage<TAzureStorage>(
13+
this ProviderBuilder providerBuilder,
14+
Action<AzureBlobStorageConnectionOptions> action)
15+
where TAzureStorage : IBlobStorage
16+
{
17+
var connectionOptions = new AzureBlobStorageConnectionOptions();
18+
action.Invoke(connectionOptions);
19+
20+
var implementationType = TypeHelpers.GetImplementationType<TAzureStorage, AzureBlobStorage, AzureBlobStorageConnectionOptions>();
21+
providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => Activator.CreateInstance(implementationType, connectionOptions));
22+
23+
// Because of AzureBlobStorage does not inherits TAzureStorage, DI complains on unability of casting
24+
// providerBuilder.ServiceCollection.AddScoped(typeof(TAzureStorage), x => new AzureBlobStorage(connectionOptions));
25+
26+
return providerBuilder;
27+
}
28+
}
29+
}

ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</ItemGroup>
2727

2828
<ItemGroup>
29+
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
2930
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
3031
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
3132
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ManagedCode.Storage.Azure.Options
2+
{
3+
public class AzureBlobStorageConnectionOptions
4+
{
5+
public string ConnectionString { get; set; }
6+
public string Container { get; set; }
7+
}
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace ManagedCode.Storage.Core.Builders
4+
{
5+
public class ProviderBuilder
6+
{
7+
public IServiceCollection ServiceCollection { get; }
8+
9+
public ProviderBuilder(IServiceCollection serviceCollection)
10+
{
11+
ServiceCollection = serviceCollection;
12+
}
13+
}
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using ManagedCode.Storage.Core.Builders;
3+
4+
namespace ManagedCode.Storage.Core.Extensions
5+
{
6+
public static class ServiceCollectionExtensions
7+
{
8+
public static ProviderBuilder AddManagedCodeStorage(this IServiceCollection serviceCollection)
9+
{
10+
return new ProviderBuilder(serviceCollection);
11+
}
12+
}
13+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Reflection.Emit;
4+
5+
namespace ManagedCode.Storage.Core.Helpers
6+
{
7+
public static class TypeHelpers
8+
{
9+
public static Type GetImplementationType<TAbstraction, TImplementation, TOptions>()
10+
where TAbstraction : IBlobStorage
11+
{
12+
var typeSignature = typeof(TAbstraction).Name;
13+
var an = new AssemblyName(typeSignature);
14+
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
15+
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ManagedCodeCModule");
16+
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
17+
TypeAttributes.Public |
18+
TypeAttributes.Class |
19+
TypeAttributes.AutoClass |
20+
TypeAttributes.AnsiClass |
21+
TypeAttributes.BeforeFieldInit |
22+
TypeAttributes.AutoLayout,
23+
null);
24+
25+
tb.SetParent(typeof(TImplementation));
26+
tb.AddInterfaceImplementation(typeof(TAbstraction));
27+
28+
var newConstructor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
29+
new Type[] { typeof(TOptions) });
30+
31+
var baseConstructors = typeof(TImplementation).GetConstructors(
32+
BindingFlags.Public |
33+
BindingFlags.NonPublic |
34+
BindingFlags.Instance);
35+
36+
var emitter = newConstructor.GetILGenerator();
37+
emitter.Emit(OpCodes.Nop);
38+
39+
// Load `this` and call base constructor with arguments
40+
emitter.Emit(OpCodes.Ldarg_0);
41+
emitter.Emit(OpCodes.Ldarg, 1);
42+
emitter.Emit(OpCodes.Call, baseConstructors[0]);
43+
44+
emitter.Emit(OpCodes.Ret);
45+
46+
return tb.CreateType();
47+
}
48+
}
49+
}

ManagedCode.Storage.Core/IBlobStorage.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
using System.IO;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using ManagedCode.Storage.Core.Models;
67

78
namespace ManagedCode.Storage.Core
89
{
9-
public interface IStorage : IDisposable
10+
public interface IBlobStorage : IDisposable
1011
{
1112
IAsyncEnumerable<Blob> GetBlobListAsync(CancellationToken cancellationToken = default);
12-
IAsyncEnumerable<Blob> GetBlob(string blob, CancellationToken cancellationToken = default);
13-
IAsyncEnumerable<Blob> GetBlob(Blob blob, CancellationToken cancellationToken = default);
14-
IAsyncEnumerable<Blob> GetBlob(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
15-
IAsyncEnumerable<Blob> GetBlob(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default);
16-
13+
IAsyncEnumerable<Blob> GetBlobsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
14+
Task<Blob> GetBlobAsync(string blob, CancellationToken cancellationToken = default);
15+
1716
Task UploadAsync(string blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default);
1817
Task UploadAsync(string blob, string pathToFile, bool append = false, CancellationToken cancellationToken = default);
1918
Task UploadAsync(Blob blob, Stream dataStream, bool append = false, CancellationToken cancellationToken = default);
@@ -33,11 +32,5 @@ public interface IStorage : IDisposable
3332
Task<bool> ExistsAsync(Blob blob, CancellationToken cancellationToken = default);
3433
IAsyncEnumerable<bool> ExistsAsync(IEnumerable<string> blobs, CancellationToken cancellationToken = default);
3534
IAsyncEnumerable<bool> ExistsAsync(IEnumerable<Blob> blobs, CancellationToken cancellationToken = default);
36-
37-
}
38-
39-
public class Blob
40-
{
41-
public string Path { get; set; }
4235
}
4336
}

0 commit comments

Comments
 (0)