Skip to content

Commit 0ef0703

Browse files
committed
StorageFactory
1 parent 261a57f commit 0ef0703

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+344
-91
lines changed

Integraions/ManagedCode.Storage.Client/StorageClient.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,10 @@
1111

1212
namespace ManagedCode.Storage.Client;
1313

14-
public class StorageClient : IStorageClient
14+
public class StorageClient(HttpClient httpClient) : IStorageClient
1515
{
16-
private readonly HttpClient _httpClient;
1716
private long _chunkSize;
1817

19-
public StorageClient(HttpClient httpClient)
20-
{
21-
_httpClient = httpClient;
22-
}
23-
2418
public long ChunkSize
2519
{
2620
get
@@ -47,7 +41,7 @@ public async Task<Result<BlobMetadata>> UploadFile(Stream stream, string apiUrl,
4741
using var formData = new MultipartFormDataContent();
4842
formData.Add(streamContent, contentName, contentName);
4943

50-
var response = await _httpClient.PostAsync(apiUrl, formData, cancellationToken);
44+
var response = await httpClient.PostAsync(apiUrl, formData, cancellationToken);
5145

5246
if (response.IsSuccessStatusCode)
5347
return await response.Content.ReadFromJsonAsync<Result<BlobMetadata>>(cancellationToken: cancellationToken);
@@ -65,7 +59,7 @@ public async Task<Result<BlobMetadata>> UploadFile(FileInfo fileInfo, string api
6559
{
6660
formData.Add(streamContent, contentName, contentName);
6761

68-
var response = await _httpClient.PostAsync(apiUrl, formData, cancellationToken);
62+
var response = await httpClient.PostAsync(apiUrl, formData, cancellationToken);
6963

7064
if (response.IsSuccessStatusCode)
7165
{
@@ -89,7 +83,7 @@ public async Task<Result<BlobMetadata>> UploadFile(byte[] bytes, string apiUrl,
8983
{
9084
formData.Add(streamContent, contentName, contentName);
9185

92-
var response = await _httpClient.PostAsync(apiUrl, formData, cancellationToken);
86+
var response = await httpClient.PostAsync(apiUrl, formData, cancellationToken);
9387

9488
if (response.IsSuccessStatusCode)
9589
{
@@ -112,7 +106,7 @@ public async Task<Result<BlobMetadata>> UploadFile(string base64, string apiUrl,
112106

113107
formData.Add(fileContent, contentName, contentName);
114108

115-
var response = await _httpClient.PostAsync(apiUrl, formData, cancellationToken);
109+
var response = await httpClient.PostAsync(apiUrl, formData, cancellationToken);
116110

117111
if (response.IsSuccessStatusCode)
118112
return await response.Content.ReadFromJsonAsync<Result<BlobMetadata>>(cancellationToken: cancellationToken);
@@ -125,7 +119,7 @@ public async Task<Result<LocalFile>> DownloadFile(string fileName, string apiUrl
125119
{
126120
try
127121
{
128-
using var response = await _httpClient.GetStreamAsync($"{apiUrl}/{fileName}", cancellationToken);
122+
using var response = await httpClient.GetStreamAsync($"{apiUrl}/{fileName}", cancellationToken);
129123
var localFile = path is null
130124
? await LocalFile.FromStreamAsync(response, fileName)
131125
: await LocalFile.FromStreamAsync(response, path, fileName);
@@ -165,7 +159,7 @@ public async Task<Result<uint>> UploadLargeFile(Stream file, string uploadApiUrl
165159
formData.Add(content, "File", fileName);
166160
formData.Add(new StringContent(chunkIndex.ToString()), "Payload.ChunkIndex");
167161
formData.Add(new StringContent(bufferSize.ToString()), "Payload.ChunkSize");
168-
await _httpClient.PostAsync(uploadApiUrl, formData, cancellationToken);
162+
await httpClient.PostAsync(uploadApiUrl, formData, cancellationToken);
169163
}
170164
}
171165

@@ -180,7 +174,7 @@ public async Task<Result<uint>> UploadLargeFile(Stream file, string uploadApiUrl
180174

181175
await Task.WhenAll(tasks.ToArray());
182176

183-
var mergeResult = await _httpClient.PostAsync(completeApiUrl, JsonContent.Create(fileName), cancellationToken);
177+
var mergeResult = await httpClient.PostAsync(completeApiUrl, JsonContent.Create(fileName), cancellationToken);
184178

185179
return await mergeResult.Content.ReadFromJsonAsync<Result<uint>>(cancellationToken: cancellationToken);
186180
}
@@ -189,7 +183,7 @@ public async Task<Result<Stream>> GetFileStream(string fileName, string apiUrl,
189183
{
190184
try
191185
{
192-
var response = await _httpClient.GetAsync($"{apiUrl}/{fileName}");
186+
var response = await httpClient.GetAsync($"{apiUrl}/{fileName}");
193187
if (response.IsSuccessStatusCode)
194188
{
195189
var stream = await response.Content.ReadAsStreamAsync();

ManagedCode.Storage.Core/BaseStorage.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,10 @@ protected abstract Task<Result> SetLegalHoldInternalAsync(bool hasLegalHold, Leg
301301
CancellationToken cancellationToken = default);
302302

303303
protected abstract Task<Result<bool>> HasLegalHoldInternalAsync(LegalHoldOptions options, CancellationToken cancellationToken = default);
304+
305+
public void Dispose()
306+
{
307+
if(StorageClient is IDisposable disposable)
308+
disposable.Dispose();
309+
}
304310
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.DependencyInjection.Extensions;
3+
using Storage;
4+
5+
namespace ManagedCode.Storage.Core.Extensions;
6+
7+
public static class ServiceCollectionExtensions
8+
{
9+
public static IServiceCollection AddStorageFactory(this IServiceCollection serviceCollection)
10+
{
11+
serviceCollection.TryAddSingleton<IStorageFactory, StorageFactory>();
12+
return serviceCollection;
13+
}
14+
15+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
3+
using System;
4+
using System.Reflection;
5+
6+
namespace ManagedCode.Storage.Core.Extensions;
7+
8+
public static class StorageOptionsExtensions
9+
{
10+
public static T DeepCopy<T>(this T? source) where T : class, IStorageOptions
11+
{
12+
if (source == null)
13+
return default;
14+
15+
// Create new instance of the same type
16+
var instance = Activator.CreateInstance<T>();
17+
18+
// Get all properties of the type
19+
var properties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
20+
21+
foreach (var property in properties)
22+
{
23+
if (property.CanWrite && property.CanRead)
24+
{
25+
var value = property.GetValue(source);
26+
if (value != null)
27+
{
28+
// Handle value types and strings
29+
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
30+
{
31+
property.SetValue(instance, value);
32+
}
33+
// Handle reference types by recursive deep copy
34+
else
35+
{
36+
var deepCopyMethod = typeof(StorageOptionsExtensions)
37+
.GetMethod(nameof(DeepCopy))
38+
?.MakeGenericMethod(property.PropertyType);
39+
40+
if (deepCopyMethod != null)
41+
{
42+
var copiedValue = deepCopyMethod.Invoke(null, [value]);
43+
property.SetValue(instance, copiedValue);
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
return instance;
51+
}
52+
}

ManagedCode.Storage.Core/IStorage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface IStorage<out T, TOptions> : IStorage where TOptions : IStorageO
3737
/// <summary>
3838
/// Represents a storage interface that includes uploader, downloader, streamer, and storage operations.
3939
/// </summary>
40-
public interface IStorage : IUploader, IDownloader, IStreamer, IStorageOperations
40+
public interface IStorage : IUploader, IDownloader, IStreamer, IStorageOperations, IDisposable
4141
{
4242
/// <summary>
4343
/// Creates a container asynchronously if it does not already exist.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using ManagedCode.Storage.Core;
3+
4+
namespace Storage
5+
{
6+
public interface IStorageFactory
7+
{
8+
IStorage CreateStorage(IStorageOptions options);
9+
IStorage CreateStorage(Action<IStorageOptions> options);
10+
11+
TStorage CreateStorage<TStorage,TOptions>(TOptions options)
12+
where TStorage : class, IStorage
13+
where TOptions : class, IStorageOptions;
14+
15+
TStorage CreateStorage<TStorage,TOptions>(Action<TOptions> options)
16+
where TStorage : class, IStorage
17+
where TOptions : class, IStorageOptions;
18+
}
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using ManagedCode.Storage.Core;
3+
4+
namespace Storage
5+
{
6+
public interface IStorageProvider
7+
{
8+
Type StorageOptionsType { get; }
9+
TStorage CreateStorage<TStorage, TOptions>(TOptions options)
10+
where TStorage : class, IStorage
11+
where TOptions : class, IStorageOptions;
12+
13+
14+
IStorageOptions GetDefaultOptions();
15+
}
16+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using ManagedCode.Storage.Core;
5+
6+
namespace Storage
7+
{
8+
public class StorageFactory : IStorageFactory
9+
{
10+
public StorageFactory(IEnumerable<IStorageProvider> providers)
11+
{
12+
Providers = providers.ToDictionary(p => p.StorageOptionsType);
13+
}
14+
15+
public Dictionary<Type, IStorageProvider> Providers { get; set; }
16+
17+
public IStorage CreateStorage(IStorageOptions options)
18+
{
19+
if (Providers.TryGetValue(options.GetType(), out var provider))
20+
{
21+
return provider.CreateStorage<IStorage, IStorageOptions>(options);
22+
}
23+
24+
throw new NotSupportedException($"Provider for {options.GetType()} not found");
25+
}
26+
27+
public IStorage CreateStorage(Action<IStorageOptions> options)
28+
{
29+
if (Providers.TryGetValue(options.GetType(), out var provider))
30+
{
31+
var storageOptions = provider.GetDefaultOptions();
32+
options.Invoke(storageOptions);
33+
return CreateStorage(storageOptions);
34+
}
35+
36+
throw new NotSupportedException($"Provider for {options.GetType()} not found");
37+
}
38+
39+
public TStorage CreateStorage<TStorage, TOptions>(TOptions options)
40+
where TStorage : class, IStorage
41+
where TOptions : class, IStorageOptions
42+
{
43+
if (Providers.TryGetValue(typeof(TOptions), out var provider))
44+
{
45+
return provider.CreateStorage<TStorage, TOptions>(options);
46+
}
47+
48+
throw new NotSupportedException($"Provider for {typeof(TOptions)} not found");
49+
}
50+
51+
public TStorage CreateStorage<TStorage, TOptions>(Action<TOptions> options)
52+
where TStorage : class, IStorage
53+
where TOptions : class, IStorageOptions
54+
{
55+
if (Providers.TryGetValue(typeof(TOptions), out var provider))
56+
{
57+
TOptions storageOptions = (TOptions)provider.GetDefaultOptions();
58+
options.Invoke(storageOptions);
59+
return provider.CreateStorage<TStorage, TOptions>(storageOptions);
60+
}
61+
62+
throw new NotSupportedException($"Provider for {typeof(TOptions)} not found");
63+
64+
}
65+
}
66+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using ManagedCode.Storage.Azure;
4+
using ManagedCode.Storage.Azure.Options;
5+
using ManagedCode.Storage.Core;
6+
using ManagedCode.Storage.Core.Extensions;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Storage.Providers
11+
{
12+
public class AzureStorageProvider(IServiceProvider serviceProvider, AzureStorageOptions defaultOptions) : IStorageProvider
13+
{
14+
public Type StorageOptionsType => typeof(IAzureStorageOptions);
15+
16+
public TStorage CreateStorage<TStorage, TOptions>(TOptions options)
17+
where TStorage : class, IStorage
18+
where TOptions : class, IStorageOptions
19+
{
20+
if (options is not IAzureStorageOptions azureOptions)
21+
{
22+
throw new ArgumentException($"Options must be of type {typeof(IAzureStorageOptions)}", nameof(options));
23+
}
24+
25+
var logger = serviceProvider.GetService<ILogger<AzureStorage>>();
26+
var storage = new AzureStorage(azureOptions, logger);
27+
28+
return storage as TStorage
29+
?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
30+
}
31+
32+
public IStorageOptions GetDefaultOptions()
33+
{
34+
return defaultOptions.DeepCopy();
35+
}
36+
}
37+
}

Storages/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using ManagedCode.Storage.Core;
44
using ManagedCode.Storage.Core.Exceptions;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
using Storage;
8+
using Storage.Providers;
69

710
namespace ManagedCode.Storage.Azure.Extensions;
811

@@ -28,7 +31,7 @@ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollectio
2831
return serviceCollection.AddAzureStorageAsDefault(options);
2932
}
3033

31-
public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, Action<AzureStorageCredentialsOptions> action)
34+
public static IServiceCollection AddAzureStorageWithCredential(this IServiceCollection serviceCollection, Action<AzureStorageCredentialsOptions> action)
3235
{
3336
var options = new AzureStorageCredentialsOptions();
3437
action.Invoke(options);
@@ -53,13 +56,15 @@ public static IServiceCollection AddAzureStorage(this IServiceCollection service
5356
{
5457
CheckConfiguration(options);
5558
serviceCollection.AddSingleton(options);
59+
serviceCollection.TryAddSingleton<IStorageProvider, AzureStorageProvider>();
5660
return serviceCollection.AddScoped<IAzureStorage, AzureStorage>();
5761
}
5862

5963
public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, IAzureStorageOptions options)
6064
{
6165
CheckConfiguration(options);
6266
serviceCollection.AddSingleton(options);
67+
serviceCollection.TryAddSingleton<IStorageProvider, AzureStorageProvider>();
6368
serviceCollection.AddScoped<IAzureStorage, AzureStorage>();
6469
return serviceCollection.AddScoped<IStorage, AzureStorage>();
6570
}

0 commit comments

Comments
 (0)