diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index eac13cf8..d4bd9956 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -14,75 +14,39 @@ jobs:
build-and-test:
runs-on: ubuntu-latest
- services:
- localstack:
- image: localstack/localstack:latest
- ports:
- - 4563-4599:4563-4599
- - 8055:8080
- env:
- SERVICES: s3
- DEBUG: 1
- DATA_DIR: /tmp/localstack/data
- AWS_SECRET_KEY: 'localkey'
- AWS_BUCKET_NAME: 'managed-code-bucket'
- AWS_ACCESS_KEY_ID: 'localkey'
- AWS_SECRET_ACCESS_KEY: 'localsecret'
- DEFAULT_REGION: 'eu-west-1'
steps:
+
+ - name: checkout
+ uses: actions/checkout@v3
- - uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
-
- - name: NDepend
- uses: ndepend/ndepend-action@v1
- with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- license: ${{ secrets.NDEPENDLICENSE }}
-
- - name: azuright
- uses: potatoqualitee/azuright@v1.1
-
- - name: docker run fake-gcs-server
- run: |
- docker run -d --name fake-gcs-server -p 4443:4443 -v ${PWD}/examples/data:/data fsouza/fake-gcs-server -scheme http -external-url "http://localhost:4443"
- sleep 5s
-
- - name: check storage emulators
- run: |
- curl http://localhost:4443/
- curl http://localhost:4566/
- curl http://localhost:10000/
-
- # run build and test
+
- name: Restore dependencies
run: dotnet restore
+
- name: Build
run: dotnet build --no-restore
+
- name: Test
run: dotnet test --no-build --logger 'trx;LogFileName=test-results.trx'
- env:
- DEFAULT_REGION: eu-west-1
- AWS_ACCESS_KEY_ID: localkey
- AWS_SECRET_ACCESS_KEY: localsecret
+
- name: Collect Code Coverage
run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=ManagedCode.Storage.Tests/lcov.info
- env:
- DEFAULT_REGION: eu-west-1
- AWS_ACCESS_KEY_ID: localkey
- AWS_SECRET_ACCESS_KEY: localsecret
+
+
+ - name: NDepend
+ uses: ndepend/ndepend-action@v1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ license: ${{ secrets.NDEPENDLICENSE }}
+ coveragefolder: ManagedCode.Storage.Tests
+ baseline: recent
+ #baseline: main_recent
-# - name: test-reports
-# uses: dorny/test-reporter@v1.5.0
-# with:
-# name: Test Reporter
-# reporter: dotnet-trx
-# path: ManagedCode.Storage.Tests/test-results.trx
-
- name : coverlet
uses: b3b00/coverlet-action@1.1.9
with:
@@ -95,3 +59,5 @@ jobs:
with:
github-token: ${{secrets.GITHUB_TOKEN }}
path-to-lcov: ManagedCode.Storage.Tests/lcov.info
+
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 84bb38d7..1ee2a624 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,8 +17,8 @@
https://github.com/managedcode/Storage
https://github.com/managedcode/Storage
Managed Code - Storage
- 2.1.14
- 2.1.14
+ 2.1.15-alpha
+ 2.1.15-alpha
diff --git a/ManagedCode.Storage.Aws/AWSStorage.cs b/ManagedCode.Storage.Aws/AWSStorage.cs
index 0eb00e17..5bec1579 100644
--- a/ManagedCode.Storage.Aws/AWSStorage.cs
+++ b/ManagedCode.Storage.Aws/AWSStorage.cs
@@ -34,7 +34,7 @@ public override async Task RemoveContainerAsync(CancellationToken cancel
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -104,7 +104,7 @@ protected override async Task CreateContainerInternalAsync(CancellationT
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -124,7 +124,7 @@ protected override async Task DeleteDirectoryInternalAsync(string direct
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -183,7 +183,7 @@ await localFile.CopyFromStreamAsync(await StorageClient.GetObjectStreamAsync(Sto
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -211,7 +211,7 @@ await StorageClient.DeleteObjectAsync(new DeleteObjectRequest
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -235,7 +235,7 @@ protected override async Task> ExistsInternalAsync(ExistOptions opt
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -269,7 +269,7 @@ protected override async Task> GetBlobMetadataInternalAsync
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -301,7 +301,7 @@ protected override async Task SetLegalHoldInternalAsync(bool hasLegalHol
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -323,7 +323,7 @@ protected override async Task> HasLegalHoldInternalAsync(LegalHoldO
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
diff --git a/ManagedCode.Storage.Aws/BlobStream.cs b/ManagedCode.Storage.Aws/BlobStream.cs
new file mode 100644
index 00000000..3f38a01e
--- /dev/null
+++ b/ManagedCode.Storage.Aws/BlobStream.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Amazon.S3;
+using Amazon.S3.Model;
+
+namespace ManagedCode.Storage.Aws;
+
+public class BlobStream : Stream
+ {
+ /* Note the that maximum size (as of now) of a file in S3 is 5TB so it isn't
+ * safe to assume all uploads will work here. MAX_PART_SIZE times MAX_PART_COUNT
+ * is ~50TB, which is too big for S3. */
+ const long MIN_PART_LENGTH = 5L * 1024 * 1024; // all parts but the last this size or greater
+ const long MAX_PART_LENGTH = 5L * 1024 * 1024 * 1024; // 5GB max per PUT
+ const long MAX_PART_COUNT = 10000; // no more than 10,000 parts total
+ const long DEFAULT_PART_LENGTH = MIN_PART_LENGTH;
+
+ internal class Metadata
+ {
+ public string BucketName;
+ public string Key;
+ public long PartLength = DEFAULT_PART_LENGTH;
+
+ public int PartCount = 0;
+ public string UploadId;
+ public MemoryStream CurrentStream;
+
+ public long Position = 0; // based on bytes written
+ public long Length = 0; // based on bytes written or SetLength, whichever is larger (no truncation)
+
+ public List Tasks = new List();
+ public ConcurrentDictionary PartETags = new ConcurrentDictionary();
+ }
+
+ Metadata _metadata = new Metadata();
+ IAmazonS3 _s3 = null;
+
+ public BlobStream(IAmazonS3 s3, string s3uri, long partLength = DEFAULT_PART_LENGTH)
+ : this(s3, new Uri(s3uri), partLength)
+ {
+ }
+
+ public BlobStream(IAmazonS3 s3, Uri s3uri, long partLength = DEFAULT_PART_LENGTH)
+ : this (s3, s3uri.Host, s3uri.LocalPath.Substring(1), partLength)
+ {
+ }
+
+ public BlobStream(IAmazonS3 s3, string bucket, string key, long partLength = DEFAULT_PART_LENGTH)
+ {
+ _s3 = s3;
+ _metadata.BucketName = bucket;
+ _metadata.Key = key;
+ _metadata.PartLength = partLength;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_metadata != null)
+ {
+ Flush(true);
+ CompleteUpload();
+ }
+ }
+ _metadata = null;
+ base.Dispose(disposing);
+ }
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => _metadata.Length = Math.Max(_metadata.Length, _metadata.Position);
+
+ public override long Position
+ {
+ get => _metadata.Position;
+ set => throw new NotImplementedException();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
+
+ public override void SetLength(long value)
+ {
+ _metadata.Length = Math.Max(_metadata.Length, value);
+ _metadata.PartLength = Math.Max(MIN_PART_LENGTH, Math.Min(MAX_PART_LENGTH, _metadata.Length / MAX_PART_COUNT));
+ }
+
+ private void StartNewPart()
+ {
+ if (_metadata.CurrentStream != null) {
+ Flush(false);
+ }
+ _metadata.CurrentStream = new MemoryStream();
+ _metadata.PartLength = Math.Min(MAX_PART_LENGTH, Math.Max(_metadata.PartLength, (_metadata.PartCount / 2 + 1) * MIN_PART_LENGTH));
+ }
+
+ public override void Flush()
+ {
+ Flush(false);
+ }
+
+ private void Flush(bool disposing)
+ {
+ if ((_metadata.CurrentStream == null || _metadata.CurrentStream.Length < MIN_PART_LENGTH) &&
+ !disposing)
+ return;
+
+ if (_metadata.UploadId == null) {
+ _metadata.UploadId = _s3.InitiateMultipartUploadAsync(new InitiateMultipartUploadRequest()
+ {
+ BucketName = _metadata.BucketName,
+ Key = _metadata.Key
+ }).GetAwaiter().GetResult().UploadId;
+ }
+
+ if (_metadata.CurrentStream != null)
+ {
+ var i = ++_metadata.PartCount;
+
+ _metadata.CurrentStream.Seek(0, SeekOrigin.Begin);
+ var request = new UploadPartRequest()
+ {
+ BucketName = _metadata.BucketName,
+ Key = _metadata.Key,
+ UploadId = _metadata.UploadId,
+ PartNumber = i,
+ IsLastPart = disposing,
+ InputStream = _metadata.CurrentStream
+ };
+ _metadata.CurrentStream = null;
+
+ var upload = Task.Run(async () =>
+ {
+ var response = await _s3.UploadPartAsync(request);
+ _metadata.PartETags.AddOrUpdate(i, response.ETag,
+ (n, s) => response.ETag);
+ request.InputStream.Dispose();
+ });
+ _metadata.Tasks.Add(upload);
+ }
+ }
+
+ private void CompleteUpload()
+ {
+ Task.WaitAll(_metadata.Tasks.ToArray());
+
+ if (Length > 0) {
+ _s3.CompleteMultipartUploadAsync(new CompleteMultipartUploadRequest()
+ {
+ BucketName = _metadata.BucketName,
+ Key = _metadata.Key,
+ PartETags = _metadata.PartETags.Select(e => new PartETag(e.Key, e.Value)).ToList(),
+ UploadId = _metadata.UploadId
+ }).GetAwaiter().GetResult();
+ }
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (count == 0) return;
+
+ // write as much of the buffer as will fit to the current part, and if needed
+ // allocate a new part and continue writing to it (and so on).
+ var o = offset;
+ var c = Math.Min(count, buffer.Length - offset); // don't over-read the buffer, even if asked to
+ do
+ {
+ if (_metadata.CurrentStream == null || _metadata.CurrentStream.Length >= _metadata.PartLength)
+ StartNewPart();
+
+ var remaining = _metadata.PartLength - _metadata.CurrentStream.Length;
+ var w = Math.Min(c, (int)remaining);
+ _metadata.CurrentStream.Write(buffer, o, w);
+
+ _metadata.Position += w;
+ c -= w;
+ o += w;
+ } while (c > 0);
+ }
+ }
diff --git a/ManagedCode.Storage.Aws/ManagedCode.Storage.Aws.csproj b/ManagedCode.Storage.Aws/ManagedCode.Storage.Aws.csproj
index 62117abf..00b41d63 100644
--- a/ManagedCode.Storage.Aws/ManagedCode.Storage.Aws.csproj
+++ b/ManagedCode.Storage.Aws/ManagedCode.Storage.Aws.csproj
@@ -22,10 +22,10 @@
-
-
+
+
-
+
diff --git a/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs b/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs
index 11cc2f80..a3e21081 100644
--- a/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs
+++ b/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs
@@ -3,10 +3,12 @@
namespace ManagedCode.Storage.Aws.Options;
-public class AWSStorageOptions : StorageOptions
+public class AWSStorageOptions : IStorageOptions
{
public string? PublicKey { get; set; }
public string? SecretKey { get; set; }
public string? Bucket { get; set; }
public AmazonS3Config? OriginalOptions { get; set; } = new();
+
+ public bool CreateContainerIfNotExists { get; set; } = true;
}
\ No newline at end of file
diff --git a/ManagedCode.Storage.AzureDataLake/AzureDataLakeStorage.cs b/ManagedCode.Storage.Azure.DataLake/AzureDataLakeStorage.cs
similarity index 94%
rename from ManagedCode.Storage.AzureDataLake/AzureDataLakeStorage.cs
rename to ManagedCode.Storage.Azure.DataLake/AzureDataLakeStorage.cs
index fd63b3b3..1fe4e227 100644
--- a/ManagedCode.Storage.AzureDataLake/AzureDataLakeStorage.cs
+++ b/ManagedCode.Storage.Azure.DataLake/AzureDataLakeStorage.cs
@@ -9,12 +9,12 @@
using Azure.Storage.Files.DataLake.Models;
using ManagedCode.Communication;
using ManagedCode.Communication.Extensions;
-using ManagedCode.Storage.AzureDataLake.Options;
+using ManagedCode.Storage.Azure.DataLake.Options;
using ManagedCode.Storage.Core;
using ManagedCode.Storage.Core.Models;
using Microsoft.Extensions.Logging;
-namespace ManagedCode.Storage.AzureDataLake;
+namespace ManagedCode.Storage.Azure.DataLake;
public class AzureDataLakeStorage : BaseStorage, IAzureDataLakeStorage
{
@@ -38,7 +38,7 @@ public override async Task RemoveContainerAsync(CancellationToken cancel
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -54,7 +54,7 @@ public async Task> OpenReadStreamAsync(OpenReadStreamOptions opti
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -70,7 +70,7 @@ public async Task> OpenWriteStreamAsync(OpenWriteStreamOptions op
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -130,7 +130,7 @@ await _dataLakeServiceClient.CreateFileSystemAsync(StorageOptions.FileSystem, St
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -156,7 +156,7 @@ protected override async Task> UploadInternalAsync(Stream s
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -187,7 +187,7 @@ protected override async Task> DownloadInternalAsync(LocalFile
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -202,7 +202,7 @@ protected override async Task> DeleteInternalAsync(DeleteOptions op
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -217,7 +217,7 @@ protected override async Task> ExistsInternalAsync(ExistOptions opt
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -246,7 +246,7 @@ protected override async Task> GetBlobMetadataInternalAsync
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
diff --git a/ManagedCode.Storage.AzureDataLake/Extensions/ServiceCollectionExtensions.cs b/ManagedCode.Storage.Azure.DataLake/Extensions/ServiceCollectionExtensions.cs
similarity index 95%
rename from ManagedCode.Storage.AzureDataLake/Extensions/ServiceCollectionExtensions.cs
rename to ManagedCode.Storage.Azure.DataLake/Extensions/ServiceCollectionExtensions.cs
index d2051ff4..de832309 100644
--- a/ManagedCode.Storage.AzureDataLake/Extensions/ServiceCollectionExtensions.cs
+++ b/ManagedCode.Storage.Azure.DataLake/Extensions/ServiceCollectionExtensions.cs
@@ -1,10 +1,10 @@
using System;
-using ManagedCode.Storage.AzureDataLake.Options;
+using ManagedCode.Storage.Azure.DataLake.Options;
using ManagedCode.Storage.Core;
using ManagedCode.Storage.Core.Exceptions;
using Microsoft.Extensions.DependencyInjection;
-namespace ManagedCode.Storage.AzureDataLake.Extensions;
+namespace ManagedCode.Storage.Azure.DataLake.Extensions;
public static class ServiceCollectionExtensions
{
diff --git a/ManagedCode.Storage.AzureDataLake/IAzureDataLakeStorage.cs b/ManagedCode.Storage.Azure.DataLake/IAzureDataLakeStorage.cs
similarity index 90%
rename from ManagedCode.Storage.AzureDataLake/IAzureDataLakeStorage.cs
rename to ManagedCode.Storage.Azure.DataLake/IAzureDataLakeStorage.cs
index 14c6e267..25de14b4 100644
--- a/ManagedCode.Storage.AzureDataLake/IAzureDataLakeStorage.cs
+++ b/ManagedCode.Storage.Azure.DataLake/IAzureDataLakeStorage.cs
@@ -3,10 +3,10 @@
using System.Threading.Tasks;
using Azure.Storage.Files.DataLake;
using ManagedCode.Communication;
-using ManagedCode.Storage.AzureDataLake.Options;
+using ManagedCode.Storage.Azure.DataLake.Options;
using ManagedCode.Storage.Core;
-namespace ManagedCode.Storage.AzureDataLake;
+namespace ManagedCode.Storage.Azure.DataLake;
public interface IAzureDataLakeStorage : IStorage
{
diff --git a/ManagedCode.Storage.AzureDataLake/ManagedCode.Storage.AzureDataLake.csproj b/ManagedCode.Storage.Azure.DataLake/ManagedCode.Storage.Azure.DataLake.csproj
similarity index 88%
rename from ManagedCode.Storage.AzureDataLake/ManagedCode.Storage.AzureDataLake.csproj
rename to ManagedCode.Storage.Azure.DataLake/ManagedCode.Storage.Azure.DataLake.csproj
index ddbfda76..30bccec4 100644
--- a/ManagedCode.Storage.AzureDataLake/ManagedCode.Storage.AzureDataLake.csproj
+++ b/ManagedCode.Storage.Azure.DataLake/ManagedCode.Storage.Azure.DataLake.csproj
@@ -12,7 +12,7 @@
ManagedCode.Storage.AzureDataLake
- ManagedCode.Storage.AzureDataLake
+ ManagedCode.Storage.Azure.DataLake
Storage for AzureDataLake
managedcode, azure, storage, cloud, blob, datalake, data, lake
@@ -22,11 +22,11 @@
-
-
-
+
+
+
-
+
diff --git a/ManagedCode.Storage.Azure.DataLake/Options/AzureDataLakeStorageOptions.cs b/ManagedCode.Storage.Azure.DataLake/Options/AzureDataLakeStorageOptions.cs
new file mode 100644
index 00000000..277f83e4
--- /dev/null
+++ b/ManagedCode.Storage.Azure.DataLake/Options/AzureDataLakeStorageOptions.cs
@@ -0,0 +1,17 @@
+using Azure.Storage.Files.DataLake.Models;
+using ManagedCode.Storage.Core;
+
+namespace ManagedCode.Storage.Azure.DataLake.Options;
+
+public class AzureDataLakeStorageOptions : IStorageOptions
+{
+ public string ConnectionString { get; set; }
+ public string FileSystem { get; set; }
+
+ public bool CreateContainerIfNotExists { get; set; } = true;
+
+ public DataLakeFileSystemCreateOptions PublicAccessType { get; set; } = new()
+ {
+ PublicAccessType = global::Azure.Storage.Files.DataLake.Models.PublicAccessType.None
+ };
+}
\ No newline at end of file
diff --git a/ManagedCode.Storage.AzureDataLake/Options/OpenReadStreamOptions.cs b/ManagedCode.Storage.Azure.DataLake/Options/OpenReadStreamOptions.cs
similarity index 76%
rename from ManagedCode.Storage.AzureDataLake/Options/OpenReadStreamOptions.cs
rename to ManagedCode.Storage.Azure.DataLake/Options/OpenReadStreamOptions.cs
index 67537b20..4c8b4ba6 100644
--- a/ManagedCode.Storage.AzureDataLake/Options/OpenReadStreamOptions.cs
+++ b/ManagedCode.Storage.Azure.DataLake/Options/OpenReadStreamOptions.cs
@@ -1,6 +1,6 @@
using ManagedCode.Storage.Core.Models;
-namespace ManagedCode.Storage.AzureDataLake.Options;
+namespace ManagedCode.Storage.Azure.DataLake.Options;
public class OpenReadStreamOptions : BaseOptions
{
diff --git a/ManagedCode.Storage.AzureDataLake/Options/OpenWriteStreamOptions.cs b/ManagedCode.Storage.Azure.DataLake/Options/OpenWriteStreamOptions.cs
similarity index 71%
rename from ManagedCode.Storage.AzureDataLake/Options/OpenWriteStreamOptions.cs
rename to ManagedCode.Storage.Azure.DataLake/Options/OpenWriteStreamOptions.cs
index 21ea0084..7bb1b7c9 100644
--- a/ManagedCode.Storage.AzureDataLake/Options/OpenWriteStreamOptions.cs
+++ b/ManagedCode.Storage.Azure.DataLake/Options/OpenWriteStreamOptions.cs
@@ -1,6 +1,6 @@
using ManagedCode.Storage.Core.Models;
-namespace ManagedCode.Storage.AzureDataLake.Options;
+namespace ManagedCode.Storage.Azure.DataLake.Options;
public class OpenWriteStreamOptions : BaseOptions
{
diff --git a/ManagedCode.Storage.Azure/AzureStorage.cs b/ManagedCode.Storage.Azure/AzureStorage.cs
index 754ba868..63bcc708 100644
--- a/ManagedCode.Storage.Azure/AzureStorage.cs
+++ b/ManagedCode.Storage.Azure/AzureStorage.cs
@@ -3,9 +3,11 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure;
+using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
@@ -17,11 +19,11 @@
namespace ManagedCode.Storage.Azure;
-public class AzureStorage : BaseStorage, IAzureStorage
+public class AzureStorage : BaseStorage, IAzureStorage
{
private readonly ILogger? _logger;
- public AzureStorage(AzureStorageOptions options, ILogger? logger = null) : base(options)
+ public AzureStorage(IAzureStorageOptions options, ILogger? logger = null) : base(options)
{
_logger = logger;
}
@@ -36,7 +38,7 @@ public override async Task RemoveContainerAsync(CancellationToken cancel
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -69,7 +71,8 @@ public override async IAsyncEnumerable GetBlobMetadataListAsync(st
}
}
- public async Task> OpenReadStreamAsync(string fileName, CancellationToken cancellationToken = default)
+ public async Task> OpenReadStreamAsync(string fileName,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -80,12 +83,13 @@ public async Task> OpenReadStreamAsync(string fileName, Cancellat
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
- public async Task> OpenWriteStreamAsync(string fileName, CancellationToken cancellationToken = default)
+ public async Task> OpenWriteStreamAsync(string fileName,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -96,7 +100,7 @@ public async Task> OpenWriteStreamAsync(string fileName, Cancella
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -113,24 +117,36 @@ public Stream GetBlobStream(string fileName, bool userBuffer = true, int bufferS
protected override BlobContainerClient CreateStorageClient()
{
- return new BlobContainerClient(
- StorageOptions.ConnectionString,
- StorageOptions.Container,
- StorageOptions.OriginalOptions
- );
+ return StorageOptions switch
+ {
+ AzureStorageOptions azureStorageOptions => new BlobContainerClient(
+ azureStorageOptions.ConnectionString,
+ azureStorageOptions.Container,
+ azureStorageOptions.OriginalOptions
+ ),
+
+ AzureStorageCredentialsOptions azureStorageCredentialsOptions => new BlobContainerClient(
+ new Uri(
+ $"https://{azureStorageCredentialsOptions.AccountName}.blob.core.windows.net/{azureStorageCredentialsOptions.ContainerName}"),
+ azureStorageCredentialsOptions.Credentials,
+ azureStorageCredentialsOptions.OriginalOptions
+ ),
+ };
}
protected override async Task CreateContainerInternalAsync(CancellationToken cancellationToken = default)
{
try
{
- _ = await StorageClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer, cancellationToken: cancellationToken);
+ _ = await StorageClient.CreateIfNotExistsAsync(PublicAccessType.BlobContainer,
+ cancellationToken: cancellationToken);
var policy = await StorageClient.GetAccessPolicyAsync(cancellationToken: cancellationToken);
if (policy.Value.BlobPublicAccess != StorageOptions.PublicAccessType)
{
- await StorageClient.SetAccessPolicyAsync(StorageOptions.PublicAccessType, cancellationToken: cancellationToken);
+ await StorageClient.SetAccessPolicyAsync(StorageOptions.PublicAccessType,
+ cancellationToken: cancellationToken);
}
-
+
IsContainerCreated = true;
return Result.Succeed();
@@ -138,12 +154,13 @@ protected override async Task CreateContainerInternalAsync(CancellationT
catch (Exception ex)
{
IsContainerCreated = false;
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
- protected override async Task DeleteDirectoryInternalAsync(string directory, CancellationToken cancellationToken = default)
+ protected override async Task DeleteDirectoryInternalAsync(string directory,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -159,7 +176,7 @@ protected override async Task DeleteDirectoryInternalAsync(string direct
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -188,7 +205,7 @@ protected override async Task> UploadInternalAsync(Stream s
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -222,12 +239,13 @@ protected override async Task> DownloadInternalAsync(LocalFile
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
- protected override async Task> DeleteInternalAsync(DeleteOptions options, CancellationToken cancellationToken = default)
+ protected override async Task> DeleteInternalAsync(DeleteOptions options,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -243,12 +261,13 @@ protected override async Task> DeleteInternalAsync(DeleteOptions op
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
- protected override async Task> ExistsInternalAsync(ExistOptions options, CancellationToken cancellationToken = default)
+ protected override async Task> ExistsInternalAsync(ExistOptions options,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -259,7 +278,7 @@ protected override async Task> ExistsInternalAsync(ExistOptions opt
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -293,7 +312,7 @@ protected override async Task> GetBlobMetadataInternalAsync
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
@@ -312,12 +331,13 @@ protected override async Task SetLegalHoldInternalAsync(bool hasLegalHol
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
- protected override async Task> HasLegalHoldInternalAsync(LegalHoldOptions options, CancellationToken cancellationToken = default)
+ protected override async Task> HasLegalHoldInternalAsync(LegalHoldOptions options,
+ CancellationToken cancellationToken = default)
{
try
{
@@ -328,8 +348,30 @@ protected override async Task> HasLegalHoldInternalAsync(LegalHoldO
}
catch (Exception ex)
{
- _logger?.LogError(ex.Message, ex);
+ _logger.LogException(ex);
return Result.Fail(ex);
}
}
+
+ public async Task SetStorageOptions(IStorageOptions options, CancellationToken cancellationToken = default)
+ {
+ StorageOptions = options as IAzureStorageOptions;
+
+ StorageClient = CreateStorageClient();
+ return await CreateContainerAsync(cancellationToken);
+ }
+
+ public async Task SetStorageOptions(Action options,
+ CancellationToken cancellationToken = default)
+ {
+ var type = options.GetType().GetGenericArguments()[0];
+
+ StorageOptions = JsonSerializer.Deserialize(JsonSerializer.Serialize(StorageOptions), type) as IAzureStorageOptions;
+
+ options.Invoke(StorageOptions);
+
+ StorageClient = CreateStorageClient();
+
+ return await CreateContainerAsync(cancellationToken);
+ }
}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs b/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs
index 49f43afd..e4d457ae 100644
--- a/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs
+++ b/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs
@@ -8,7 +8,8 @@ namespace ManagedCode.Storage.Azure.Extensions;
public static class ServiceCollectionExtensions
{
- public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, Action action)
+ public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection,
+ Action action)
{
var options = new AzureStorageOptions();
action.Invoke(options);
@@ -18,7 +19,8 @@ public static IServiceCollection AddAzureStorage(this IServiceCollection service
return serviceCollection.AddAzureStorage(options);
}
- public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, Action action)
+ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection,
+ Action action)
{
var options = new AzureStorageOptions();
action.Invoke(options);
@@ -28,14 +30,38 @@ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollectio
return serviceCollection.AddAzureStorageAsDefault(options);
}
- public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, AzureStorageOptions options)
+ public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection,
+ Action action)
+ {
+ var options = new AzureStorageCredentialsOptions();
+ action.Invoke(options);
+
+ CheckConfiguration(options);
+
+ return serviceCollection.AddAzureStorage(options);
+ }
+
+ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection,
+ Action action)
+ {
+ var options = new AzureStorageCredentialsOptions();
+ action.Invoke(options);
+
+ CheckConfiguration(options);
+
+ return serviceCollection.AddAzureStorageAsDefault(options);
+ }
+
+ public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection,
+ IAzureStorageOptions options)
{
CheckConfiguration(options);
serviceCollection.AddSingleton(options);
return serviceCollection.AddTransient();
}
- public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, AzureStorageOptions options)
+ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection,
+ IAzureStorageOptions options)
{
CheckConfiguration(options);
serviceCollection.AddSingleton(options);
@@ -43,16 +69,40 @@ public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollectio
return serviceCollection.AddTransient();
}
- private static void CheckConfiguration(AzureStorageOptions options)
+ private static void CheckConfiguration(IAzureStorageOptions options)
{
- if (string.IsNullOrEmpty(options.ConnectionString))
+ if (options is AzureStorageOptions azureStorageOptions)
{
- throw new BadConfigurationException($"{nameof(options.ConnectionString)} cannot be empty");
+ if (string.IsNullOrEmpty(azureStorageOptions.ConnectionString))
+ {
+ throw new BadConfigurationException($"{nameof(azureStorageOptions.ConnectionString)} cannot be empty");
+ }
+
+ if (string.IsNullOrEmpty(azureStorageOptions.Container))
+ {
+ throw new BadConfigurationException($"{nameof(azureStorageOptions.Container)} cannot be empty");
+ }
}
- if (string.IsNullOrEmpty(options.Container))
+ if (options is AzureStorageCredentialsOptions azureStorageCredentialsOptions)
{
- throw new BadConfigurationException($"{nameof(options.Container)} cannot be empty");
+ if (string.IsNullOrEmpty(azureStorageCredentialsOptions.AccountName))
+ {
+ throw new BadConfigurationException(
+ $"{nameof(azureStorageCredentialsOptions.AccountName)} cannot be empty");
+ }
+
+ if (string.IsNullOrEmpty(azureStorageCredentialsOptions.ContainerName))
+ {
+ throw new BadConfigurationException(
+ $"{nameof(azureStorageCredentialsOptions.ContainerName)} cannot be empty");
+ }
+
+ if (azureStorageCredentialsOptions.Credentials is null)
+ {
+ throw new BadConfigurationException(
+ $"{nameof(azureStorageCredentialsOptions.Credentials)} cannot be null");
+ }
}
}
}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Azure/IAzureStorage.cs b/ManagedCode.Storage.Azure/IAzureStorage.cs
index cd0ce52b..68c5c9eb 100644
--- a/ManagedCode.Storage.Azure/IAzureStorage.cs
+++ b/ManagedCode.Storage.Azure/IAzureStorage.cs
@@ -8,7 +8,7 @@
namespace ManagedCode.Storage.Azure;
-public interface IAzureStorage : IStorage
+public interface IAzureStorage : IStorage
{
Task> OpenReadStreamAsync(string fileName, CancellationToken cancellationToken = default);
Task> OpenWriteStreamAsync(string fileName, CancellationToken cancellationToken = default);
diff --git a/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj b/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj
index 542dcb26..a88d0bf3 100644
--- a/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj
+++ b/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj
@@ -22,11 +22,12 @@
-
-
-
+
+
+
+
-
+
diff --git a/ManagedCode.Storage.Azure/Options/AzureStorageCredentialsOptions.cs b/ManagedCode.Storage.Azure/Options/AzureStorageCredentialsOptions.cs
new file mode 100644
index 00000000..9b646d4d
--- /dev/null
+++ b/ManagedCode.Storage.Azure/Options/AzureStorageCredentialsOptions.cs
@@ -0,0 +1,20 @@
+using Azure.Core;
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using ManagedCode.Storage.Core;
+
+namespace ManagedCode.Storage.Azure.Options;
+
+public class AzureStorageCredentialsOptions : IAzureStorageOptions
+{
+ public string AccountName { get; set; }
+ public string ContainerName { get; set; }
+
+ public TokenCredential Credentials { get; set; }
+
+ public string? Container { get; set; }
+ public PublicAccessType PublicAccessType { get; set; }
+ public BlobClientOptions? OriginalOptions { get; set; }
+
+ public bool CreateContainerIfNotExists { get; set; } = true;
+}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Azure/Options/AzureStorageOptions.cs b/ManagedCode.Storage.Azure/Options/AzureStorageOptions.cs
index 017268ff..d38abc00 100644
--- a/ManagedCode.Storage.Azure/Options/AzureStorageOptions.cs
+++ b/ManagedCode.Storage.Azure/Options/AzureStorageOptions.cs
@@ -4,10 +4,12 @@
namespace ManagedCode.Storage.Azure.Options;
-public class AzureStorageOptions : StorageOptions
+public class AzureStorageOptions : IAzureStorageOptions
{
public string? ConnectionString { get; set; }
public string? Container { get; set; }
public PublicAccessType PublicAccessType { get; set; }
public BlobClientOptions? OriginalOptions { get; set; }
+
+ public bool CreateContainerIfNotExists { get; set; } = true;
}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Azure/Options/IAzureStorageOptions.cs b/ManagedCode.Storage.Azure/Options/IAzureStorageOptions.cs
new file mode 100644
index 00000000..942b328f
--- /dev/null
+++ b/ManagedCode.Storage.Azure/Options/IAzureStorageOptions.cs
@@ -0,0 +1,12 @@
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using ManagedCode.Storage.Core;
+
+namespace ManagedCode.Storage.Azure.Options;
+
+public interface IAzureStorageOptions : IStorageOptions
+{
+ public string? Container { get; set; }
+ public PublicAccessType PublicAccessType { get; set; }
+ public BlobClientOptions? OriginalOptions { get; set; }
+}
\ No newline at end of file
diff --git a/ManagedCode.Storage.AzureDataLake/Options/AzureDataLakeStorageOptions.cs b/ManagedCode.Storage.AzureDataLake/Options/AzureDataLakeStorageOptions.cs
deleted file mode 100644
index 58b68eb9..00000000
--- a/ManagedCode.Storage.AzureDataLake/Options/AzureDataLakeStorageOptions.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Azure.Storage.Files.DataLake.Models;
-using ManagedCode.Storage.Core;
-
-namespace ManagedCode.Storage.AzureDataLake.Options;
-
-public class AzureDataLakeStorageOptions : StorageOptions
-{
- public string ConnectionString { get; set; }
- public string FileSystem { get; set; }
-
- public DataLakeFileSystemCreateOptions PublicAccessType { get; set; } = new()
- {
- PublicAccessType = Azure.Storage.Files.DataLake.Models.PublicAccessType.None
- };
-}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Client.SignalR/Class1.cs b/ManagedCode.Storage.Client.SignalR/Class1.cs
new file mode 100644
index 00000000..206aed0a
--- /dev/null
+++ b/ManagedCode.Storage.Client.SignalR/Class1.cs
@@ -0,0 +1,5 @@
+namespace ManagedCode.Storage.Client;
+
+public class Class1
+{
+}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Client.SignalR/ManagedCode.Storage.Client.SignalR.csproj b/ManagedCode.Storage.Client.SignalR/ManagedCode.Storage.Client.SignalR.csproj
new file mode 100644
index 00000000..bc2f854f
--- /dev/null
+++ b/ManagedCode.Storage.Client.SignalR/ManagedCode.Storage.Client.SignalR.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net6.0;net7.0
+ 11
+ true
+ embedded
+ enable
+ true
+ Library
+
+
+
+
+ ManagedCode.Storage.Client.SignalR
+ MManagedCode.Storage.Client.SignalR
+ Extensions for ASP.NET for Storage
+ managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ManagedCode.Storage.Client/Class1.cs b/ManagedCode.Storage.Client/Class1.cs
new file mode 100644
index 00000000..206aed0a
--- /dev/null
+++ b/ManagedCode.Storage.Client/Class1.cs
@@ -0,0 +1,5 @@
+namespace ManagedCode.Storage.Client;
+
+public class Class1
+{
+}
\ No newline at end of file
diff --git a/ManagedCode.Storage.Client/ManagedCode.Storage.Client.csproj b/ManagedCode.Storage.Client/ManagedCode.Storage.Client.csproj
new file mode 100644
index 00000000..845bdd74
--- /dev/null
+++ b/ManagedCode.Storage.Client/ManagedCode.Storage.Client.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net6.0;net7.0
+ 11
+ true
+ embedded
+ enable
+ true
+ Library
+
+
+
+
+ ManagedCode.Storage.Client
+ ManagedCode.Storage.Client
+ Extensions for ASP.NET for Storage
+ managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ManagedCode.Storage.Core/BaseStorage.cs b/ManagedCode.Storage.Core/BaseStorage.cs
index 008213cd..0e3fa739 100644
--- a/ManagedCode.Storage.Core/BaseStorage.cs
+++ b/ManagedCode.Storage.Core/BaseStorage.cs
@@ -12,7 +12,7 @@
namespace ManagedCode.Storage.Core;
-public abstract class BaseStorage : IStorage where TOptions : StorageOptions
+public abstract class BaseStorage : IStorage where TOptions : IStorageOptions
{
protected bool IsContainerCreated;
protected TOptions StorageOptions;
diff --git a/ManagedCode.Storage.Core/IStorage.cs b/ManagedCode.Storage.Core/IStorage.cs
index f4042b02..38e12144 100644
--- a/ManagedCode.Storage.Core/IStorage.cs
+++ b/ManagedCode.Storage.Core/IStorage.cs
@@ -8,7 +8,7 @@
namespace ManagedCode.Storage.Core;
-public interface IStorage : IStorage where TOptions : StorageOptions
+public interface IStorage : IStorage where TOptions : IStorageOptions
{
T StorageClient { get; }
Task SetStorageOptions(TOptions options, CancellationToken cancellationToken = default);
diff --git a/ManagedCode.Storage.Core/LoggerExtensions.cs b/ManagedCode.Storage.Core/LoggerExtensions.cs
new file mode 100644
index 00000000..c121e09e
--- /dev/null
+++ b/ManagedCode.Storage.Core/LoggerExtensions.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Logging;
+
+namespace ManagedCode.Storage.Core;
+
+public static class LoggerExtensions
+{
+ public static void LogException(this ILogger? logger, Exception ex, [CallerMemberName] string methodName = null)
+ {
+ logger?.LogError(ex, methodName);
+ }
+}
\ 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 6e583547..5eeb2668 100644
--- a/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj
+++ b/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj
@@ -18,10 +18,10 @@
-
+
-
+
diff --git a/ManagedCode.Storage.Core/Prototype.cs b/ManagedCode.Storage.Core/Prototype.cs
new file mode 100644
index 00000000..da14cf73
--- /dev/null
+++ b/ManagedCode.Storage.Core/Prototype.cs
@@ -0,0 +1,227 @@
+// using System;
+// using System.Collections.Generic;
+// using System.IO;
+// using System.Threading;
+// using System.Threading.Tasks;
+// using ManagedCode.Communication;
+// using ManagedCode.Storage.Core.Models;
+//
+// namespace ManageCode.FileStream.Client.Abstractions;
+// public interface IFileEndpoint
+// {
+//
+//
+// public Task UploadAsync(string[] filesPath);
+// }
+//
+// public class Prog
+// {
+// public void Do()
+// {
+// IFileClient client;
+//
+// var a = client.UploadAsync(x => x.FromPath("file path"));
+// }
+// }
+//
+// public class FileUploadExtensions
+// {
+//
+// ///
+// /// Upload data from the stream into the blob storage.
+// ///
+// Task> UploadAsync(Stream stream, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload array of bytes into the blob storage.
+// ///
+// Task> UploadAsync(byte[] data, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the string into the blob storage.
+// ///
+// Task> UploadAsync(string content, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the file into the blob storage.
+// ///
+// Task> UploadAsync(FileInfo fileInfo, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the stream into the blob storage.
+// ///
+// Task> UploadAsync(Stream stream, UploadOptions options, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload array of bytes into the blob storage.
+// ///
+// Task> UploadAsync(byte[] data, UploadOptions options, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the string into the blob storage.
+// ///
+// Task> UploadAsync(string content, UploadOptions options, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the file into the blob storage.
+// ///
+// Task> UploadAsync(FileInfo fileInfo, UploadOptions options, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the stream into the blob storage.
+// ///
+// Task> UploadAsync(Stream stream, Action action, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload array of bytes into the blob storage.
+// ///
+// Task> UploadAsync(byte[] data, Action action, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the string into the blob storage.
+// ///
+// Task> UploadAsync(string content, Action action, CancellationToken cancellationToken = default);
+//
+// ///
+// /// Upload data from the file into the blob storage.
+// ///
+// Task> UploadAsync(FileInfo fileInfo, Action action, CancellationToken cancellationToken = default);
+// }
+//
+// public class BlobMetaData
+// {
+//
+// }
+//
+//
+// public interface IBlobStorage :
+// IFileUploader,
+// IFileDownloader,
+// IFileDeleter