├── .github
└── workflows
│ ├── codeql-analysis.yml
│ ├── dotnet.yml
│ └── nuget.yml
├── .gitignore
├── Directory.Build.props
├── Integraions
├── ManagedCode.Storage.Client.SignalR
│ ├── Class1.cs
│ └── ManagedCode.Storage.Client.SignalR.csproj
├── ManagedCode.Storage.Client
│ ├── IStorageClient.cs
│ ├── ManagedCode.Storage.Client.csproj
│ ├── ProgressStatus.cs
│ └── StorageClient.cs
└── ManagedCode.Storage.Server
│ ├── Extensions
│ ├── Controller
│ │ ├── ControllerDownloadExtensions.cs
│ │ └── ControllerUploadExtensions.cs
│ ├── DependencyInjection
│ │ └── StorageServiceCollectionExtensions.cs
│ ├── File
│ │ ├── BrowserFileExtensions.cs
│ │ └── FormFileExtensions.cs
│ └── Storage
│ │ ├── StorageBrowserFileExtensions.cs
│ │ ├── StorageExtensions.cs
│ │ └── StorageFromFileExtensions.cs
│ ├── Helpers
│ └── StreamHelper.cs
│ ├── ManagedCode.Storage.Server.csproj
│ ├── Models
│ ├── FilePayload.cs
│ └── FileUploadPayload.cs
│ ├── Properties
│ └── launchSettings.json
│ └── StorageSetupBackgroundService.cs
├── LICENSE
├── ManagedCode.Storage.Core
├── BaseStorage.cs
├── Exceptions
│ └── BadConfigurationException.cs
├── Extensions
│ ├── ServiceCollectionExtensions.cs
│ └── StorageOptionsExtensions.cs
├── Helpers
│ └── Crc32Helper.cs
├── IDownloader.cs
├── IStorage.cs
├── IStorageOperations.cs
├── IStorageOptions.cs
├── IStreamer.cs
├── IUploader.cs
├── LoggerExtensions.cs
├── ManagedCode.Storage.Core.csproj
├── Models
│ ├── BaseOptions.cs
│ ├── BlobMetadata.cs
│ ├── DeleteOptions.cs
│ ├── DownloadOptions.cs
│ ├── ExistOptions.cs
│ ├── LegalHoldOptions.cs
│ ├── LocalFile.cs
│ ├── MetadataOptions.cs
│ └── UploadOptions.cs
├── Prototype.cs
├── Providers
│ ├── IStorageFactory.cs
│ ├── IStorageProvider.cs
│ └── StorageFactory.cs
└── StringStream.cs
├── ManagedCode.Storage.TestFakes
├── FakeAWSStorage.cs
├── FakeAzureDataLakeStorage.cs
├── FakeAzureStorage.cs
├── FakeGoogleStorage.cs
├── ManagedCode.Storage.TestFakes.csproj
└── MockCollectionExtensions.cs
├── ManagedCode.Storage.sln
├── README.md
├── Storages
├── ManagedCode.Storage.Aws
│ ├── AWSStorage.cs
│ ├── AWSStorageProvider.cs
│ ├── BlobStream.cs
│ ├── Extensions
│ │ ├── ServiceCollectionExtensions.cs
│ │ └── StorageFactoryExtensions.cs
│ ├── IAWSStorage.cs
│ ├── ManagedCode.Storage.Aws.csproj
│ └── Options
│ │ └── AWSStorageOptions.cs
├── ManagedCode.Storage.Azure.DataLake
│ ├── AzureDataLakeStorage.cs
│ ├── AzureDataLakeStorageProvider.cs
│ ├── Extensions
│ │ ├── ServiceCollectionExtensions.cs
│ │ └── StorageFactoryExtensions.cs
│ ├── IAzureDataLakeStorage.cs
│ ├── ManagedCode.Storage.Azure.DataLake.csproj
│ └── Options
│ │ ├── AzureDataLakeStorageOptions.cs
│ │ ├── OpenReadStreamOptions.cs
│ │ └── OpenWriteStreamOptions.cs
├── ManagedCode.Storage.Azure
│ ├── AzureStorage.cs
│ ├── AzureStorageProvider.cs
│ ├── BlobStream.cs
│ ├── Extensions
│ │ ├── ServiceCollectionExtensions.cs
│ │ └── StorageFactoryExtensions.cs
│ ├── IAzureStorage.cs
│ ├── ManagedCode.Storage.Azure.csproj
│ └── Options
│ │ ├── AzureStorageCredentialsOptions.cs
│ │ ├── AzureStorageOptions.cs
│ │ └── IAzureStorageOptions.cs
├── ManagedCode.Storage.FileSystem
│ ├── Extensions
│ │ ├── ServiceCollectionExtensions.cs
│ │ └── StorageFactoryExtensions.cs
│ ├── FileSystemStorage.cs
│ ├── FileSystemStorageProvider.cs
│ ├── IFileSystemStorage.cs
│ ├── ManagedCode.Storage.FileSystem.csproj
│ └── Options
│ │ └── FileSystemStorageOptions.cs
└── ManagedCode.Storage.Google
│ ├── Extensions
│ ├── ServiceCollectionExtensions.cs
│ └── StorageFactoryExtensions.cs
│ ├── GCPStorage.cs
│ ├── GCPStorageProvider.cs
│ ├── IGCPStorage.cs
│ ├── ManagedCode.Storage.Google.csproj
│ └── Options
│ ├── BucketOptions.cs
│ └── GCPStorageOptions.cs
├── Tests
└── ManagedCode.Storage.Tests
│ ├── AspNetTests
│ ├── Abstracts
│ │ ├── BaseControllerTests.cs
│ │ ├── BaseDownloadControllerTests.cs
│ │ ├── BaseStreamControllerTests.cs
│ │ └── BaseUploadControllerTests.cs
│ └── Azure
│ │ ├── AzureDownloadControllerTests.cs
│ │ ├── AzureStreamControllerTests.cs
│ │ └── AzureUploadControllerTests.cs
│ ├── Common
│ ├── BaseContainer.cs
│ ├── ContainerImages.cs
│ ├── EmptyContainer.cs
│ ├── FileHelper.cs
│ ├── StorageTestApplication.cs
│ └── TestApp
│ │ ├── Controllers
│ │ ├── AzureTestController.cs
│ │ └── Base
│ │ │ └── BaseTestController.cs
│ │ └── HttpHostProgram.cs
│ ├── Constants
│ └── ApiEndpoints.cs
│ ├── ExtensionsTests
│ ├── FormFileExtensionsTests.cs
│ ├── ReplaceExtensionsTests.cs
│ ├── StorageExtensionsTests.cs
│ └── StoragePrivderExtensionsTests.cs
│ ├── ManagedCode.Storage.Tests.csproj
│ └── Storages
│ ├── AWS
│ ├── AWSBlobTests.cs
│ ├── AWSConfigurator.cs
│ ├── AWSContainerTests.cs
│ ├── AWSDownloadTests.cs
│ ├── AWSUploadTests.cs
│ └── AwsConfigTests.cs
│ ├── Abstracts
│ ├── BlobTests.cs
│ ├── ContainerTests.cs
│ ├── DownloadTests.cs
│ ├── StorageClientTests.cs
│ ├── StreamTests.cs
│ └── UploadTests.cs
│ ├── Azure
│ ├── AzureBlobStreamTests.cs
│ ├── AzureBlobTests.cs
│ ├── AzureConfigTests.cs
│ ├── AzureConfigurator.cs
│ ├── AzureContainerTests.cs
│ ├── AzureDataLakeTests.cs
│ ├── AzureDownloadTests.cs
│ └── AzureUploadTests.cs
│ ├── FileSystem
│ ├── FileSystemBlobTests.cs
│ ├── FileSystemConfigurator.cs
│ ├── FileSystemContainerTests.cs
│ ├── FileSystemDownloadTests.cs
│ ├── FileSystemTests.cs
│ └── FileSystemUploadTests.cs
│ └── GCS
│ ├── GCSBlobTests.cs
│ ├── GCSConfigTests.cs
│ ├── GCSConfigurator.cs
│ ├── GCSContainerTests.cs
│ ├── GCSDownloadTests.cs
│ └── GCSUploadTests.cs
└── logo.png
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '35 11 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'csharp' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v4
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v3
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v3
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v3
68 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | pull_request:
8 | types: [opened, synchronize, reopened]
9 |
10 | workflow_dispatch:
11 |
12 | jobs:
13 |
14 | build-and-test:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 |
19 | - name: checkout
20 | uses: actions/checkout@v4
21 |
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: 9.0.x
26 |
27 | - name: Restore dependencies
28 | run: dotnet restore
29 |
30 | - name: Build
31 | run: dotnet build
32 |
33 | - name: Test
34 | run: dotnet test /p:CollectCoverage=true /p:CoverletOutput=coverage /p:CoverletOutputFormat=opencover
35 |
36 | - name: Copy coverage files
37 | run: |
38 | mkdir '${{ github.workspace }}/coverage'
39 | find . -name "*.opencover.xml" -exec sh -c 'cp "$0" "coverage/coverage-$(basename $0)"' {} \;
40 |
41 | - name: List coverage files
42 | run: ls '${{ github.workspace }}/coverage/'
43 |
44 | - name: SonarCloud Scan
45 | uses: sonarsource/sonarcloud-github-action@v2.3.0
46 | if: github.ref == 'refs/heads/main'
47 | with:
48 | args: >
49 | -Dsonar.organization=managedcode
50 | -Dsonar.projectKey=managedcode_Storage
51 | -Dsonar.token=${{ secrets.SONAR_TOKEN }}
52 | -Dsonar.cs.opencover.reportsPaths=${{ github.workspace }}/coverage/
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
56 |
57 | # - name: NDepend
58 | # uses: ndepend/ndepend-action@v1
59 | # with:
60 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | # license: ${{ secrets.NDEPENDLICENSE }}
62 | # coveragefolder: ${{ github.workspace }}/coverage/
63 | # baseline: main_recent
64 | # retention-days: 15
65 | # #stopIfQGFailed: true
66 |
67 | - name: Upload coverage reports to Codecov
68 | uses: codecov/codecov-action@v3
69 | env:
70 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
71 |
--------------------------------------------------------------------------------
/.github/workflows/nuget.yml:
--------------------------------------------------------------------------------
1 | name: nuget
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | workflow_dispatch:
8 |
9 | jobs:
10 | nuget-pack:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v4
16 |
17 | - name: Setup .NET
18 | uses: actions/setup-dotnet@v4
19 | with:
20 | dotnet-version: '9.0.x'
21 |
22 | - name: Restore dependencies
23 | run: dotnet restore
24 |
25 | - name: Build
26 | run: dotnet build --configuration Release
27 |
28 | - name: Test
29 | run: dotnet test --configuration Release
30 |
31 | - name: NDepend
32 | uses: ndepend/ndepend-action@v1
33 | with:
34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 | license: ${{ secrets.NDEPENDLICENSE }}
36 | coveragefolder: ${{ github.workspace }}/coverage/
37 | baseline: main_recent
38 | retention-days: 15
39 | #stopIfQGFailed: true
40 |
41 |
42 | - name: Pack
43 | run: dotnet pack --configuration Release -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg -o "packages"
44 |
45 | - name: Push
46 | run: dotnet nuget push "packages/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
47 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net9.0
5 | 13
6 | true
7 | embedded
8 | enable
9 |
10 |
11 |
12 |
13 | ManagedCode
14 | Copyright © 2021-$([System.DateTime]::Now.ToString(`yyyy`)) ManagedCode SAS
15 | true
16 | true
17 | true
18 | snupkg
19 | Github
20 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
21 | logo.png
22 | MIT
23 | true
24 | README.md
25 |
26 | https://github.com/managedcode/Storage
27 | https://github.com/managedcode/Storage
28 | Managed Code - Storage
29 | 9.0.5
30 | 9.0.5
31 |
32 |
33 |
34 |
35 | true
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | all
44 | runtime; build; native; contentfiles; analyzers; buildtransitive
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Client.SignalR/Class1.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Client.SignalR;
2 |
3 | public class Class1
4 | {
5 | }
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Client.SignalR/ManagedCode.Storage.Client.SignalR.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Library
6 |
7 |
8 |
9 |
10 | ManagedCode.Storage.Client.SignalR
11 | MManagedCode.Storage.Client.SignalR
12 | Extensions for ASP.NET for Storage
13 | managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Client/IStorageClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using ManagedCode.Communication;
6 | using ManagedCode.Storage.Core.Models;
7 |
8 | namespace ManagedCode.Storage.Client;
9 |
10 | public interface IStorageClient
11 | {
12 | void SetChunkSize(long size);
13 |
14 | event EventHandler OnProgressStatusChanged;
15 |
16 | Task> UploadFile(string base64, string apiUrl, string contentName, CancellationToken cancellationToken = default);
17 |
18 | Task> UploadFile(byte[] bytes, string apiUrl, string contentName, CancellationToken cancellationToken = default);
19 |
20 | Task> UploadFile(FileInfo fileInfo, string apiUrl, string contentName, CancellationToken cancellationToken = default);
21 |
22 | Task> UploadFile(Stream stream, string apiUrl, string contentName, CancellationToken cancellationToken = default);
23 |
24 | Task> DownloadFile(string fileName, string apiUrl, string? path = null, CancellationToken cancellationToken = default);
25 |
26 | Task> UploadLargeFile(Stream file, string uploadApiUrl, string completeApiUrl, Action? onProgressChanged,
27 | CancellationToken cancellationToken = default);
28 |
29 | Task> GetFileStream(string fileName, string apiUrl, CancellationToken cancellationToken = default);
30 | }
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Client/ManagedCode.Storage.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Library
6 |
7 |
8 |
9 |
10 | ManagedCode.Storage.Client
11 | ManagedCode.Storage.Client
12 | Extensions for ASP.NET for Storage
13 | managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Client/ProgressStatus.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ManagedCode.Storage.Client;
4 |
5 | public record ProgressStatus(
6 | string File,
7 | float Progress,
8 | long TotalBytes,
9 | long TransferredBytes,
10 | TimeSpan Elapsed,
11 | TimeSpan Remaining,
12 | string Speed,
13 | string? Error = null);
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/Controller/ControllerDownloadExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.MimeTypes;
5 | using ManagedCode.Storage.Core;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 | using IResult = Microsoft.AspNetCore.Http.IResult;
9 |
10 | namespace ManagedCode.Storage.Server.Extensions.Controller;
11 |
12 | public static class ControllerDownloadExtensions
13 | {
14 | public static async Task DownloadAsStreamAsync(
15 | this ControllerBase controller,
16 | IStorage storage,
17 | string blobName,
18 | bool enableRangeProcessing = true,
19 | CancellationToken cancellationToken = default)
20 | {
21 | var result = await storage.GetStreamAsync(blobName, cancellationToken);
22 | if (result.IsFailed)
23 | throw new FileNotFoundException(blobName);
24 |
25 | return Results.Stream(result.Value, MimeHelper.GetMimeType(blobName), blobName, enableRangeProcessing: enableRangeProcessing);
26 | }
27 |
28 | public static async Task DownloadAsFileResultAsync(
29 | this ControllerBase controller,
30 | IStorage storage,
31 | string blobName,
32 | bool enableRangeProcessing = true,
33 | CancellationToken cancellationToken = default)
34 | {
35 | var result = await storage.GetStreamAsync(blobName, cancellationToken);
36 | if (result.IsFailed)
37 | throw new FileNotFoundException(blobName);
38 |
39 | return new FileStreamResult(result.Value, MimeHelper.GetMimeType(blobName))
40 | {
41 | FileDownloadName = blobName,
42 | EnableRangeProcessing = enableRangeProcessing
43 | };
44 | }
45 |
46 | public static async Task DownloadAsFileContentResultAsync(
47 | this ControllerBase controller,
48 | IStorage storage,
49 | string blobName,
50 | CancellationToken cancellationToken = default)
51 | {
52 | var result = await storage.DownloadAsync(blobName, cancellationToken);
53 | if (result.IsFailed)
54 | throw new FileNotFoundException(blobName);
55 |
56 | using var memoryStream = new MemoryStream();
57 | await result.Value.FileStream.CopyToAsync(memoryStream, cancellationToken);
58 | return new FileContentResult(memoryStream.ToArray(), MimeHelper.GetMimeType(blobName))
59 | {
60 | FileDownloadName = blobName
61 | };
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/Controller/ControllerUploadExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using ManagedCode.Storage.Core;
6 | using ManagedCode.Storage.Core.Models;
7 | using ManagedCode.Storage.Server.Extensions.File;
8 | using ManagedCode.Storage.Server.Helpers;
9 | using Microsoft.AspNetCore.Components.Forms;
10 | using Microsoft.AspNetCore.Http;
11 | using Microsoft.AspNetCore.Mvc;
12 | using Microsoft.AspNetCore.WebUtilities;
13 | using Microsoft.Net.Http.Headers;
14 |
15 | namespace ManagedCode.Storage.Server.Extensions.Controller;
16 |
17 | public static class ControllerUploadExtensions
18 | {
19 | private const int DefaultMultipartBoundaryLengthLimit = 70;
20 | private const int MinLengthForLargeFile = 256 * 1024;
21 |
22 | public static async Task UploadFormFileAsync(
23 | this ControllerBase controller,
24 | IStorage storage,
25 | IFormFile file,
26 | UploadOptions? options = null,
27 | CancellationToken cancellationToken = default)
28 | {
29 | options ??= new UploadOptions(file.FileName, mimeType: file.ContentType);
30 |
31 | if (file.Length > MinLengthForLargeFile)
32 | {
33 | var localFile = await file.ToLocalFileAsync(cancellationToken);
34 | var result = await storage.UploadAsync(localFile.FileInfo, options, cancellationToken);
35 | result.ThrowIfFail();
36 | return result.Value;
37 | }
38 | else
39 | {
40 | await using var stream = file.OpenReadStream();
41 | var result = await storage.UploadAsync(stream, options, cancellationToken);
42 | result.ThrowIfFail();
43 | return result.Value;
44 | }
45 | }
46 |
47 | public static async Task UploadFromBrowserFileAsync(
48 | this ControllerBase controller,
49 | IStorage storage,
50 | IBrowserFile file,
51 | UploadOptions? options = null,
52 | CancellationToken cancellationToken = default)
53 | {
54 | options ??= new UploadOptions(file.Name, mimeType: file.ContentType);
55 |
56 | if (file.Size > MinLengthForLargeFile)
57 | {
58 | var localFile = await file.ToLocalFileAsync(cancellationToken);
59 | var result = await storage.UploadAsync(localFile.FileInfo, options, cancellationToken);
60 | result.ThrowIfFail();
61 | return result.Value;
62 | }
63 | else
64 | {
65 | await using var stream = file.OpenReadStream();
66 | var result = await storage.UploadAsync(stream, options, cancellationToken);
67 | result.ThrowIfFail();
68 | return result.Value;
69 | }
70 | }
71 |
72 | public static async Task UploadFromStreamAsync(
73 | this ControllerBase controller,
74 | IStorage storage,
75 | HttpRequest request,
76 | UploadOptions? options = null,
77 | CancellationToken cancellationToken = default)
78 | {
79 | if (!StreamHelper.IsMultipartContentType(request.ContentType))
80 | {
81 | throw new InvalidOperationException("Not a multipart request");
82 | }
83 |
84 | var boundary = StreamHelper.GetBoundary(
85 | MediaTypeHeaderValue.Parse(request.ContentType),
86 | DefaultMultipartBoundaryLengthLimit);
87 |
88 | var multipartReader = new MultipartReader(boundary, request.Body);
89 | var section = await multipartReader.ReadNextSectionAsync(cancellationToken);
90 |
91 | while (section != null)
92 | {
93 | if (ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition)
94 | && StreamHelper.HasFileContentDisposition(contentDisposition))
95 | {
96 | var fileName = contentDisposition.FileName.Value;
97 | var contentType = section.ContentType;
98 |
99 | options ??= new UploadOptions(fileName, mimeType: contentType);
100 |
101 | using var memoryStream = new MemoryStream();
102 | await section.Body.CopyToAsync(memoryStream, cancellationToken);
103 | memoryStream.Position = 0;
104 |
105 | var result = await storage.UploadAsync(memoryStream, options, cancellationToken);
106 | result.ThrowIfFail();
107 | return result.Value;
108 | }
109 |
110 | section = await multipartReader.ReadNextSectionAsync(cancellationToken);
111 | }
112 |
113 | throw new InvalidOperationException("No file found in request");
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/DependencyInjection/StorageServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 |
3 | namespace ManagedCode.Storage.Server.Extensions.DependencyInjection;
4 |
5 | public static class StorageServiceCollectionExtensions
6 | {
7 | public static IServiceCollection AddStorageSetupService(this IServiceCollection services)
8 | {
9 | return services.AddHostedService();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/File/BrowserFileExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using ManagedCode.Storage.Core.Models;
4 | using Microsoft.AspNetCore.Components.Forms;
5 |
6 | namespace ManagedCode.Storage.Server.Extensions.File;
7 |
8 | public static class BrowserFileExtensions
9 | {
10 | public static async Task ToLocalFileAsync(this IBrowserFile formFile, CancellationToken cancellationToken = default)
11 | {
12 | var localFile = LocalFile.FromRandomNameWithExtension(formFile.Name);
13 | await localFile.CopyFromStreamAsync(formFile.OpenReadStream(), cancellationToken);
14 | return localFile;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/File/FormFileExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using ManagedCode.Storage.Core.Models;
6 | using Microsoft.AspNetCore.Http;
7 |
8 | namespace ManagedCode.Storage.Server.Extensions.File;
9 |
10 | public static class FormFileExtensions
11 | {
12 | public static async Task ToLocalFileAsync(this IFormFile formFile, CancellationToken cancellationToken = default)
13 | {
14 | var localFile = LocalFile.FromRandomNameWithExtension(formFile.FileName);
15 | await localFile.CopyFromStreamAsync(formFile.OpenReadStream(), cancellationToken);
16 | return localFile;
17 | }
18 |
19 | public static async IAsyncEnumerable ToLocalFilesAsync(this IFormFileCollection formFileCollection,
20 | [EnumeratorCancellation] CancellationToken cancellationToken = default)
21 | {
22 | foreach (var formFile in formFileCollection)
23 | yield return await formFile.ToLocalFileAsync(cancellationToken);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/Storage/StorageBrowserFileExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Communication;
5 | using ManagedCode.Storage.Core;
6 | using ManagedCode.Storage.Core.Models;
7 | using ManagedCode.Storage.Server.Extensions.File;
8 | using Microsoft.AspNetCore.Components.Forms;
9 |
10 | namespace ManagedCode.Storage.Server.Extensions.Storage;
11 |
12 | public static class StorageBrowserFileExtensions
13 | {
14 | private const int MinLengthForLargeFile = 256 * 1024;
15 |
16 | public static async Task> UploadToStorageAsync(this IStorage storage, IBrowserFile formFile, UploadOptions? options = null,
17 | CancellationToken cancellationToken = default)
18 | {
19 | options ??= new UploadOptions(formFile.Name, mimeType: formFile.ContentType);
20 |
21 | if (formFile.Size > MinLengthForLargeFile)
22 | {
23 | var localFile = await formFile.ToLocalFileAsync(cancellationToken);
24 | return await storage.UploadAsync(localFile.FileInfo, options, cancellationToken);
25 | }
26 |
27 | await using (var stream = formFile.OpenReadStream())
28 | {
29 | return await storage.UploadAsync(stream, options, cancellationToken);
30 | }
31 | }
32 |
33 | public static async Task> UploadToStorageAsync(this IStorage storage, IBrowserFile formFile, Action options,
34 | CancellationToken cancellationToken = default)
35 | {
36 | var newOptions = new UploadOptions(formFile.Name, mimeType: formFile.ContentType);
37 | options.Invoke(newOptions);
38 |
39 | if (formFile.Size > MinLengthForLargeFile)
40 | {
41 | var localFile = await formFile.ToLocalFileAsync(cancellationToken);
42 | return await storage.UploadAsync(localFile.FileInfo, newOptions, cancellationToken);
43 | }
44 |
45 | await using (var stream = formFile.OpenReadStream())
46 | {
47 | return await storage.UploadAsync(stream, newOptions, cancellationToken);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/Storage/StorageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using ManagedCode.Communication;
4 | using ManagedCode.MimeTypes;
5 | using ManagedCode.Storage.Core;
6 | using ManagedCode.Storage.Core.Models;
7 | using Microsoft.AspNetCore.Mvc;
8 |
9 | namespace ManagedCode.Storage.Server.Extensions.Storage;
10 |
11 | public static class StorageExtensions
12 | {
13 | public static async Task> DownloadAsFileResult(this IStorage storage, string blobName,
14 | CancellationToken cancellationToken = default)
15 | {
16 | var result = await storage.DownloadAsync(blobName, cancellationToken);
17 |
18 | if (result.IsFailed)
19 | return Result.Fail(result.Errors);
20 |
21 | var fileStream = new FileStreamResult(result.Value!.FileStream, MimeHelper.GetMimeType(result.Value.FileInfo.Extension))
22 | {
23 | FileDownloadName = result.Value.Name
24 | };
25 |
26 | return Result.Succeed(fileStream);
27 | }
28 |
29 | public static async Task> DownloadAsFileResult(this IStorage storage, BlobMetadata blobMetadata,
30 | CancellationToken cancellationToken = default)
31 | {
32 | var result = await storage.DownloadAsync(blobMetadata.Name, cancellationToken);
33 |
34 | if (result.IsFailed)
35 | return Result.Fail(result.Errors);
36 |
37 | var fileStream = new FileStreamResult(result.Value!.FileStream, MimeHelper.GetMimeType(result.Value.FileInfo.Extension))
38 | {
39 | FileDownloadName = result.Value.Name
40 | };
41 |
42 | return Result.Succeed(fileStream);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Extensions/Storage/StorageFromFileExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using ManagedCode.Communication;
7 | using ManagedCode.Storage.Core;
8 | using ManagedCode.Storage.Core.Models;
9 | using ManagedCode.Storage.Server.Extensions.File;
10 | using Microsoft.AspNetCore.Http;
11 |
12 | namespace ManagedCode.Storage.Server.Extensions.Storage;
13 |
14 | public static class StorageFromFileExtensions
15 | {
16 | public static async Task> UploadToStorageAsync(this IStorage storage, IFormFile formFile, UploadOptions? options = null,
17 | CancellationToken cancellationToken = default)
18 | {
19 | options ??= new UploadOptions(formFile.FileName, mimeType: formFile.ContentType);
20 |
21 | await using var stream = formFile.OpenReadStream();
22 | return await storage.UploadAsync(stream, options, cancellationToken);
23 | }
24 |
25 | public static async Task> UploadToStorageAsync(this IStorage storage, IFormFile formFile, Action options,
26 | CancellationToken cancellationToken = default)
27 | {
28 | var newOptions = new UploadOptions(formFile.FileName, mimeType: formFile.ContentType);
29 | options.Invoke(newOptions);
30 |
31 | await using var stream = formFile.OpenReadStream();
32 | return await storage.UploadAsync(stream, newOptions, cancellationToken);
33 | }
34 |
35 | public static async IAsyncEnumerable> UploadToStorageAsync(this IStorage storage, IFormFileCollection formFiles,
36 | UploadOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
37 | {
38 | foreach (var formFile in formFiles)
39 | yield return await storage.UploadToStorageAsync(formFile, options, cancellationToken);
40 | }
41 |
42 | public static async IAsyncEnumerable> UploadToStorageAsync(this IStorage storage, IFormFileCollection formFiles,
43 | Action options, [EnumeratorCancellation] CancellationToken cancellationToken = default)
44 | {
45 | foreach (var formFile in formFiles)
46 | yield return await storage.UploadToStorageAsync(formFile, options, cancellationToken);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Helpers/StreamHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Microsoft.Net.Http.Headers;
4 |
5 | namespace ManagedCode.Storage.Server.Helpers;
6 |
7 | internal static class StreamHelper
8 | {
9 | public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
10 | {
11 | var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
12 |
13 | if (string.IsNullOrWhiteSpace(boundary))
14 | {
15 | throw new InvalidDataException("Missing content-type boundary.");
16 | }
17 |
18 | if (boundary.Length > lengthLimit)
19 | {
20 | throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
21 | }
22 |
23 | return boundary;
24 | }
25 |
26 | public static bool IsMultipartContentType(string? contentType)
27 | {
28 | return !string.IsNullOrEmpty(contentType)
29 | && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
30 | }
31 |
32 | public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
33 | {
34 | return contentDisposition != null
35 | && contentDisposition.DispositionType.Equals("form-data")
36 | && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
37 | || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/ManagedCode.Storage.Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Library
6 |
7 |
8 |
9 |
10 | ManagedCode.Storage.Server
11 | ManagedCode.Storage.Server
12 | Extensions for ASP.NET for Storage
13 | managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | all
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Models/FilePayload.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Server.Models;
2 |
3 | public class FilePayload
4 | {
5 | public int ChunkIndex { get; set; }
6 | public int ChunkSize { get; set; }
7 | }
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Models/FileUploadPayload.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 |
3 | namespace ManagedCode.Storage.Server.Models;
4 |
5 | public class FileUploadPayload
6 | {
7 | public IFormFile File { get; set; }
8 | public FilePayload Payload { get; set; }
9 | }
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ManagedCode.Storage.AspNetExtensions": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:59086;http://localhost:59087"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/Integraions/ManagedCode.Storage.Server/StorageSetupBackgroundService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Storage.Core;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace ManagedCode.Storage.Server;
8 |
9 | public class StorageSetupBackgroundService(IEnumerable storages) : BackgroundService
10 | {
11 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
12 | {
13 | foreach (var storage in storages)
14 | await storage.CreateContainerAsync(stoppingToken);
15 | }
16 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Managed-Code
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Exceptions/BadConfigurationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ManagedCode.Storage.Core.Exceptions;
4 |
5 | public class BadConfigurationException : Exception
6 | {
7 | public BadConfigurationException()
8 | {
9 | }
10 |
11 | public BadConfigurationException(string message) : base(message)
12 | {
13 | }
14 |
15 | public BadConfigurationException(string message, Exception inner) : base(message, inner)
16 | {
17 | }
18 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Core.Providers;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.DependencyInjection.Extensions;
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();
12 | return serviceCollection;
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Extensions/StorageOptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | // using System;
2 | // using System.Reflection;
3 | // using System.Text.Json;
4 | //
5 | // namespace ManagedCode.Storage.Core.Extensions;
6 | //
7 | // public static class StorageOptionsExtensions
8 | // {
9 | // public static T DeepCopy(this T? source) where T : class, IStorageOptions
10 | // {
11 | // if (source == null)
12 | // return default;
13 | //
14 | // var options = new JsonSerializerOptions
15 | // {
16 | // WriteIndented = false,
17 | // PropertyNameCaseInsensitive = true
18 | // };
19 | //
20 | // var json = JsonSerializer.Serialize(source, source.GetType(), options);
21 | // return (T)JsonSerializer.Deserialize(json, source.GetType(), options)!;
22 | // }
23 | // }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Helpers/Crc32Helper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace ManagedCode.Storage.Core.Helpers;
5 |
6 | public static class Crc32Helper
7 | {
8 | private const uint Polynomial = 0xedb88320;
9 | private static readonly uint[] Crc32Table;
10 |
11 | static Crc32Helper()
12 | {
13 | Crc32Table = new uint[256];
14 |
15 | for (var i = 0; i < 256; i++)
16 | {
17 | var crc = (uint)i;
18 | for (var j = 8; j > 0; j--)
19 | if ((crc & 1) == 1)
20 | crc = (crc >> 1) ^ Polynomial;
21 | else
22 | crc >>= 1;
23 |
24 | Crc32Table[i] = crc;
25 | }
26 | }
27 |
28 | public static uint Calculate(byte[] bytes)
29 | {
30 | var crcValue = 0xffffffff;
31 |
32 | foreach (var by in bytes)
33 | {
34 | var tableIndex = (byte)((crcValue & 0xff) ^ by);
35 | crcValue = Crc32Table[tableIndex] ^ (crcValue >> 8);
36 | }
37 |
38 | return ~crcValue;
39 | }
40 |
41 | public static uint CalculateFileCrc(string filePath)
42 | {
43 | var crcValue = 0xffffffff;
44 |
45 | using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
46 | {
47 | var buffer = new byte[4096]; // 4KB buffer
48 | while (fs.Read(buffer, 0, buffer.Length) > 0)
49 | crcValue = Calculate(buffer, crcValue);
50 | }
51 |
52 | return ~crcValue; // Return the final CRC value
53 | }
54 |
55 | private static uint Calculate(byte[] bytes, uint crcValue = 0xffffffff)
56 | {
57 | foreach (var by in bytes)
58 | {
59 | var tableIndex = (byte)((crcValue & 0xff) ^ by);
60 | crcValue = Crc32Table[tableIndex] ^ (crcValue >> 8);
61 | }
62 |
63 | return crcValue;
64 | }
65 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/IDownloader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Communication;
5 | using ManagedCode.Storage.Core.Models;
6 |
7 | namespace ManagedCode.Storage.Core
8 | {
9 | ///
10 | /// Represents a downloader interface for downloading files.
11 | ///
12 | public interface IDownloader
13 | {
14 | ///
15 | /// Downloads a file asynchronously.
16 | ///
17 | /// The name of the file to download.
18 | /// A cancellation token that can be used to cancel the operation.
19 | /// A task that represents the asynchronous operation. The task result contains the downloaded file.
20 | Task> DownloadAsync(string fileName, CancellationToken cancellationToken = default);
21 |
22 | ///
23 | /// Downloads a file asynchronously using blob metadata.
24 | ///
25 | /// The metadata of the blob to be downloaded, containing information such as file name, size, and location.
26 | /// A cancellation token that can be used to cancel the download operation. The default value is default(CancellationToken).
27 | /// A task that represents the asynchronous operation. The task result contains a Result indicating success or failure, and the downloaded file data.
28 | ///
29 | /// The method uses the provided blob metadata to locate and download the file from storage.
30 | /// The downloaded file is returned as a LocalFile object wrapped in a Result type for error handling.
31 | ///
32 | Task> DownloadAsync(BlobMetadata metadata, CancellationToken cancellationToken = default);
33 |
34 | ///
35 | /// Downloads a file asynchronously with the specified download options.
36 | ///
37 | /// The options to use when downloading the file.
38 | /// A cancellation token that can be used to cancel the operation.
39 | /// A task that represents the asynchronous operation. The task result contains the downloaded file.
40 | Task> DownloadAsync(DownloadOptions options, CancellationToken cancellationToken = default);
41 |
42 | ///
43 | /// Downloads a file asynchronously with the specified action to configure the download options.
44 | ///
45 | /// An action to configure the download options.
46 | /// A cancellation token that can be used to cancel the operation.
47 | /// A task that represents the asynchronous operation. The task result contains the downloaded file.
48 | Task> DownloadAsync(Action action, CancellationToken cancellationToken = default);
49 | }
50 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/IStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Communication;
5 |
6 | namespace ManagedCode.Storage.Core
7 | {
8 | ///
9 | /// Represents a generic storage interface with a specific client and options.
10 | ///
11 | /// The type of the storage client.
12 | /// The type of the storage options.
13 | public interface IStorage : IStorage where TOptions : IStorageOptions
14 | {
15 | ///
16 | /// Gets the storage client.
17 | ///
18 | T StorageClient { get; }
19 |
20 | ///
21 | /// Sets the storage options asynchronously.
22 | ///
23 | /// The options to set.
24 | /// A cancellation token that can be used to cancel the operation.
25 | /// A task that represents the asynchronous operation. The task result contains the result of the operation.
26 | Task SetStorageOptions(TOptions options, CancellationToken cancellationToken = default);
27 |
28 | ///
29 | /// Sets the storage options asynchronously with the specified action to configure the options.
30 | ///
31 | /// An action to configure the options.
32 | /// A cancellation token that can be used to cancel the operation.
33 | /// A task that represents the asynchronous operation. The task result contains the result of the operation.
34 | Task SetStorageOptions(Action options, CancellationToken cancellationToken = default);
35 | }
36 |
37 | ///
38 | /// Represents a storage interface that includes uploader, downloader, streamer, and storage operations.
39 | ///
40 | public interface IStorage : IUploader, IDownloader, IStreamer, IStorageOperations, IDisposable
41 | {
42 | ///
43 | /// Creates a container asynchronously if it does not already exist.
44 | ///
45 | /// A cancellation token that can be used to cancel the operation.
46 | /// A task that represents the asynchronous operation. The task result contains the result of the operation.
47 | Task CreateContainerAsync(CancellationToken cancellationToken = default);
48 |
49 | ///
50 | /// Removes a container asynchronously if it exists.
51 | ///
52 | /// A cancellation token that can be used to cancel the operation.
53 | /// A task that represents the asynchronous operation. The task result contains the result of the operation.
54 | Task RemoveContainerAsync(CancellationToken cancellationToken = default);
55 | }
56 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/IStorageOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core;
2 |
3 | ///
4 | /// Provides the options for storage operations.
5 | ///
6 | public interface IStorageOptions
7 | {
8 | ///
9 | /// Gets or sets a value indicating whether to create the container if it does not exist.
10 | ///
11 | ///
12 | /// true if the container should be created if it does not exist; otherwise, false.
13 | ///
14 | public bool CreateContainerIfNotExists { get; set; }
15 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/IStreamer.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Communication;
5 |
6 | namespace ManagedCode.Storage.Core;
7 |
8 | public interface IStreamer
9 | {
10 | ///
11 | /// Gets file stream.
12 | ///
13 | Task> GetStreamAsync(string fileName, CancellationToken cancellationToken = default);
14 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/LoggerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace ManagedCode.Storage.Core;
6 |
7 | public static class LoggerExtensions
8 | {
9 | public static void LogException(this ILogger? logger, Exception ex, [CallerMemberName] string methodName = default!)
10 | {
11 | logger?.LogError(ex, methodName);
12 | }
13 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/ManagedCode.Storage.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.Core
10 | ManagedCode.Storage.Core
11 | Base interfaces for ManagedCode.StorageS
12 | managedcode, aws, azure, gcp, storage, cloud, blob
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/BaseOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ManagedCode.Storage.Core.Models;
4 |
5 | public abstract class BaseOptions
6 | {
7 | public string FileName { get; set; } = string.Empty;
8 | public string? Directory { get; set; }
9 |
10 | // TODO: Check this
11 | public string FullPath => string.IsNullOrWhiteSpace(Directory) ? FileName : $"{Directory}/{FileName}";
12 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/BlobMetadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace ManagedCode.Storage.Core.Models;
5 |
6 | public class BlobMetadata
7 | {
8 | public string FullName { get; set; } = null!;
9 | public string Name { get; set; } = null!;
10 | public Uri? Uri { get; set; }
11 | public Dictionary? Metadata { get; set; }
12 | public DateTimeOffset LastModified { get; set; }
13 | public DateTimeOffset CreatedOn { get; set; }
14 | public string? Container { get; set; }
15 | public string? MimeType { get; set; }
16 | public ulong Length { get; set; }
17 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/DeleteOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core.Models;
2 |
3 | public class DeleteOptions : BaseOptions
4 | {
5 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/DownloadOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core.Models;
2 |
3 | public class DownloadOptions : BaseOptions
4 | {
5 | public string? LocalPath { get; set; }
6 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/ExistOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core.Models;
2 |
3 | public class ExistOptions : BaseOptions
4 | {
5 | public static ExistOptions FromBaseOptions(BaseOptions options)
6 | {
7 | return new ExistOptions { FileName = options.FileName, Directory = options.Directory };
8 | }
9 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/LegalHoldOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core.Models;
2 |
3 | public class LegalHoldOptions : BaseOptions
4 | {
5 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/MetadataOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Core.Models;
2 |
3 | public class MetadataOptions : BaseOptions
4 | {
5 | public static MetadataOptions FromBaseOptions(BaseOptions options)
6 | {
7 | return new MetadataOptions { FileName = options.FileName, Directory = options.Directory };
8 | }
9 |
10 | public string ETag { get; set; } = string.Empty;
11 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Models/UploadOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ManagedCode.Storage.Core.Models;
4 |
5 | public class UploadOptions : BaseOptions
6 | {
7 | public UploadOptions()
8 | {
9 | }
10 |
11 | public UploadOptions(string? fileName = null, string? directory = null, string? mimeType = null, Dictionary? metadata = null,
12 | string? fileNamePrefix = null)
13 | {
14 | FileName = fileName ?? FileName;
15 | MimeType = mimeType;
16 | Directory = directory;
17 | Metadata = metadata;
18 | FileNamePrefix = fileNamePrefix;
19 | }
20 |
21 | public string? FileNamePrefix { get; set; }
22 | public string? MimeType { get; set; }
23 | public Dictionary? Metadata { get; set; }
24 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Providers/IStorageFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ManagedCode.Storage.Core.Providers
4 | {
5 | public interface IStorageFactory
6 | {
7 | IStorage CreateStorage(IStorageOptions options);
8 | IStorage CreateStorage(Action options);
9 |
10 | TStorage CreateStorage(TOptions options)
11 | where TStorage : class, IStorage
12 | where TOptions : class, IStorageOptions;
13 |
14 | TStorage CreateStorage(Action options)
15 | where TStorage : class, IStorage
16 | where TOptions : class, IStorageOptions;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Providers/IStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ManagedCode.Storage.Core.Providers
4 | {
5 | public interface IStorageProvider
6 | {
7 | Type StorageOptionsType { get; }
8 | TStorage CreateStorage(TOptions options)
9 | where TStorage : class, IStorage
10 | where TOptions : class, IStorageOptions;
11 |
12 |
13 | IStorageOptions GetDefaultOptions();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/Providers/StorageFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace ManagedCode.Storage.Core.Providers
6 | {
7 | public class StorageFactory : IStorageFactory
8 | {
9 | public StorageFactory(IEnumerable providers)
10 | {
11 | Providers = providers.ToDictionary(p => p.StorageOptionsType, p => p);
12 | }
13 |
14 | private IStorageProvider? GetProvider(Type optionsType)
15 | {
16 | return Providers
17 | .FirstOrDefault(x => x.Key.IsAssignableFrom(optionsType))
18 | .Value;
19 | }
20 |
21 | public Dictionary Providers { get; set; }
22 |
23 | public IStorage CreateStorage(IStorageOptions options)
24 | {
25 | var provider = GetProvider(options.GetType())
26 | ?? throw new NotSupportedException($"Provider for {options.GetType()} not found");
27 |
28 | return provider.CreateStorage(options);
29 | }
30 |
31 | public IStorage CreateStorage(Action options)
32 | {
33 | var provider = GetProvider(options.GetType())
34 | ?? throw new NotSupportedException($"Provider for {options.GetType()} not found");
35 |
36 | var storageOptions = provider.GetDefaultOptions();
37 | options.Invoke(storageOptions);
38 | return CreateStorage(storageOptions);
39 | }
40 |
41 | public TStorage CreateStorage(TOptions options)
42 | where TStorage : class, IStorage
43 | where TOptions : class, IStorageOptions
44 | {
45 | var provider = GetProvider(typeof(TOptions))
46 | ?? throw new NotSupportedException($"Provider for {typeof(TOptions)} not found");
47 |
48 | return provider.CreateStorage(options);
49 | }
50 |
51 | public TStorage CreateStorage(Action options)
52 | where TStorage : class, IStorage
53 | where TOptions : class, IStorageOptions
54 | {
55 | var provider = GetProvider(typeof(TOptions))
56 | ?? throw new NotSupportedException($"Provider for {typeof(TOptions)} not found");
57 |
58 | TOptions storageOptions = (TOptions)provider.GetDefaultOptions();
59 | options.Invoke(storageOptions);
60 | return provider.CreateStorage(storageOptions);
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.Core/StringStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace ManagedCode.Storage.Core
5 | {
6 | internal class StringStream(string str) : Stream
7 | {
8 | private readonly string _string = str ?? throw new ArgumentNullException(nameof(str));
9 |
10 | public override bool CanRead => true;
11 | public override bool CanSeek => true;
12 | public override bool CanWrite => false;
13 | public override long Length => _string.Length * 2;
14 | public override long Position { get; set; }
15 |
16 | private byte this[int i] => (byte)(_string[i / 2] >> ((i & 1) * 8));
17 |
18 | public override long Seek(long offset, SeekOrigin origin)
19 | {
20 | Position = origin switch
21 | {
22 | SeekOrigin.Begin => offset,
23 | SeekOrigin.Current => Position + offset,
24 | SeekOrigin.End => Length - offset,
25 | _ => throw new ArgumentOutOfRangeException(nameof(origin), origin, null)
26 | };
27 |
28 | return Position;
29 | }
30 |
31 | public override int Read(byte[] buffer, int offset, int count)
32 | {
33 | var length = Math.Min(count, Length - Position);
34 | for (var i = 0; i < length; i++)
35 | buffer[offset + i] = this[(int)Position++];
36 |
37 | return (int)length;
38 | }
39 |
40 | public override int ReadByte() => Position < Length ? this[(int)Position++] : -1;
41 |
42 | public override void Flush() { }
43 |
44 | public override void SetLength(long value) => throw new NotSupportedException();
45 |
46 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
47 |
48 | public override string ToString() => _string;
49 | }
50 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/FakeAWSStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Amazon.S3;
5 | using ManagedCode.Communication;
6 | using ManagedCode.Storage.Aws;
7 | using ManagedCode.Storage.Aws.Options;
8 | using ManagedCode.Storage.FileSystem;
9 | using ManagedCode.Storage.FileSystem.Options;
10 |
11 | namespace ManagedCode.Storage.TestFakes;
12 |
13 | public class FakeAWSStorage : FileSystemStorage, IAWSStorage
14 | {
15 | public FakeAWSStorage() : base(new FileSystemStorageOptions())
16 | {
17 | }
18 |
19 | public IAmazonS3 StorageClient { get; }
20 |
21 | public Task SetStorageOptions(AWSStorageOptions options, CancellationToken cancellationToken = default)
22 | {
23 | return Task.FromResult(Result.Succeed());
24 | }
25 |
26 | public Task SetStorageOptions(Action options, CancellationToken cancellationToken = default)
27 | {
28 | return Task.FromResult(Result.Succeed());
29 | }
30 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/FakeAzureDataLakeStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Azure.Storage.Files.DataLake;
6 | using ManagedCode.Communication;
7 | using ManagedCode.Storage.Azure.DataLake;
8 | using ManagedCode.Storage.Azure.DataLake.Options;
9 | using ManagedCode.Storage.FileSystem;
10 | using ManagedCode.Storage.FileSystem.Options;
11 |
12 | namespace ManagedCode.Storage.TestFakes;
13 |
14 | public class FakeAzureDataLakeStorage : FileSystemStorage, IAzureDataLakeStorage
15 | {
16 | public FakeAzureDataLakeStorage() : base(new FileSystemStorageOptions())
17 | {
18 | }
19 |
20 | public DataLakeFileSystemClient StorageClient { get; }
21 |
22 | public Task SetStorageOptions(AzureDataLakeStorageOptions options, CancellationToken cancellationToken = default)
23 | {
24 | return Task.FromResult(Result.Succeed());
25 | }
26 |
27 | public Task SetStorageOptions(Action options, CancellationToken cancellationToken = default)
28 | {
29 | return Task.FromResult(Result.Succeed());
30 | }
31 |
32 | public Task CreateDirectoryAsync(string directory, CancellationToken cancellationToken = default)
33 | {
34 | if (Directory.Exists(directory))
35 | Task.FromResult(Result.Fail());
36 |
37 | try
38 | {
39 | Directory.CreateDirectory(directory);
40 | return Task.FromResult(Result.Succeed());
41 | }
42 | catch (Exception e)
43 | {
44 | return Task.FromResult(Result.Fail(e));
45 | }
46 | }
47 |
48 | public Task RenameDirectory(string directory, string newDirectory, CancellationToken cancellationToken = default)
49 | {
50 | if (!Directory.Exists(directory))
51 | Task.FromResult(Result.Fail());
52 |
53 | try
54 | {
55 | Directory.Move(directory, newDirectory);
56 | return Task.FromResult(Result.Succeed());
57 | }
58 | catch (Exception e)
59 | {
60 | return Task.FromResult(Result.Fail(e));
61 | }
62 | }
63 |
64 | public Task> OpenWriteStreamAsync(OpenWriteStreamOptions options, CancellationToken cancellationToken = default)
65 | {
66 | if (!File.Exists(options.FullPath))
67 | return Task.FromResult(Result.Fail());
68 |
69 | try
70 | {
71 | return Task.FromResult(Result.Succeed(File.OpenWrite(options.FullPath)));
72 | }
73 | catch (Exception e)
74 | {
75 | return Task.FromResult(Result.Fail(e));
76 | }
77 | }
78 |
79 | public Task> OpenReadStreamAsync(OpenReadStreamOptions options, CancellationToken cancellationToken = default)
80 | {
81 | if (!File.Exists(options.FullPath))
82 | return Task.FromResult(Result.Fail());
83 |
84 | try
85 | {
86 | return Task.FromResult(Result.Succeed(File.OpenRead(options.FullPath)));
87 | }
88 | catch (Exception e)
89 | {
90 | return Task.FromResult(Result.Fail(e));
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/FakeAzureStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Azure.Storage.Blobs;
6 | using ManagedCode.Communication;
7 | using ManagedCode.Storage.Azure;
8 | using ManagedCode.Storage.Azure.Options;
9 | using ManagedCode.Storage.Core;
10 | using ManagedCode.Storage.FileSystem;
11 | using ManagedCode.Storage.FileSystem.Options;
12 |
13 | namespace ManagedCode.Storage.TestFakes;
14 |
15 | public class FakeAzureStorage : FileSystemStorage, IAzureStorage
16 | {
17 | public FakeAzureStorage() : base(new FileSystemStorageOptions())
18 | {
19 | }
20 |
21 | public BlobContainerClient StorageClient { get; }
22 |
23 | public Task SetStorageOptions(IStorageOptions options, CancellationToken cancellationToken = default)
24 | {
25 | return Task.FromResult(Result.Succeed());
26 | }
27 |
28 | public Task SetStorageOptions(Action options, CancellationToken cancellationToken = default)
29 | {
30 | return Task.FromResult(Result.Succeed());
31 | }
32 |
33 | public Task> OpenReadStreamAsync(string fileName, CancellationToken cancellationToken = default)
34 | {
35 | if (!File.Exists(fileName))
36 | return Task.FromResult(Result.Fail());
37 |
38 | try
39 | {
40 | return Task.FromResult(Result.Succeed(File.OpenRead(fileName)));
41 | }
42 | catch (Exception e)
43 | {
44 | return Task.FromResult(Result.Fail(e));
45 | }
46 | }
47 |
48 | public Task> OpenWriteStreamAsync(string fileName, CancellationToken cancellationToken = default)
49 | {
50 | if (!File.Exists(fileName))
51 | return Task.FromResult(Result.Fail());
52 |
53 | try
54 | {
55 | return Task.FromResult(Result.Succeed(File.OpenWrite(fileName)));
56 | }
57 | catch (Exception e)
58 | {
59 | return Task.FromResult(Result.Fail(e));
60 | }
61 | }
62 |
63 | public Stream GetBlobStream(string fileName, bool userBuffer = true, int bufferSize = BlobStream.DefaultBufferSize)
64 | {
65 | return File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
66 | }
67 |
68 | public Task SetStorageOptions(AzureStorageOptions options, CancellationToken cancellationToken = default)
69 | {
70 | return Task.FromResult(Result.Succeed());
71 | }
72 |
73 | public Task SetStorageOptions(Action options, CancellationToken cancellationToken = default)
74 | {
75 | return Task.FromResult(Result.Succeed());
76 | }
77 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/FakeGoogleStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Google.Cloud.Storage.V1;
5 | using ManagedCode.Communication;
6 | using ManagedCode.Storage.FileSystem;
7 | using ManagedCode.Storage.FileSystem.Options;
8 | using ManagedCode.Storage.Google;
9 | using ManagedCode.Storage.Google.Options;
10 |
11 | namespace ManagedCode.Storage.TestFakes;
12 |
13 | public class FakeGoogleStorage : FileSystemStorage, IGCPStorage
14 | {
15 | public FakeGoogleStorage() : base(new FileSystemStorageOptions())
16 | {
17 | }
18 |
19 | public StorageClient StorageClient { get; }
20 |
21 | public Task SetStorageOptions(GCPStorageOptions options, CancellationToken cancellationToken = default)
22 | {
23 | return Task.FromResult(Result.Succeed());
24 | }
25 |
26 | public Task SetStorageOptions(Action options, CancellationToken cancellationToken = default)
27 | {
28 | return Task.FromResult(Result.Succeed());
29 | }
30 | }
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/ManagedCode.Storage.TestFakes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.TestFakes
10 | ManagedCode.Storage.TestFakes
11 | Fake implementaions for units tests
12 | managedcode, aws, gcp, azure storage, cloud, asp.net, file, upload, download
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ManagedCode.Storage.TestFakes/MockCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Aws;
2 | using ManagedCode.Storage.Azure;
3 | using ManagedCode.Storage.Azure.DataLake;
4 | using ManagedCode.Storage.Core;
5 | using ManagedCode.Storage.Google;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 |
9 | namespace ManagedCode.Storage.TestFakes;
10 |
11 | public static class MockCollectionExtensions
12 | {
13 | public static IServiceCollection ReplaceAWSStorageAsDefault(this IServiceCollection serviceCollection)
14 | {
15 | serviceCollection.ReplaceAWSStorage();
16 | serviceCollection.AddSingleton();
17 | return serviceCollection;
18 | }
19 |
20 | public static IServiceCollection ReplaceAWSStorage(this IServiceCollection serviceCollection)
21 | {
22 | serviceCollection.RemoveAll();
23 | serviceCollection.RemoveAll();
24 | serviceCollection.AddSingleton();
25 | return serviceCollection;
26 | }
27 |
28 | public static IServiceCollection ReplaceAzureDataLakeStorage(this IServiceCollection serviceCollection)
29 | {
30 | serviceCollection.RemoveAll();
31 | serviceCollection.RemoveAll();
32 | serviceCollection.AddSingleton();
33 | return serviceCollection;
34 | }
35 |
36 | public static IServiceCollection ReplaceAzureDataLakeStorageAsDefault(this IServiceCollection serviceCollection)
37 | {
38 | serviceCollection.ReplaceAzureDataLakeStorage();
39 | serviceCollection.AddSingleton();
40 | return serviceCollection;
41 | }
42 |
43 | public static IServiceCollection ReplaceAzureStorage(this IServiceCollection serviceCollection)
44 | {
45 | serviceCollection.RemoveAll();
46 | serviceCollection.RemoveAll();
47 | serviceCollection.AddSingleton();
48 | return serviceCollection;
49 | }
50 |
51 | public static IServiceCollection ReplaceAzureStorageAsDefault(this IServiceCollection serviceCollection)
52 | {
53 | serviceCollection.ReplaceAzureStorage();
54 | serviceCollection.AddSingleton();
55 | return serviceCollection;
56 | }
57 |
58 | public static IServiceCollection ReplaceGoogleStorageAsDefault(this IServiceCollection serviceCollection)
59 | {
60 | serviceCollection.ReplaceGoogleStorage();
61 | serviceCollection.AddSingleton();
62 | return serviceCollection;
63 | }
64 |
65 | public static IServiceCollection ReplaceGoogleStorage(this IServiceCollection serviceCollection)
66 | {
67 | serviceCollection.RemoveAll();
68 | serviceCollection.RemoveAll();
69 | serviceCollection.AddSingleton();
70 | return serviceCollection;
71 | }
72 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/AWSStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Aws.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Extensions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace ManagedCode.Storage.Aws
10 | {
11 | public class AWSStorageProvider(IServiceProvider serviceProvider, AWSStorageOptions defaultOptions) : IStorageProvider
12 | {
13 | public Type StorageOptionsType => typeof(AWSStorageOptions);
14 |
15 | public TStorage CreateStorage(TOptions options)
16 | where TStorage : class, IStorage
17 | where TOptions : class, IStorageOptions
18 | {
19 | if (options is not AWSStorageOptions azureOptions)
20 | {
21 | throw new ArgumentException($"Options must be of type {typeof(AWSStorageOptions)}", nameof(options));
22 | }
23 |
24 | var logger = serviceProvider.GetService>();
25 | var storage = new AWSStorage(azureOptions, logger);
26 |
27 | return storage as TStorage
28 | ?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
29 | }
30 |
31 | public IStorageOptions GetDefaultOptions()
32 | {
33 | return new AWSStorageOptions()
34 | {
35 | PublicKey = defaultOptions.PublicKey,
36 | SecretKey = defaultOptions.SecretKey,
37 | RoleName = defaultOptions.RoleName,
38 | Bucket = defaultOptions.Bucket,
39 | OriginalOptions = defaultOptions.OriginalOptions,
40 | CreateContainerIfNotExists = defaultOptions.CreateContainerIfNotExists,
41 | UseInstanceProfileCredentials = defaultOptions.UseInstanceProfileCredentials
42 | };
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Aws.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Exceptions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 |
9 | namespace ManagedCode.Storage.Aws.Extensions;
10 |
11 | public static class ServiceCollectionExtensions
12 | {
13 | public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, Action action)
14 | {
15 | var options = new AWSStorageOptions();
16 | action.Invoke(options);
17 |
18 | CheckConfiguration(options);
19 |
20 | return serviceCollection.AddAWSStorage(options);
21 | }
22 |
23 | public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, Action action)
24 | {
25 | var options = new AWSStorageOptions();
26 | action.Invoke(options);
27 |
28 | CheckConfiguration(options);
29 |
30 | return serviceCollection.AddAWSStorageAsDefault(options);
31 | }
32 |
33 | public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, AWSStorageOptions options)
34 | {
35 | CheckConfiguration(options);
36 | serviceCollection.AddSingleton(options);
37 | serviceCollection.AddSingleton();
38 | return serviceCollection.AddSingleton();
39 | }
40 |
41 | public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, AWSStorageOptions options)
42 | {
43 | CheckConfiguration(options);
44 | serviceCollection.AddSingleton(options);
45 | serviceCollection.AddSingleton();
46 | serviceCollection.AddSingleton();
47 | return serviceCollection.AddSingleton();
48 | }
49 |
50 | public static IServiceCollection AddAWSStorage(this IServiceCollection serviceCollection, string key, Action action)
51 | {
52 | var options = new AWSStorageOptions();
53 | action.Invoke(options);
54 | CheckConfiguration(options);
55 |
56 | serviceCollection.AddKeyedSingleton(key, options);
57 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
58 | {
59 | var opts = sp.GetKeyedService(k);
60 | return new AWSStorage(opts);
61 | });
62 |
63 | return serviceCollection;
64 | }
65 |
66 | public static IServiceCollection AddAWSStorageAsDefault(this IServiceCollection serviceCollection, string key, Action action)
67 | {
68 | var options = new AWSStorageOptions();
69 | action.Invoke(options);
70 | CheckConfiguration(options);
71 |
72 | serviceCollection.AddKeyedSingleton(key, options);
73 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
74 | {
75 | var opts = sp.GetKeyedService(k);
76 | return new AWSStorage(opts);
77 | });
78 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
79 | sp.GetRequiredKeyedService(k));
80 |
81 | return serviceCollection;
82 | }
83 |
84 | private static void CheckConfiguration(AWSStorageOptions options)
85 | {
86 | // Make sure the bucket name is set.
87 | if (string.IsNullOrEmpty(options.Bucket))
88 | throw new BadConfigurationException($"{nameof(options.Bucket)} cannot be empty");
89 |
90 | // If we are using instance profile credentials, we don't need to check for the public and secret keys.
91 | if (!options.UseInstanceProfileCredentials)
92 | {
93 | if (string.IsNullOrEmpty(options.PublicKey))
94 | throw new BadConfigurationException($"{nameof(options.PublicKey)} cannot be empty");
95 |
96 | if (string.IsNullOrEmpty(options.SecretKey))
97 | throw new BadConfigurationException($"{nameof(options.SecretKey)} cannot be empty");
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/Extensions/StorageFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Aws.Options;
3 | using ManagedCode.Storage.Core.Providers;
4 |
5 | namespace ManagedCode.Storage.Aws.Extensions;
6 |
7 | public static class StorageFactoryExtensions
8 | {
9 | public static IAWSStorage CreateAWSStorage(this IStorageFactory factory, string bucketName)
10 | {
11 | return factory.CreateStorage(options => options.Bucket = bucketName);
12 | }
13 |
14 | public static IAWSStorage CreateAWSStorage(this IStorageFactory factory, AWSStorageOptions options)
15 | {
16 | return factory.CreateStorage(options);
17 | }
18 |
19 |
20 | public static IAWSStorage CreateAWSStorage(this IStorageFactory factory, Action options)
21 | {
22 | return factory.CreateStorage(options);
23 | }
24 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/IAWSStorage.cs:
--------------------------------------------------------------------------------
1 | using Amazon.S3;
2 | using ManagedCode.Storage.Aws.Options;
3 | using ManagedCode.Storage.Core;
4 |
5 | namespace ManagedCode.Storage.Aws;
6 |
7 | public interface IAWSStorage : IStorage
8 | {
9 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/ManagedCode.Storage.Aws.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.Aws
10 | ManagedCode.Storage.Aws
11 | Storage for AWS
12 | managedcode, aws, storage, cloud, s3, blob
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Aws/Options/AWSStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using Amazon.S3;
2 | using ManagedCode.Storage.Core;
3 |
4 | namespace ManagedCode.Storage.Aws.Options;
5 |
6 | ///
7 | /// Configuration options for AWS S3 storage.
8 | ///
9 | public class AWSStorageOptions : IStorageOptions
10 | {
11 | ///
12 | /// The public key to access the AWS S3 storage bucket.
13 | ///
14 | public string? PublicKey { get; set; }
15 |
16 | ///
17 | /// The secret key to access the AWS S3 storage bucket.
18 | ///
19 | public string? SecretKey { get; set; }
20 |
21 | ///
22 | /// The name of the IAM role.
23 | ///
24 | ///
25 | /// If this is set, the and will be ignored.
26 | /// Note that this can only be used when running on an EC2 instance.
27 | ///
28 | public string? RoleName { get; set; }
29 |
30 | ///
31 | /// The name of the bucket to use.
32 | ///
33 | public string? Bucket { get; set; }
34 |
35 | ///
36 | /// The underlying Amazon S3 configuration.
37 | ///
38 | public AmazonS3Config? OriginalOptions { get; set; } = new();
39 |
40 | ///
41 | /// Whether to create the container if it does not exist. Default is true.
42 | ///
43 | public bool CreateContainerIfNotExists { get; set; } = true;
44 |
45 | ///
46 | /// Whether to use the instance profile credentials. Default is false.
47 | ///
48 | ///
49 | /// If this is set to true, the and will be ignored.
50 | /// Note that this can only be used when running on an EC2 instance.
51 | ///
52 | public bool UseInstanceProfileCredentials { get; set; } = false;
53 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/AzureDataLakeStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.DataLake.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Extensions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace ManagedCode.Storage.Azure.DataLake
10 | {
11 | public class AzureDataLakeStorageProvider(IServiceProvider serviceProvider, AzureDataLakeStorageOptions defaultOptions) : IStorageProvider
12 | {
13 | public Type StorageOptionsType => typeof(AzureDataLakeStorageOptions);
14 |
15 | public TStorage CreateStorage(TOptions options)
16 | where TStorage : class, IStorage
17 | where TOptions : class, IStorageOptions
18 | {
19 | if (options is not AzureDataLakeStorageOptions azureOptions)
20 | {
21 | throw new ArgumentException($"Options must be of type {typeof(AzureDataLakeStorageOptions)}", nameof(options));
22 | }
23 |
24 | var logger = serviceProvider.GetService>();
25 | var storage = new AzureDataLakeStorage(azureOptions, logger);
26 |
27 | return storage as TStorage
28 | ?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
29 | }
30 |
31 | public IStorageOptions GetDefaultOptions()
32 | {
33 | return new AzureDataLakeStorageOptions()
34 | {
35 | ConnectionString = defaultOptions.ConnectionString,
36 | FileSystem = defaultOptions.FileSystem,
37 | PublicAccessType = defaultOptions.PublicAccessType,
38 | CreateContainerIfNotExists = defaultOptions.CreateContainerIfNotExists
39 | };
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.DataLake.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Exceptions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 |
9 | namespace ManagedCode.Storage.Azure.DataLake.Extensions;
10 |
11 | public static class ServiceCollectionExtensions
12 | {
13 | public static IServiceCollection AddAzureDataLakeStorage(this IServiceCollection serviceCollection, Action action)
14 | {
15 | var options = new AzureDataLakeStorageOptions();
16 | action.Invoke(options);
17 |
18 | CheckConfiguration(options);
19 |
20 | return serviceCollection.AddAzureDataLakeStorage(options);
21 | }
22 |
23 | public static IServiceCollection AddAzureDataLakeStorageAsDefault(this IServiceCollection serviceCollection,
24 | Action action)
25 | {
26 | var options = new AzureDataLakeStorageOptions();
27 | action.Invoke(options);
28 |
29 | CheckConfiguration(options);
30 |
31 | return serviceCollection.AddAzureDataLakeStorageAsDefault(options);
32 | }
33 |
34 | public static IServiceCollection AddAzureDataLakeStorage(this IServiceCollection serviceCollection, AzureDataLakeStorageOptions options)
35 | {
36 | CheckConfiguration(options);
37 | serviceCollection.AddSingleton(options);
38 | serviceCollection.AddSingleton();
39 | return serviceCollection.AddSingleton();
40 | }
41 |
42 | public static IServiceCollection AddAzureDataLakeStorageAsDefault(this IServiceCollection serviceCollection, AzureDataLakeStorageOptions options)
43 | {
44 | CheckConfiguration(options);
45 | serviceCollection.AddSingleton(options);
46 | serviceCollection.AddSingleton();
47 | serviceCollection.AddSingleton();
48 | return serviceCollection.AddSingleton();
49 | }
50 |
51 | public static IServiceCollection AddAzureDataLakeStorage(this IServiceCollection serviceCollection, string key, Action action)
52 | {
53 | var options = new AzureDataLakeStorageOptions();
54 | action.Invoke(options);
55 | CheckConfiguration(options);
56 |
57 | serviceCollection.AddKeyedSingleton(key, options);
58 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
59 | {
60 | var opts = sp.GetKeyedService(k);
61 | return new AzureDataLakeStorage(opts);
62 | });
63 |
64 | return serviceCollection;
65 | }
66 |
67 | public static IServiceCollection AddAzureDataLakeStorageAsDefault(this IServiceCollection serviceCollection, string key, Action action)
68 | {
69 | var options = new AzureDataLakeStorageOptions();
70 | action.Invoke(options);
71 | CheckConfiguration(options);
72 |
73 | serviceCollection.AddKeyedSingleton(key, options);
74 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
75 | {
76 | var opts = sp.GetKeyedService(k);
77 | return new AzureDataLakeStorage(opts);
78 | });
79 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
80 | sp.GetRequiredKeyedService(k));
81 |
82 | return serviceCollection;
83 | }
84 |
85 | private static void CheckConfiguration(AzureDataLakeStorageOptions options)
86 | {
87 | if (string.IsNullOrEmpty(options.ConnectionString))
88 | throw new BadConfigurationException($"{nameof(options.ConnectionString)} cannot be empty");
89 |
90 | if (string.IsNullOrEmpty(options.FileSystem))
91 | throw new BadConfigurationException($"{nameof(options.FileSystem)} cannot be empty");
92 | }
93 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/Extensions/StorageFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.DataLake.Options;
3 | using ManagedCode.Storage.Core.Providers;
4 |
5 | namespace ManagedCode.Storage.Azure.DataLake.Extensions;
6 |
7 | public static class StorageFactoryExtensions
8 | {
9 | public static IAzureDataLakeStorage CreateAzureDataLakeStorage(this IStorageFactory factory, string fileSystemName)
10 | {
11 | return factory.CreateStorage(options => options.FileSystem = fileSystemName);
12 | }
13 |
14 | public static IAzureDataLakeStorage CreateAzureDataLakeStorage(this IStorageFactory factory, AzureDataLakeStorageOptions options)
15 | {
16 | return factory.CreateStorage(options);
17 | }
18 |
19 |
20 | public static IAzureDataLakeStorage CreateAzureDataLakeStorage(this IStorageFactory factory, Action options)
21 | {
22 | return factory.CreateStorage(options);
23 | }
24 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/IAzureDataLakeStorage.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Azure.Storage.Files.DataLake;
5 | using ManagedCode.Communication;
6 | using ManagedCode.Storage.Azure.DataLake.Options;
7 | using ManagedCode.Storage.Core;
8 |
9 | namespace ManagedCode.Storage.Azure.DataLake;
10 |
11 | public interface IAzureDataLakeStorage : IStorage
12 | {
13 | ///
14 | /// Create directory
15 | ///
16 | Task CreateDirectoryAsync(string directory, CancellationToken cancellationToken = default);
17 |
18 | ///
19 | /// Rename directory
20 | ///
21 | Task RenameDirectory(string directory, string newDirectory, CancellationToken cancellationToken = default);
22 |
23 | Task> OpenWriteStreamAsync(OpenWriteStreamOptions options, CancellationToken cancellationToken = default);
24 |
25 | Task> OpenReadStreamAsync(OpenReadStreamOptions options, CancellationToken cancellationToken = default);
26 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/ManagedCode.Storage.Azure.DataLake.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.AzureDataLake
10 | ManagedCode.Storage.Azure.DataLake
11 | Storage for AzureDataLake
12 | managedcode, azure, storage, cloud, blob, datalake, data, lake
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/Options/AzureDataLakeStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Files.DataLake.Models;
2 | using ManagedCode.Storage.Core;
3 |
4 | namespace ManagedCode.Storage.Azure.DataLake.Options;
5 |
6 | public class AzureDataLakeStorageOptions : IStorageOptions
7 | {
8 | public string ConnectionString { get; set; }
9 | public string FileSystem { get; set; }
10 |
11 | public DataLakeFileSystemCreateOptions PublicAccessType { get; set; } = new()
12 | {
13 | PublicAccessType = global::Azure.Storage.Files.DataLake.Models.PublicAccessType.None
14 | };
15 |
16 | public bool CreateContainerIfNotExists { get; set; } = true;
17 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/Options/OpenReadStreamOptions.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Core.Models;
2 |
3 | namespace ManagedCode.Storage.Azure.DataLake.Options;
4 |
5 | public class OpenReadStreamOptions : BaseOptions
6 | {
7 | public long Position { get; set; }
8 |
9 | public int BufferSize { get; set; }
10 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure.DataLake/Options/OpenWriteStreamOptions.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Core.Models;
2 |
3 | namespace ManagedCode.Storage.Azure.DataLake.Options;
4 |
5 | public class OpenWriteStreamOptions : BaseOptions
6 | {
7 | public bool Overwrite { get; set; }
8 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/AzureStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Extensions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace ManagedCode.Storage.Azure
10 | {
11 | public class AzureStorageProvider(IServiceProvider serviceProvider, IAzureStorageOptions defaultOptions) : IStorageProvider
12 | {
13 | public Type StorageOptionsType => typeof(IAzureStorageOptions);
14 |
15 | public TStorage CreateStorage(TOptions options)
16 | where TStorage : class, IStorage
17 | where TOptions : class, IStorageOptions
18 | {
19 | if (options is not IAzureStorageOptions azureOptions)
20 | {
21 | throw new ArgumentException($"Options must be of type {typeof(IAzureStorageOptions)}", nameof(options));
22 | }
23 |
24 | var logger = serviceProvider.GetService>();
25 | var storage = new AzureStorage(azureOptions, logger);
26 |
27 | return storage as TStorage
28 | ?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
29 | }
30 |
31 | public IStorageOptions GetDefaultOptions()
32 | {
33 | return defaultOptions switch
34 | {
35 | AzureStorageCredentialsOptions credentialsOptions => new AzureStorageCredentialsOptions
36 | {
37 | AccountName = credentialsOptions.AccountName,
38 | ContainerName = credentialsOptions.ContainerName,
39 | Credentials = credentialsOptions.Credentials,
40 | Container = credentialsOptions.Container,
41 | PublicAccessType = credentialsOptions.PublicAccessType,
42 | OriginalOptions = credentialsOptions.OriginalOptions,
43 | CreateContainerIfNotExists = credentialsOptions.CreateContainerIfNotExists
44 | },
45 | AzureStorageOptions storageOptions => new AzureStorageOptions
46 | {
47 | ConnectionString = storageOptions.ConnectionString,
48 | Container = storageOptions.Container,
49 | PublicAccessType = storageOptions.PublicAccessType,
50 | OriginalOptions = storageOptions.OriginalOptions,
51 | CreateContainerIfNotExists = storageOptions.CreateContainerIfNotExists
52 | },
53 | _ => throw new ArgumentException($"Unknown options type: {defaultOptions.GetType()}")
54 | };
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.Options;
3 | using ManagedCode.Storage.Core;
4 | using ManagedCode.Storage.Core.Exceptions;
5 | using ManagedCode.Storage.Core.Providers;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 |
9 | namespace ManagedCode.Storage.Azure.Extensions;
10 |
11 | public static class ServiceCollectionExtensions
12 | {
13 | public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, Action action)
14 | {
15 | var options = new AzureStorageOptions();
16 | action.Invoke(options);
17 |
18 | CheckConfiguration(options);
19 |
20 | return serviceCollection.AddAzureStorage(options);
21 | }
22 |
23 | public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, Action action)
24 | {
25 | var options = new AzureStorageOptions();
26 | action.Invoke(options);
27 |
28 | CheckConfiguration(options);
29 |
30 | return serviceCollection.AddAzureStorageAsDefault(options);
31 | }
32 |
33 | public static IServiceCollection AddAzureStorageWithCredential(this IServiceCollection serviceCollection, Action action)
34 | {
35 | var options = new AzureStorageCredentialsOptions();
36 | action.Invoke(options);
37 |
38 | CheckConfiguration(options);
39 |
40 | return serviceCollection.AddAzureStorage(options);
41 | }
42 |
43 | public static IServiceCollection AddAzureStorageAsDefaultWithCredential(this IServiceCollection serviceCollection, Action action)
44 | {
45 | var options = new AzureStorageCredentialsOptions();
46 | action.Invoke(options);
47 |
48 | CheckConfiguration(options);
49 |
50 | return serviceCollection.AddAzureStorageAsDefault(options);
51 | }
52 |
53 | public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, IAzureStorageOptions options)
54 | {
55 | CheckConfiguration(options);
56 | serviceCollection.AddSingleton(options);
57 | serviceCollection.AddSingleton();
58 | return serviceCollection.AddSingleton();
59 | }
60 |
61 | public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, IAzureStorageOptions options)
62 | {
63 | CheckConfiguration(options);
64 | serviceCollection.AddSingleton(options);
65 | serviceCollection.AddSingleton();
66 | serviceCollection.AddSingleton();
67 | return serviceCollection.AddSingleton();
68 | }
69 |
70 | public static IServiceCollection AddAzureStorage(this IServiceCollection serviceCollection, string key, Action action)
71 | {
72 | var options = new AzureStorageOptions();
73 | action.Invoke(options);
74 | CheckConfiguration(options);
75 |
76 | serviceCollection.AddKeyedSingleton(key, options);
77 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
78 | {
79 | var opts = sp.GetKeyedService(k);
80 | return new AzureStorage(opts);
81 | });
82 |
83 | return serviceCollection;
84 | }
85 |
86 | public static IServiceCollection AddAzureStorageAsDefault(this IServiceCollection serviceCollection, string key, Action action)
87 | {
88 | var options = new AzureStorageOptions();
89 | action.Invoke(options);
90 | CheckConfiguration(options);
91 |
92 | serviceCollection.AddKeyedSingleton(key, options);
93 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
94 | {
95 | var opts = sp.GetKeyedService(k);
96 | return new AzureStorage(opts);
97 | });
98 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
99 | sp.GetRequiredKeyedService(k));
100 |
101 | return serviceCollection;
102 | }
103 |
104 | private static void CheckConfiguration(IAzureStorageOptions options)
105 | {
106 | if (options is AzureStorageOptions azureStorageOptions)
107 | {
108 | if (string.IsNullOrEmpty(azureStorageOptions.ConnectionString))
109 | throw new BadConfigurationException($"{nameof(azureStorageOptions.ConnectionString)} cannot be empty");
110 |
111 | if (string.IsNullOrEmpty(azureStorageOptions.Container))
112 | throw new BadConfigurationException($"{nameof(azureStorageOptions.Container)} cannot be empty");
113 | }
114 |
115 | if (options is AzureStorageCredentialsOptions azureStorageCredentialsOptions)
116 | {
117 | if (string.IsNullOrEmpty(azureStorageCredentialsOptions.AccountName))
118 | throw new BadConfigurationException($"{nameof(azureStorageCredentialsOptions.AccountName)} cannot be empty");
119 |
120 | if (string.IsNullOrEmpty(azureStorageCredentialsOptions.ContainerName))
121 | throw new BadConfigurationException($"{nameof(azureStorageCredentialsOptions.ContainerName)} cannot be empty");
122 |
123 | if (azureStorageCredentialsOptions.Credentials is null)
124 | throw new BadConfigurationException($"{nameof(azureStorageCredentialsOptions.Credentials)} cannot be null");
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/Extensions/StorageFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Azure.Options;
3 | using ManagedCode.Storage.Core.Providers;
4 |
5 | namespace ManagedCode.Storage.Azure.Extensions;
6 |
7 | public static class StorageFactoryExtensions
8 | {
9 | public static IAzureStorage CreateAzureStorage(this IStorageFactory factory, string containerName)
10 | {
11 | return factory.CreateStorage(options => options.Container = containerName);
12 | }
13 |
14 | public static IAzureStorage CreateAzureStorage(this IStorageFactory factory, IAzureStorageOptions options)
15 | {
16 | return factory.CreateStorage(options);
17 | }
18 |
19 |
20 | public static IAzureStorage CreateAzureStorage(this IStorageFactory factory, Action options)
21 | {
22 | return factory.CreateStorage(options);
23 | }
24 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/IAzureStorage.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Azure.Storage.Blobs;
5 | using ManagedCode.Communication;
6 | using ManagedCode.Storage.Core;
7 |
8 | namespace ManagedCode.Storage.Azure;
9 |
10 | public interface IAzureStorage : IStorage
11 | {
12 | Task> OpenReadStreamAsync(string fileName, CancellationToken cancellationToken = default);
13 | Task> OpenWriteStreamAsync(string fileName, CancellationToken cancellationToken = default);
14 |
15 | Stream GetBlobStream(string fileName, bool userBuffer = true, int bufferSize = BlobStream.DefaultBufferSize);
16 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/ManagedCode.Storage.Azure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.Azure
10 | ManagedCode.Storage.Azure
11 | Storage for Azure
12 | managedcode, azure, storage, cloud, blob
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/Options/AzureStorageCredentialsOptions.cs:
--------------------------------------------------------------------------------
1 | using Azure.Core;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Models;
4 |
5 | namespace ManagedCode.Storage.Azure.Options;
6 |
7 | public class AzureStorageCredentialsOptions : IAzureStorageOptions
8 | {
9 | public string AccountName { get; set; }
10 | public string ContainerName { get; set; }
11 |
12 | public TokenCredential Credentials { get; set; }
13 |
14 | public string? Container { get; set; }
15 | public PublicAccessType PublicAccessType { get; set; }
16 | public BlobClientOptions? OriginalOptions { get; set; }
17 |
18 | public bool CreateContainerIfNotExists { get; set; } = true;
19 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/Options/AzureStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Models;
3 |
4 | namespace ManagedCode.Storage.Azure.Options;
5 |
6 | public class AzureStorageOptions : IAzureStorageOptions
7 | {
8 | public string? ConnectionString { get; set; }
9 | public string? Container { get; set; }
10 | public PublicAccessType PublicAccessType { get; set; }
11 | public BlobClientOptions? OriginalOptions { get; set; }
12 |
13 | public bool CreateContainerIfNotExists { get; set; } = true;
14 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Azure/Options/IAzureStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Models;
3 | using ManagedCode.Storage.Core;
4 |
5 | namespace ManagedCode.Storage.Azure.Options;
6 |
7 | public interface IAzureStorageOptions : IStorageOptions
8 | {
9 | public string? Container { get; set; }
10 | public PublicAccessType PublicAccessType { get; set; }
11 | public BlobClientOptions? OriginalOptions { get; set; }
12 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core;
3 | using ManagedCode.Storage.Core.Providers;
4 | using ManagedCode.Storage.FileSystem.Options;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.DependencyInjection.Extensions;
7 |
8 | namespace ManagedCode.Storage.FileSystem.Extensions;
9 |
10 | public static class ServiceCollectionExtensions
11 | {
12 | public static IServiceCollection AddFileSystemStorage(this IServiceCollection serviceCollection, Action action)
13 | {
14 | var fsStorageOptions = new FileSystemStorageOptions();
15 | action.Invoke(fsStorageOptions);
16 | return serviceCollection.AddFileSystemStorage(fsStorageOptions);
17 | }
18 |
19 | public static IServiceCollection AddFileSystemStorageAsDefault(this IServiceCollection serviceCollection, Action action)
20 | {
21 | var fsStorageOptions = new FileSystemStorageOptions();
22 | action.Invoke(fsStorageOptions);
23 |
24 | return serviceCollection.AddFileSystemStorageAsDefault(fsStorageOptions);
25 | }
26 |
27 | public static IServiceCollection AddFileSystemStorage(this IServiceCollection serviceCollection, FileSystemStorageOptions options)
28 | {
29 | serviceCollection.AddSingleton(options);
30 | serviceCollection.AddSingleton();
31 | return serviceCollection.AddSingleton(sp => new FileSystemStorage(options));
32 | }
33 |
34 | public static IServiceCollection AddFileSystemStorageAsDefault(this IServiceCollection serviceCollection, FileSystemStorageOptions options)
35 | {
36 | serviceCollection.AddSingleton(options);
37 | serviceCollection.AddSingleton();
38 | serviceCollection.AddSingleton(sp => new FileSystemStorage(options));
39 | return serviceCollection.AddSingleton(sp => new FileSystemStorage(options));
40 | }
41 |
42 | public static IServiceCollection AddFileSystemStorage(this IServiceCollection serviceCollection, string key, Action action)
43 | {
44 | var options = new FileSystemStorageOptions();
45 | action.Invoke(options);
46 |
47 | serviceCollection.AddKeyedSingleton(key, options);
48 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
49 | {
50 | var opts = sp.GetKeyedService(k);
51 | return new FileSystemStorage(opts);
52 | });
53 |
54 | return serviceCollection;
55 | }
56 |
57 | public static IServiceCollection AddFileSystemStorageAsDefault(this IServiceCollection serviceCollection, string key, Action action)
58 | {
59 | var options = new FileSystemStorageOptions();
60 | action.Invoke(options);
61 |
62 | serviceCollection.AddKeyedSingleton(key, options);
63 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
64 | {
65 | var opts = sp.GetKeyedService(k);
66 | return new FileSystemStorage(opts);
67 | });
68 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
69 | sp.GetRequiredKeyedService(k));
70 |
71 | return serviceCollection;
72 | }
73 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/Extensions/StorageFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core.Providers;
3 | using ManagedCode.Storage.FileSystem.Options;
4 |
5 | namespace ManagedCode.Storage.FileSystem.Extensions;
6 |
7 | public static class StorageFactoryExtensions
8 | {
9 | public static IFileSystemStorage CreateFileSystemStorage(this IStorageFactory factory, string baseFolder)
10 | {
11 | return factory.CreateStorage(options => options.BaseFolder = baseFolder);
12 | }
13 |
14 | public static IFileSystemStorage CreateFileSystemStorage(this IStorageFactory factory, FileSystemStorageOptions options)
15 | {
16 | return factory.CreateStorage(options);
17 | }
18 |
19 |
20 | public static IFileSystemStorage CreateFileSystemStorage(this IStorageFactory factory, Action options)
21 | {
22 | return factory.CreateStorage(options);
23 | }
24 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/FileSystemStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core;
3 | using ManagedCode.Storage.Core.Extensions;
4 | using ManagedCode.Storage.Core.Providers;
5 | using ManagedCode.Storage.FileSystem.Options;
6 |
7 | namespace ManagedCode.Storage.FileSystem
8 | {
9 | public class FileSystemStorageProvider(IServiceProvider serviceProvider, FileSystemStorageOptions defaultOptions) : IStorageProvider
10 | {
11 | public Type StorageOptionsType => typeof(FileSystemStorageOptions);
12 |
13 | public TStorage CreateStorage(TOptions options)
14 | where TStorage : class, IStorage
15 | where TOptions : class, IStorageOptions
16 | {
17 | if (options is not FileSystemStorageOptions azureOptions)
18 | {
19 | throw new ArgumentException($"Options must be of type {typeof(FileSystemStorageOptions)}", nameof(options));
20 | }
21 |
22 | //var logger = serviceProvider.GetService>();
23 | var storage = new FileSystemStorage(azureOptions);
24 |
25 | return storage as TStorage
26 | ?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
27 | }
28 |
29 | public IStorageOptions GetDefaultOptions()
30 | {
31 | return new FileSystemStorageOptions
32 | {
33 | BaseFolder = defaultOptions.BaseFolder,
34 | CreateContainerIfNotExists = defaultOptions.CreateContainerIfNotExists
35 | };
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/IFileSystemStorage.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Core;
2 |
3 | namespace ManagedCode.Storage.FileSystem;
4 |
5 | public interface IFileSystemStorage : IStorage
6 | {
7 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/ManagedCode.Storage.FileSystem.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.FileSystem
10 | ManagedCode.Storage.FileSystem
11 | Storage for FileSystem
12 | managedcode, file, storage, filesystem
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.FileSystem/Options/FileSystemStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Core;
2 |
3 | namespace ManagedCode.Storage.FileSystem.Options;
4 |
5 | public class FileSystemStorageOptions : IStorageOptions
6 | {
7 | public string? BaseFolder { get; set; }
8 |
9 | public bool CreateContainerIfNotExists { get; set; } = true;
10 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core;
3 | using ManagedCode.Storage.Core.Exceptions;
4 | using ManagedCode.Storage.Core.Providers;
5 | using ManagedCode.Storage.Google.Options;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 |
9 | namespace ManagedCode.Storage.Google.Extensions;
10 |
11 | public static class ServiceCollectionExtensions
12 | {
13 | public static IServiceCollection AddGCPStorage(this IServiceCollection serviceCollection, Action action)
14 | {
15 | var options = new GCPStorageOptions();
16 | action.Invoke(options);
17 |
18 | CheckConfiguration(options);
19 |
20 | return serviceCollection.AddGCPStorage(options);
21 | }
22 |
23 | public static IServiceCollection AddGCPStorageAsDefault(this IServiceCollection serviceCollection, Action action)
24 | {
25 | var options = new GCPStorageOptions();
26 | action.Invoke(options);
27 |
28 | CheckConfiguration(options);
29 |
30 | return serviceCollection.AddGCPStorageAsDefault(options);
31 | }
32 |
33 | public static IServiceCollection AddGCPStorage(this IServiceCollection serviceCollection, GCPStorageOptions options)
34 | {
35 | CheckConfiguration(options);
36 | serviceCollection.AddSingleton(options);
37 | serviceCollection.AddSingleton();
38 | return serviceCollection.AddSingleton();
39 | }
40 |
41 | public static IServiceCollection AddGCPStorageAsDefault(this IServiceCollection serviceCollection, GCPStorageOptions options)
42 | {
43 | CheckConfiguration(options);
44 |
45 | serviceCollection.AddSingleton(options);
46 | serviceCollection.AddSingleton();
47 | serviceCollection.AddSingleton();
48 | return serviceCollection.AddSingleton();
49 | }
50 |
51 | public static IServiceCollection AddGCPStorage(this IServiceCollection serviceCollection, string key, Action action)
52 | {
53 | var options = new GCPStorageOptions();
54 | action.Invoke(options);
55 | CheckConfiguration(options);
56 |
57 | serviceCollection.AddKeyedSingleton(key, options);
58 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
59 | {
60 | var opts = sp.GetKeyedService(k);
61 | return new GCPStorage(opts);
62 | });
63 |
64 | return serviceCollection;
65 | }
66 |
67 | public static IServiceCollection AddGCPStorageAsDefault(this IServiceCollection serviceCollection, string key, Action action)
68 | {
69 | var options = new GCPStorageOptions();
70 | action.Invoke(options);
71 | CheckConfiguration(options);
72 |
73 | serviceCollection.AddKeyedSingleton(key, options);
74 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
75 | {
76 | var opts = sp.GetKeyedService(k);
77 | return new GCPStorage(opts);
78 | });
79 | serviceCollection.AddKeyedSingleton(key, (sp, k) =>
80 | sp.GetRequiredKeyedService(k));
81 |
82 | return serviceCollection;
83 | }
84 |
85 | private static void CheckConfiguration(GCPStorageOptions options)
86 | {
87 | if (options.StorageClientBuilder is null && options.GoogleCredential is null)
88 | throw new BadConfigurationException($"{nameof(options.StorageClientBuilder)} or {nameof(options.GoogleCredential)} must be assigned");
89 |
90 | if (string.IsNullOrEmpty(options.BucketOptions?.Bucket))
91 | throw new BadConfigurationException($"{nameof(options.BucketOptions.Bucket)} cannot be empty");
92 |
93 | if (string.IsNullOrEmpty(options.BucketOptions?.ProjectId))
94 | throw new BadConfigurationException($"{nameof(options.BucketOptions.ProjectId)} cannot be empty");
95 | }
96 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/Extensions/StorageFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core.Providers;
3 | using ManagedCode.Storage.Google.Options;
4 |
5 | namespace ManagedCode.Storage.Google.Extensions;
6 |
7 | public static class StorageFactoryExtensions
8 | {
9 | public static IGCPStorage CreateGCPStorage(this IStorageFactory factory, string containerName)
10 | {
11 | return factory.CreateStorage(options => options.BucketOptions.Bucket = containerName);
12 | }
13 |
14 | public static IGCPStorage CreateGCPStorage(this IStorageFactory factory, GCPStorageOptions options)
15 | {
16 | return factory.CreateStorage(options);
17 | }
18 |
19 |
20 | public static IGCPStorage CreateGCPStorage(this IStorageFactory factory, Action options)
21 | {
22 | return factory.CreateStorage(options);
23 | }
24 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/GCPStorageProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ManagedCode.Storage.Core;
3 | using ManagedCode.Storage.Core.Extensions;
4 | using ManagedCode.Storage.Core.Providers;
5 | using ManagedCode.Storage.Google.Options;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace ManagedCode.Storage.Google
10 | {
11 | public class GCPStorageProvider(IServiceProvider serviceProvider, GCPStorageOptions defaultOptions) : IStorageProvider
12 | {
13 | public Type StorageOptionsType => typeof(GCPStorageOptions);
14 |
15 | public TStorage CreateStorage(TOptions options)
16 | where TStorage : class, IStorage
17 | where TOptions : class, IStorageOptions
18 | {
19 | if (options is not GCPStorageOptions azureOptions)
20 | {
21 | throw new ArgumentException($"Options must be of type {typeof(GCPStorageOptions)}", nameof(options));
22 | }
23 |
24 | var logger = serviceProvider.GetService>();
25 | var storage = new GCPStorage(azureOptions, logger);
26 |
27 | return storage as TStorage
28 | ?? throw new InvalidOperationException($"Cannot create storage of type {typeof(TStorage)}");
29 | }
30 |
31 | public IStorageOptions GetDefaultOptions()
32 | {
33 | return new GCPStorageOptions()
34 | {
35 | AuthFileName = defaultOptions.AuthFileName,
36 | BucketOptions = new BucketOptions
37 | {
38 | Bucket = defaultOptions.BucketOptions.Bucket,
39 | ProjectId = defaultOptions.BucketOptions.ProjectId
40 | },
41 | GoogleCredential = defaultOptions.GoogleCredential,
42 | OriginalOptions = defaultOptions.OriginalOptions,
43 | StorageClientBuilder = defaultOptions.StorageClientBuilder,
44 | CreateContainerIfNotExists = defaultOptions.CreateContainerIfNotExists
45 | };
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/IGCPStorage.cs:
--------------------------------------------------------------------------------
1 | using Google.Cloud.Storage.V1;
2 | using ManagedCode.Storage.Core;
3 | using ManagedCode.Storage.Google.Options;
4 |
5 | namespace ManagedCode.Storage.Google;
6 |
7 | public interface IGCPStorage : IStorage
8 | {
9 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/ManagedCode.Storage.Google.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 | ManagedCode.Storage.Gcp
10 | ManagedCode.Storage.Gcp
11 | Storage for Google Cloud Storage
12 | managedcode, gcs, storage, cloud, blob
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/Options/BucketOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Google.Options;
2 |
3 | public class BucketOptions
4 | {
5 | public string ProjectId { get; set; }
6 |
7 | public string Bucket { get; set; }
8 | }
--------------------------------------------------------------------------------
/Storages/ManagedCode.Storage.Google/Options/GCPStorageOptions.cs:
--------------------------------------------------------------------------------
1 | using Google.Apis.Auth.OAuth2;
2 | using Google.Cloud.Storage.V1;
3 | using ManagedCode.Storage.Core;
4 |
5 | namespace ManagedCode.Storage.Google.Options;
6 |
7 | public class GCPStorageOptions : IStorageOptions
8 | {
9 | public string AuthFileName { get; set; } = null!;
10 | public BucketOptions BucketOptions { get; set; }
11 | public GoogleCredential? GoogleCredential { get; set; }
12 | public CreateBucketOptions? OriginalOptions { get; set; }
13 | public StorageClientBuilder? StorageClientBuilder { get; set; }
14 |
15 | public bool CreateContainerIfNotExists { get; set; } = true;
16 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using ManagedCode.Storage.Client;
3 | using ManagedCode.Storage.Tests.Common;
4 | using Xunit;
5 |
6 | namespace ManagedCode.Storage.Tests.AspNetTests.Abstracts;
7 |
8 | [Collection(nameof(StorageTestApplication))]
9 | public abstract class BaseControllerTests(StorageTestApplication testApplication, string apiEndpoint)
10 | {
11 | protected readonly string ApiEndpoint = apiEndpoint;
12 | protected readonly StorageTestApplication TestApplication = testApplication;
13 |
14 | protected HttpClient GetHttpClient()
15 | {
16 | return TestApplication.CreateClient();
17 | }
18 |
19 | protected IStorageClient GetStorageClient()
20 | {
21 | return new StorageClient(TestApplication.CreateClient());
22 | }
23 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseDownloadControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using FluentAssertions;
5 | using ManagedCode.Storage.Core.Helpers;
6 | using ManagedCode.Storage.Core.Models;
7 | using ManagedCode.Storage.Tests.Common;
8 | using ManagedCode.Storage.Tests.Constants;
9 | using Xunit;
10 |
11 | namespace ManagedCode.Storage.Tests.AspNetTests.Abstracts;
12 |
13 | public abstract class BaseDownloadControllerTests : BaseControllerTests
14 | {
15 | private readonly string _downloadEndpoint;
16 | private readonly string _downloadBytesEndpoint;
17 | private readonly string _uploadEndpoint;
18 |
19 | protected BaseDownloadControllerTests(StorageTestApplication testApplication, string apiEndpoint) : base(testApplication, apiEndpoint)
20 | {
21 | _uploadEndpoint = string.Format(ApiEndpoints.Base.UploadFile, ApiEndpoint);
22 | _downloadEndpoint = string.Format(ApiEndpoints.Base.DownloadFile, ApiEndpoint);
23 | _downloadBytesEndpoint = string.Format(ApiEndpoints.Base.DownloadBytes, ApiEndpoint);
24 | }
25 |
26 | [Fact]
27 | public async Task DownloadFile_WhenFileExists_SaveToTempStorage_ReturnSuccess()
28 | {
29 | // Arrange
30 | var storageClient = GetStorageClient();
31 | var contentName = "file";
32 |
33 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
34 | FileHelper.GenerateLocalFile(localFile, 1);
35 | var fileCRC = Crc32Helper.Calculate(await localFile.ReadAllBytesAsync());
36 | var uploadFileBlob = await storageClient.UploadFile(localFile.FileStream, _uploadEndpoint, contentName);
37 |
38 | // Act
39 | var downloadedFileResult = await storageClient.DownloadFile(uploadFileBlob.Value.FullName, _downloadEndpoint);
40 |
41 | // Assert
42 | downloadedFileResult.IsSuccess
43 | .Should()
44 | .BeTrue();
45 | downloadedFileResult.Value
46 | .Should()
47 | .NotBeNull();
48 | var downloadedFileCRC = Crc32Helper.CalculateFileCrc(downloadedFileResult.Value.FilePath);
49 | downloadedFileCRC.Should()
50 | .Be(fileCRC);
51 | }
52 |
53 | [Fact]
54 | public async Task DownloadFileAsBytes_WhenFileExists_ReturnSuccess()
55 | {
56 | // Arrange
57 | var storageClient = GetStorageClient();
58 | var contentName = "file";
59 |
60 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
61 | FileHelper.GenerateLocalFile(localFile, 1);
62 | var fileCRC = Crc32Helper.Calculate(await localFile.ReadAllBytesAsync());
63 | var uploadFileBlob = await storageClient.UploadFile(localFile.FileStream, _uploadEndpoint, contentName);
64 |
65 | // Act
66 | var downloadedFileResult = await storageClient.DownloadFile(uploadFileBlob.Value.FullName, _downloadBytesEndpoint);
67 |
68 | // Assert
69 | downloadedFileResult.IsSuccess.Should().BeTrue();
70 | downloadedFileResult.Value.Should().NotBeNull();
71 | var downloadedFileCRC = Crc32Helper.Calculate(await downloadedFileResult.Value.ReadAllBytesAsync());
72 | downloadedFileCRC.Should().Be(fileCRC);
73 | }
74 |
75 | [Fact]
76 | public async Task DownloadFile_WhenFileDoNotExist_ReturnFail()
77 | {
78 | // Arrange
79 | var storageClient = GetStorageClient();
80 |
81 | // Act
82 | var downloadedFileResult = await storageClient.DownloadFile(Guid.NewGuid()
83 | .ToString(), _downloadEndpoint);
84 |
85 | // Assert
86 | downloadedFileResult.IsFailed
87 | .Should()
88 | .BeTrue();
89 | downloadedFileResult.GetError()
90 | .Value
91 | .ErrorCode
92 | .Should()
93 | .Be(HttpStatusCode.InternalServerError.ToString());
94 | }
95 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseStreamControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net;
4 | using System.Threading.Tasks;
5 | using FluentAssertions;
6 | using ManagedCode.Storage.Core.Helpers;
7 | using ManagedCode.Storage.Core.Models;
8 | using ManagedCode.Storage.Tests.Common;
9 | using ManagedCode.Storage.Tests.Constants;
10 | using Xunit;
11 |
12 | namespace ManagedCode.Storage.Tests.AspNetTests.Abstracts;
13 |
14 | public abstract class BaseStreamControllerTests : BaseControllerTests
15 | {
16 | private readonly string _streamEndpoint;
17 | private readonly string _uploadEndpoint;
18 |
19 | protected BaseStreamControllerTests(StorageTestApplication testApplication, string apiEndpoint) : base(testApplication, apiEndpoint)
20 | {
21 | _streamEndpoint = string.Format(ApiEndpoints.Base.StreamFile, ApiEndpoint);
22 | _uploadEndpoint = string.Format(ApiEndpoints.Base.UploadFile, ApiEndpoint);
23 | }
24 |
25 | [Fact]
26 | public async Task StreamFile_WhenFileExists_SaveToTempStorage_ReturnSuccess()
27 | {
28 | // Arrange
29 | var storageClient = GetStorageClient();
30 | var contentName = "file";
31 | var extension = ".txt";
32 | await using var localFile = LocalFile.FromRandomNameWithExtension(extension);
33 | FileHelper.GenerateLocalFile(localFile, 1);
34 | var fileCRC = Crc32Helper.Calculate(await localFile.ReadAllBytesAsync());
35 | var uploadFileBlob = await storageClient.UploadFile(localFile.FileStream, _uploadEndpoint, contentName);
36 |
37 | // Act
38 | var streamFileResult = await storageClient.GetFileStream(uploadFileBlob.Value.FullName, _streamEndpoint);
39 |
40 | // Assert
41 | streamFileResult.IsSuccess
42 | .Should()
43 | .BeTrue();
44 | streamFileResult.Should()
45 | .NotBeNull();
46 |
47 | await using var stream = streamFileResult.Value;
48 | await using var newLocalFile = await LocalFile.FromStreamAsync(stream, Path.GetTempPath(), Guid.NewGuid()
49 | .ToString("N") + extension);
50 |
51 | var streamedFileCRC = Crc32Helper.CalculateFileCrc(newLocalFile.FilePath);
52 | streamedFileCRC.Should()
53 | .Be(fileCRC);
54 | }
55 |
56 | [Fact]
57 | public async Task StreamFile_WhenFileDoNotExist_ReturnFail()
58 | {
59 | // Arrange
60 | var storageClient = GetStorageClient();
61 |
62 | // Act
63 | var streamFileResult = await storageClient.GetFileStream(Guid.NewGuid()
64 | .ToString(), _streamEndpoint);
65 |
66 | // Assert
67 | streamFileResult.IsFailed
68 | .Should()
69 | .BeTrue();
70 | streamFileResult.GetError()
71 | .Value
72 | .ErrorCode
73 | .Should()
74 | .Be(HttpStatusCode.InternalServerError.ToString());
75 | }
76 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseUploadControllerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using FluentAssertions;
5 | using ManagedCode.Storage.Core.Helpers;
6 | using ManagedCode.Storage.Core.Models;
7 | using ManagedCode.Storage.Tests.Common;
8 | using ManagedCode.Storage.Tests.Constants;
9 | using Xunit;
10 |
11 | namespace ManagedCode.Storage.Tests.AspNetTests.Abstracts;
12 |
13 | public abstract class BaseUploadControllerTests : BaseControllerTests
14 | {
15 | private readonly string _uploadEndpoint;
16 | private readonly string _uploadLargeFile;
17 |
18 | protected BaseUploadControllerTests(StorageTestApplication testApplication, string apiEndpoint) : base(testApplication, apiEndpoint)
19 | {
20 | _uploadEndpoint = string.Format(ApiEndpoints.Base.UploadFile, ApiEndpoint);
21 | _uploadLargeFile = string.Format(ApiEndpoints.Base.UploadLargeFile, ApiEndpoint);
22 | }
23 |
24 | [Fact]
25 | public async Task UploadFileFromStream_WhenFileValid_ReturnSuccess()
26 | {
27 | // Arrange
28 | var storageClient = GetStorageClient();
29 | var contentName = "file";
30 |
31 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
32 | FileHelper.GenerateLocalFile(localFile, 1);
33 |
34 | // Act
35 | var result = await storageClient.UploadFile(localFile.FileStream, _uploadEndpoint, contentName);
36 |
37 | // Assert
38 | result.IsSuccess
39 | .Should()
40 | .BeTrue();
41 | result.Value
42 | .Should()
43 | .NotBeNull();
44 | }
45 |
46 | [Fact]
47 | public async Task UploadFileFromStream_WhenFileSizeIsForbidden_ReturnFail()
48 | {
49 | // Arrange
50 | var storageClient = GetStorageClient();
51 | var contentName = "file";
52 |
53 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
54 | FileHelper.GenerateLocalFile(localFile, 200);
55 |
56 | // Act
57 | var result = await storageClient.UploadFile(localFile.FileStream, _uploadEndpoint, contentName);
58 |
59 | // Assert
60 | result.IsFailed
61 | .Should()
62 | .BeTrue();
63 | result.GetError()
64 | .Value
65 | .ErrorCode
66 | .Should()
67 | .Be(HttpStatusCode.BadRequest.ToString());
68 | }
69 |
70 | [Fact]
71 | public async Task UploadFileFromFileInfo_WhenFileValid_ReturnSuccess()
72 | {
73 | // Arrange
74 | var storageClient = GetStorageClient();
75 | var fileName = "test.txt";
76 | var contentName = "file";
77 |
78 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
79 | FileHelper.GenerateLocalFile(localFile, 1);
80 |
81 | // Act
82 | var result = await storageClient.UploadFile(localFile.FileInfo, _uploadEndpoint, contentName);
83 |
84 | // Assert
85 | result.IsSuccess
86 | .Should()
87 | .BeTrue();
88 | result.Value
89 | .Should()
90 | .NotBeNull();
91 | }
92 |
93 | [Fact]
94 | public async Task UploadFileFromBytes_WhenFileValid_ReturnSuccess()
95 | {
96 | // Arrange
97 | var storageClient = GetStorageClient();
98 | var fileName = "test.txt";
99 | var contentName = "file";
100 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
101 | FileHelper.GenerateLocalFile(localFile, 1);
102 |
103 | var fileAsBytes = await localFile.ReadAllBytesAsync();
104 |
105 | // Act
106 | var result = await storageClient.UploadFile(fileAsBytes, _uploadEndpoint, contentName);
107 |
108 | // Assert
109 | result.IsSuccess
110 | .Should()
111 | .BeTrue();
112 | result.Value
113 | .Should()
114 | .NotBeNull();
115 | }
116 |
117 | [Fact]
118 | public async Task UploadFileFromBase64String_WhenFileValid_ReturnSuccess()
119 | {
120 | // Arrange
121 | var storageClient = GetStorageClient();
122 | var fileName = "test.txt";
123 | var contentName = "file";
124 |
125 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
126 | FileHelper.GenerateLocalFile(localFile, 1);
127 |
128 | var fileAsBytes = await localFile.ReadAllBytesAsync();
129 | var fileAsString64 = Convert.ToBase64String(fileAsBytes);
130 |
131 | // Act
132 | var result = await storageClient.UploadFile(fileAsString64, _uploadEndpoint, contentName);
133 |
134 | // Assert
135 | result.IsSuccess
136 | .Should()
137 | .BeTrue();
138 | result.Value
139 | .Should()
140 | .NotBeNull();
141 | }
142 |
143 | [Fact]
144 | public async Task UploadLargeFile_WhenFileValid_ReturnSuccess()
145 | {
146 | // Arrange
147 | var storageClient = GetStorageClient();
148 |
149 | await using var localFile = LocalFile.FromRandomNameWithExtension(".txt");
150 | FileHelper.GenerateLocalFile(localFile, 50);
151 | var crc32 = Crc32Helper.CalculateFileCrc(localFile.FilePath);
152 | storageClient.SetChunkSize(4096000);
153 |
154 | // Act
155 | var result = await storageClient.UploadLargeFile(localFile.FileStream, _uploadLargeFile + "/upload", _uploadLargeFile + "/complete", null);
156 |
157 | // Assert
158 | result.IsSuccess
159 | .Should()
160 | .BeTrue();
161 | result.Value
162 | .Should()
163 | .Be(crc32);
164 | }
165 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Azure/AzureDownloadControllerTests.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Tests.AspNetTests.Abstracts;
2 | using ManagedCode.Storage.Tests.Common;
3 | using ManagedCode.Storage.Tests.Constants;
4 |
5 | namespace ManagedCode.Storage.Tests.AspNetTests.Azure;
6 |
7 | public class AzureDownloadControllerTests : BaseDownloadControllerTests
8 | {
9 | public AzureDownloadControllerTests(StorageTestApplication testApplication) : base(testApplication, ApiEndpoints.Azure)
10 | {
11 | }
12 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Azure/AzureStreamControllerTests.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Tests.AspNetTests.Abstracts;
2 | using ManagedCode.Storage.Tests.Common;
3 | using ManagedCode.Storage.Tests.Constants;
4 |
5 | namespace ManagedCode.Storage.Tests.AspNetTests.Azure;
6 |
7 | public class AzureStreamControllerTests : BaseStreamControllerTests
8 | {
9 | public AzureStreamControllerTests(StorageTestApplication testApplication) : base(testApplication, ApiEndpoints.Azure)
10 | {
11 | }
12 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/AspNetTests/Azure/AzureUploadControllerTests.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Tests.AspNetTests.Abstracts;
2 | using ManagedCode.Storage.Tests.Common;
3 | using ManagedCode.Storage.Tests.Constants;
4 |
5 | namespace ManagedCode.Storage.Tests.AspNetTests.Azure;
6 |
7 | public class AzureUploadControllerTests : BaseUploadControllerTests
8 | {
9 | public AzureUploadControllerTests(StorageTestApplication testApplication) : base(testApplication, ApiEndpoints.Azure)
10 | {
11 | }
12 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/BaseContainer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using DotNet.Testcontainers.Containers;
6 | using FluentAssertions;
7 | using ManagedCode.Storage.Core;
8 | using ManagedCode.Storage.Core.Models;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Xunit;
11 |
12 | namespace ManagedCode.Storage.Tests.Common;
13 |
14 | public abstract class BaseContainer : IAsyncLifetime where T : IContainer
15 | {
16 | protected T Container { get; private set; }
17 |
18 | protected IStorage Storage { get; private set; }
19 | protected ServiceProvider ServiceProvider { get; private set; }
20 |
21 |
22 | public async Task InitializeAsync()
23 | {
24 | Container = Build();
25 | await Container.StartAsync();
26 | ServiceProvider = ConfigureServices();
27 | Storage = ServiceProvider.GetService()!;
28 | }
29 |
30 | public Task DisposeAsync()
31 | {
32 | return Container.DisposeAsync()
33 | .AsTask();
34 | }
35 |
36 | protected abstract T Build();
37 | protected abstract ServiceProvider ConfigureServices();
38 |
39 | protected async Task UploadTestFileAsync(string? directory = null)
40 | {
41 | var file = await GetTestFileAsync();
42 |
43 | UploadOptions options = new() { FileName = file.Name, Directory = directory };
44 | var result = await Storage.UploadAsync(file.OpenRead(), options);
45 | result.IsSuccess
46 | .Should()
47 | .BeTrue();
48 |
49 | return file;
50 | }
51 |
52 | protected async Task> UploadTestFileListAsync(string? directory = null, int? count = 10)
53 | {
54 | List fileList = new();
55 |
56 | for (var i = 0; i < count; i++)
57 | {
58 | var file = await UploadTestFileAsync(directory);
59 | fileList.Add(file);
60 | }
61 |
62 | return fileList;
63 | }
64 |
65 | protected async Task GetTestFileAsync()
66 | {
67 | var fileName = Path.GetTempFileName();
68 | var fs = File.OpenWrite(fileName);
69 | var sw = new StreamWriter(fs);
70 |
71 | for (var i = 0; i < 1000; i++)
72 | await sw.WriteLineAsync(Guid.NewGuid()
73 | .ToString());
74 |
75 | await sw.DisposeAsync();
76 | await fs.DisposeAsync();
77 |
78 | return new FileInfo(fileName);
79 | }
80 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/ContainerImages.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Storage.Tests.Common;
2 |
3 | public class ContainerImages
4 | {
5 | public const string Azurite = "mcr.microsoft.com/azure-storage/azurite:latest";
6 | public const string FakeGCSServer = "fsouza/fake-gcs-server:latest";
7 | public const string LocalStack = "localstack/localstack:latest";
8 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/EmptyContainer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using DotNet.Testcontainers.Configurations;
7 | using DotNet.Testcontainers.Containers;
8 | using DotNet.Testcontainers.Images;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Logging.Abstractions;
11 |
12 | namespace ManagedCode.Storage.Tests.Common;
13 |
14 | public sealed class EmptyContainer : IContainer
15 | {
16 | public ValueTask DisposeAsync()
17 | {
18 | return ValueTask.CompletedTask;
19 | }
20 |
21 | public ushort GetMappedPublicPort(int containerPort)
22 | {
23 | throw new NotImplementedException();
24 | }
25 |
26 | public ushort GetMappedPublicPort(string containerPort)
27 | {
28 | throw new NotImplementedException();
29 | }
30 |
31 | public Task GetExitCodeAsync(CancellationToken ct = new())
32 | {
33 | throw new NotImplementedException();
34 | }
35 |
36 | public Task<(string Stdout, string Stderr)> GetLogsAsync(DateTime since = new(), DateTime until = new(), bool timestampsEnabled = true,
37 | CancellationToken ct = new())
38 | {
39 | throw new NotImplementedException();
40 | }
41 |
42 | public Task StartAsync(CancellationToken ct = new())
43 | {
44 | return Task.CompletedTask;
45 | }
46 |
47 | public Task StopAsync(CancellationToken ct = new())
48 | {
49 | throw new NotImplementedException();
50 | }
51 |
52 | public async Task PauseAsync(CancellationToken ct = new CancellationToken())
53 | {
54 | throw new NotImplementedException();
55 | }
56 |
57 | public async Task UnpauseAsync(CancellationToken ct = new CancellationToken())
58 | {
59 | throw new NotImplementedException();
60 | }
61 |
62 | public Task CopyAsync(byte[] fileContent, string filePath,
63 | UnixFileModes fileMode = UnixFileModes.None | UnixFileModes.OtherRead | UnixFileModes.GroupRead | UnixFileModes.UserWrite |
64 | UnixFileModes.UserRead, CancellationToken ct = new())
65 | {
66 | throw new NotImplementedException();
67 | }
68 |
69 | public Task CopyAsync(string source, string target,
70 | UnixFileModes fileMode = UnixFileModes.None | UnixFileModes.OtherRead | UnixFileModes.GroupRead | UnixFileModes.UserWrite |
71 | UnixFileModes.UserRead, CancellationToken ct = new())
72 | {
73 | throw new NotImplementedException();
74 | }
75 |
76 | public Task CopyAsync(DirectoryInfo source, string target,
77 | UnixFileModes fileMode = UnixFileModes.None | UnixFileModes.OtherRead | UnixFileModes.GroupRead | UnixFileModes.UserWrite |
78 | UnixFileModes.UserRead, CancellationToken ct = new())
79 | {
80 | throw new NotImplementedException();
81 | }
82 |
83 | public Task CopyAsync(FileInfo source, string target,
84 | UnixFileModes fileMode = UnixFileModes.None | UnixFileModes.OtherRead | UnixFileModes.GroupRead | UnixFileModes.UserWrite |
85 | UnixFileModes.UserRead, CancellationToken ct = new())
86 | {
87 | throw new NotImplementedException();
88 | }
89 |
90 | public Task ReadFileAsync(string filePath, CancellationToken ct = new())
91 | {
92 | throw new NotImplementedException();
93 | }
94 |
95 | public Task ExecAsync(IList command, CancellationToken ct = new())
96 | {
97 | throw new NotImplementedException();
98 | }
99 |
100 | public DateTime CreatedTime { get; }
101 | public DateTime StartedTime { get; }
102 | public DateTime StoppedTime { get; }
103 | public DateTime PausedTime { get; }
104 | public DateTime UnpausedTime { get; }
105 |
106 | public ILogger Logger { get; } = NullLogger.Instance;
107 | public string Id { get; } = "none";
108 | public string Name { get; } = "none";
109 | public string IpAddress { get; } = "none";
110 | public string MacAddress { get; } = "none";
111 | public string Hostname { get; } = "none";
112 | public IImage Image { get; } = new DockerImage("none");
113 | public TestcontainersStates State { get; } = TestcontainersStates.Running;
114 | public TestcontainersHealthStatus Health { get; } = TestcontainersHealthStatus.Healthy;
115 | public long HealthCheckFailingStreak { get; } = 0;
116 | public event EventHandler? Creating;
117 | public event EventHandler? Starting;
118 | public event EventHandler? Stopping;
119 | public event EventHandler? Pausing;
120 | public event EventHandler? Unpausing;
121 | public event EventHandler? Created;
122 | public event EventHandler? Started;
123 | public event EventHandler? Stopped;
124 | public event EventHandler? Paused;
125 | public event EventHandler? Unpaused;
126 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/FileHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using ManagedCode.MimeTypes;
5 | using ManagedCode.Storage.Core.Models;
6 | using Microsoft.AspNetCore.Http;
7 |
8 | namespace ManagedCode.Storage.Tests.Common;
9 |
10 | public static class FileHelper
11 | {
12 | private static readonly Random Random = new();
13 |
14 | public static LocalFile GenerateLocalFile(LocalFile localFile, int byteSize)
15 | {
16 | var fs = localFile.FileStream;
17 |
18 | fs.Seek(byteSize, SeekOrigin.Begin);
19 | fs.WriteByte(0);
20 | fs.Close();
21 |
22 | return localFile;
23 | }
24 |
25 | public static LocalFile GenerateLocalFile(string fileName, int byteSize)
26 | {
27 | var path = Path.Combine(Path.GetTempPath(), fileName);
28 | var localFile = new LocalFile(path);
29 |
30 | var fs = localFile.FileStream;
31 |
32 | fs.Seek(byteSize, SeekOrigin.Begin);
33 | fs.WriteByte(0);
34 | fs.Close();
35 |
36 | return localFile;
37 | }
38 |
39 | public static LocalFile GenerateLocalFileWithData(LocalFile file, int sizeInBytes)
40 | {
41 | using (var fileStream = file.FileStream)
42 | {
43 | var random = new Random();
44 | var buffer = new byte[1024]; // Buffer for writing in chunks
45 |
46 | while (sizeInBytes > 0)
47 | {
48 | var bytesToWrite = Math.Min(sizeInBytes, buffer.Length);
49 |
50 | for (var i = 0; i < bytesToWrite; i++)
51 | {
52 | buffer[i] = (byte)random.Next(65, 91); // 'A' to 'Z'
53 | if (random.Next(2) == 0)
54 | buffer[i] = (byte)random.Next(97, 123); // 'a' to 'z'
55 | }
56 |
57 | fileStream.Write(buffer, 0, bytesToWrite);
58 | sizeInBytes -= bytesToWrite;
59 | }
60 | }
61 |
62 | return file;
63 | }
64 |
65 | public static IFormFile GenerateFormFile(string fileName, int byteSize)
66 | {
67 | var localFile = GenerateLocalFile(fileName, byteSize);
68 |
69 | var ms = new MemoryStream();
70 | localFile.FileStream.CopyTo(ms);
71 | var formFile = new FormFile(ms, 0, ms.Length, fileName, fileName)
72 | {
73 | Headers = new HeaderDictionary(),
74 | ContentType = MimeHelper.GetMimeType(localFile.FileInfo.Extension)
75 | };
76 |
77 | localFile.Dispose();
78 |
79 | return formFile;
80 | }
81 |
82 | public static string GenerateRandomFileName()
83 | {
84 | string[] extensions = { "txt", "jpg", "png", "pdf", "docx", "xlsx", "pptx", "mp3", "mp4", "zip" };
85 | var randomExtension = extensions[Random.Next(extensions.Length)];
86 | return $"{Guid.NewGuid().ToString("N").ToLowerInvariant()}.{randomExtension}";
87 | }
88 |
89 | public static string GenerateRandomFileContent(int charCount = 250_000)
90 | {
91 | const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789_abcdefghijklmnopqrstuvwxyz";
92 |
93 | return new string(Enumerable.Repeat(chars, charCount)
94 | .Select(s => s[Random.Next(s.Length)])
95 | .ToArray());
96 | }
97 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/StorageTestApplication.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using Amazon.S3;
5 | using ManagedCode.Storage.Azure.Extensions;
6 | using ManagedCode.Storage.Azure.Options;
7 | using ManagedCode.Storage.Core.Extensions;
8 | using ManagedCode.Storage.FileSystem.Extensions;
9 | using ManagedCode.Storage.FileSystem.Options;
10 | using ManagedCode.Storage.Aws.Extensions;
11 | using ManagedCode.Storage.Aws.Options;
12 | using ManagedCode.Storage.Tests.Common.TestApp;
13 | using Microsoft.AspNetCore.Mvc.Testing;
14 | using Microsoft.Extensions.Hosting;
15 | using Testcontainers.Azurite;
16 | using Testcontainers.LocalStack;
17 | using Google.Cloud.Storage.V1;
18 | using ManagedCode.Storage.Google.Extensions;
19 | using ManagedCode.Storage.Google.Options;
20 | using Testcontainers.FakeGcsServer;
21 | using Xunit;
22 |
23 | namespace ManagedCode.Storage.Tests.Common;
24 |
25 | [CollectionDefinition(nameof(StorageTestApplication))]
26 | public class StorageTestApplication : WebApplicationFactory, ICollectionFixture
27 | {
28 | private readonly AzuriteContainer _azuriteContainer;
29 | private readonly LocalStackContainer _localStackContainer;
30 | private readonly FakeGcsServerContainer _gcpContainer;
31 |
32 | public StorageTestApplication()
33 | {
34 | _azuriteContainer = new AzuriteBuilder()
35 | .WithImage(ContainerImages.Azurite)
36 | .Build();
37 |
38 | _localStackContainer = new LocalStackBuilder()
39 | .WithImage(ContainerImages.LocalStack)
40 | .Build();
41 |
42 | _gcpContainer = new FakeGcsServerBuilder()
43 | .WithImage(ContainerImages.FakeGCSServer)
44 | .Build();
45 |
46 | Task.WaitAll(
47 | _azuriteContainer.StartAsync(),
48 | _localStackContainer.StartAsync(),
49 | _gcpContainer.StartAsync()
50 | );
51 | }
52 |
53 | protected override IHost CreateHost(IHostBuilder builder)
54 | {
55 | builder.ConfigureServices(services =>
56 | {
57 | services.AddStorageFactory();
58 |
59 | services.AddFileSystemStorage(new FileSystemStorageOptions
60 | {
61 | BaseFolder = Path.Combine(Environment.CurrentDirectory, "managed-code-bucket")
62 | });
63 |
64 | services.AddAzureStorage(new AzureStorageOptions
65 | {
66 | Container = "managed-code-bucket",
67 | ConnectionString = _azuriteContainer.GetConnectionString()
68 | });
69 |
70 | services.AddGCPStorage(new GCPStorageOptions
71 | {
72 | BucketOptions = new BucketOptions
73 | {
74 | ProjectId = "api-project-0000000000000",
75 | Bucket = "managed-code-bucket"
76 | },
77 | StorageClientBuilder = new StorageClientBuilder
78 | {
79 | UnauthenticatedAccess = true,
80 | BaseUri = _gcpContainer.GetConnectionString()
81 | }
82 | });
83 |
84 |
85 | var config = new AmazonS3Config();
86 | config.ServiceURL = _localStackContainer.GetConnectionString();
87 |
88 | services.AddAWSStorage(new AWSStorageOptions
89 | {
90 | PublicKey = "localkey",
91 | SecretKey = "localsecret",
92 | Bucket = "managed-code-bucket",
93 | OriginalOptions = config
94 | });
95 |
96 | });
97 |
98 | return base.CreateHost(builder);
99 | }
100 |
101 | public override async ValueTask DisposeAsync()
102 | {
103 | await Task.WhenAll(
104 | _azuriteContainer.DisposeAsync().AsTask(),
105 | _localStackContainer.DisposeAsync().AsTask(),
106 | _gcpContainer.DisposeAsync().AsTask()
107 | );
108 | }
109 | }
--------------------------------------------------------------------------------
/Tests/ManagedCode.Storage.Tests/Common/TestApp/Controllers/AzureTestController.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Storage.Azure;
2 | using ManagedCode.Storage.Tests.Common.TestApp.Controllers.Base;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace ManagedCode.Storage.Tests.Common.TestApp.Controllers;
6 |
7 | [Route("azure")]
8 | [ApiController]
9 | public class AzureTestController(IAzureStorage storage) : BaseTestController