├── .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(storage); -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Common/TestApp/Controllers/Base/BaseTestController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Amazon.Runtime.Internal; 7 | using ManagedCode.Communication; 8 | using ManagedCode.Storage.Core; 9 | using ManagedCode.Storage.Core.Helpers; 10 | using ManagedCode.Storage.Core.Models; 11 | using ManagedCode.Storage.Server; 12 | using ManagedCode.Storage.Server.Extensions; 13 | using ManagedCode.Storage.Server.Extensions.Controller; 14 | using ManagedCode.Storage.Server.Models; 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.AspNetCore.Mvc; 17 | using IResult = Microsoft.AspNetCore.Http.IResult; 18 | 19 | namespace ManagedCode.Storage.Tests.Common.TestApp.Controllers.Base; 20 | 21 | [ApiController] 22 | public abstract class BaseTestController : ControllerBase where TStorage : IStorage 23 | { 24 | protected readonly int ChunkSize; 25 | protected readonly ResponseContext ResponseData; 26 | protected readonly IStorage Storage; 27 | 28 | protected BaseTestController(TStorage storage) 29 | { 30 | Storage = storage; 31 | ResponseData = new ResponseContext(); 32 | ChunkSize = 100000000; 33 | } 34 | 35 | [HttpPost("upload")] 36 | public async Task> UploadFileAsync([FromForm] IFormFile file, CancellationToken cancellationToken) 37 | { 38 | if (Request.HasFormContentType is false) 39 | return Result.Fail("invalid body"); 40 | 41 | return await Result.From(() => this.UploadFormFileAsync(Storage, file, cancellationToken:cancellationToken), cancellationToken); 42 | } 43 | 44 | [HttpGet("download/{fileName}")] 45 | public async Task DownloadFileAsync([FromRoute] string fileName) 46 | { 47 | return await this.DownloadAsFileResultAsync(Storage, fileName); 48 | } 49 | 50 | [HttpGet("stream/{fileName}")] 51 | public async Task StreamFileAsync([FromRoute] string fileName) 52 | { 53 | return await this.DownloadAsStreamAsync(Storage, fileName); 54 | } 55 | 56 | [HttpGet("download-bytes/{fileName}")] 57 | public async Task DownloadBytesAsync([FromRoute] string fileName) 58 | { 59 | return await this.DownloadAsFileContentResultAsync(Storage, fileName); 60 | } 61 | 62 | [HttpPost("upload-chunks/upload")] 63 | public async Task UploadLargeFile([FromForm] FileUploadPayload file, CancellationToken cancellationToken = default) 64 | { 65 | try 66 | { 67 | var newpath = Path.Combine(Path.GetTempPath(), $"{file.File.FileName}_{file.Payload.ChunkIndex}"); 68 | 69 | await using (var fs = System.IO.File.Create(newpath)) 70 | { 71 | var bytes = new byte[file.Payload.ChunkSize]; 72 | var bytesRead = 0; 73 | var fileStream = file.File.OpenReadStream(); 74 | while ((bytesRead = await fileStream.ReadAsync(bytes, 0, bytes.Length, cancellationToken)) > 0) 75 | await fs.WriteAsync(bytes, 0, bytesRead, cancellationToken); 76 | } 77 | } 78 | catch (Exception ex) 79 | { 80 | return Result.Fail(ex.Message); 81 | } 82 | 83 | return Result.Succeed(); 84 | } 85 | 86 | [HttpPost("upload-chunks/complete")] 87 | public async Task> UploadComplete([FromBody] string fileName, CancellationToken cancellationToken = default) 88 | { 89 | uint fileCRC = 0; 90 | try 91 | { 92 | var tempPath = Path.GetTempPath(); 93 | var newPath = Path.Combine(tempPath, $"{fileName}_merged"); 94 | var filePaths = Directory.GetFiles(tempPath) 95 | .Where(p => p.Contains(fileName)) 96 | .OrderBy(p => int.Parse(p.Split('_')[1])) 97 | .ToArray(); 98 | 99 | foreach (var filePath in filePaths) 100 | await MergeChunks(newPath, filePath, cancellationToken); 101 | 102 | fileCRC = Crc32Helper.CalculateFileCrc(newPath); 103 | } 104 | catch (Exception ex) 105 | { 106 | return Result.Fail(ex.Message); 107 | } 108 | 109 | return Result.Succeed(fileCRC); 110 | } 111 | 112 | private static async Task MergeChunks(string chunk1, string chunk2, CancellationToken cancellationToken) 113 | { 114 | long fileSize = 0; 115 | FileStream fs1 = null; 116 | FileStream fs2 = null; 117 | try 118 | { 119 | fs1 = System.IO.File.Open(chunk1, FileMode.Append); 120 | fs2 = System.IO.File.Open(chunk2, FileMode.Open); 121 | var fs2Content = new byte[fs2.Length]; 122 | await fs2.ReadAsync(fs2Content, 0, (int)fs2.Length, cancellationToken); 123 | await fs1.WriteAsync(fs2Content, 0, (int)fs2.Length, cancellationToken); 124 | } 125 | catch (Exception ex) 126 | { 127 | Console.WriteLine(ex.Message + " : " + ex.StackTrace); 128 | } 129 | finally 130 | { 131 | fileSize = fs1.Length; 132 | if (fs1 != null) fs1.Close(); 133 | if (fs2 != null) fs2.Close(); 134 | System.IO.File.Delete(chunk2); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Common/TestApp/HttpHostProgram.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Azure.Extensions; 2 | using ManagedCode.Storage.Tests.Common.TestApp.Controllers; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http.Features; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace ManagedCode.Storage.Tests.Common.TestApp; 8 | 9 | public class HttpHostProgram 10 | { 11 | public static void Main(string[] args) 12 | { 13 | var builder = WebApplication.CreateBuilder(args); 14 | 15 | builder.Services.AddControllers(); 16 | builder.Services.AddSignalR(); 17 | builder.Services.AddEndpointsApiExplorer(); 18 | 19 | // Configure form options for large file uploads 20 | builder.Services.Configure(options => 21 | { 22 | options.ValueLengthLimit = int.MaxValue; 23 | options.MultipartBodyLengthLimit = long.MaxValue; 24 | options.MultipartHeadersLengthLimit = int.MaxValue; 25 | }); 26 | 27 | 28 | var app = builder.Build(); 29 | 30 | app.UseRouting(); 31 | app.MapControllers(); 32 | 33 | app.Run(); 34 | } 35 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Constants/ApiEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Storage.Tests.Constants; 2 | 3 | public static class ApiEndpoints 4 | { 5 | public const string Azure = "azure"; 6 | 7 | public static class Base 8 | { 9 | public const string UploadFile = "{0}/upload"; 10 | public const string DownloadFile = "{0}/download/{1}"; 11 | public const string DownloadBytes = "{0}/download-bytes/{1}"; 12 | public const string StreamFile = "{0}/stream/{1}"; 13 | public const string UploadLargeFile = "{0}/upload-chunks"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/ExtensionsTests/FormFileExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FluentAssertions; 6 | using ManagedCode.Storage.Server; 7 | using ManagedCode.Storage.Server.Extensions.File; 8 | using ManagedCode.Storage.Tests.Common; 9 | using Microsoft.AspNetCore.Http; 10 | using Xunit; 11 | 12 | namespace ManagedCode.Storage.Tests.ExtensionsTests; 13 | 14 | public class FormFileExtensionsTests 15 | { 16 | [Fact] 17 | public async Task ToLocalFileAsync_SmallFile() 18 | { 19 | // Arrange 20 | const int size = 200 * 1024; // 200 KB 21 | var fileName = FileHelper.GenerateRandomFileName(); 22 | var formFile = FileHelper.GenerateFormFile(fileName, size); 23 | 24 | // Act 25 | var localFile = await formFile.ToLocalFileAsync(); 26 | 27 | // Assert 28 | localFile.FileStream.Length.Should().Be(formFile.Length); 29 | Path.GetExtension(localFile.Name).Should().Be(Path.GetExtension(formFile.FileName)); 30 | } 31 | 32 | [Fact] 33 | public async Task ToLocalFileAsync_LargeFile() 34 | { 35 | // Arrange 36 | const int size = 300 * 1024 * 1024; // 300 MB 37 | var fileName = FileHelper.GenerateRandomFileName(); 38 | var formFile = FileHelper.GenerateFormFile(fileName, size); 39 | 40 | // Act 41 | var localFile = await formFile.ToLocalFileAsync(); 42 | 43 | // Assert 44 | localFile.FileStream.Length.Should().Be(formFile.Length); 45 | Path.GetExtension(localFile.Name).Should().Be(Path.GetExtension(formFile.FileName)); 46 | } 47 | 48 | [Fact] 49 | public async Task ToLocalFilesAsync_SmallFiles() 50 | { 51 | // Arrange 52 | const int filesCount = 10; 53 | Random random = new(); 54 | FormFileCollection collection = new(); 55 | 56 | for (var i = 0; i < filesCount; i++) 57 | { 58 | var size = random.Next(10, 1000) * 1024; 59 | var fileName = FileHelper.GenerateRandomFileName(); 60 | collection.Add(FileHelper.GenerateFormFile(fileName, size)); 61 | } 62 | 63 | // Act 64 | var localFiles = await collection.ToLocalFilesAsync().ToListAsync(); 65 | 66 | // Assert 67 | localFiles.Count.Should().Be(filesCount); 68 | 69 | for (var i = 0; i < filesCount; i++) 70 | { 71 | localFiles[i].FileStream.Length.Should().Be(collection[i].Length); 72 | Path.GetExtension(localFiles[i].Name).Should().Be(Path.GetExtension(collection[i].FileName)); 73 | } 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/ExtensionsTests/ReplaceExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using FluentAssertions; 3 | using ManagedCode.Storage.Azure; 4 | using ManagedCode.Storage.Azure.Extensions; 5 | using ManagedCode.Storage.Azure.Options; 6 | using ManagedCode.Storage.Core; 7 | using ManagedCode.Storage.TestFakes; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Xunit; 10 | 11 | namespace ManagedCode.Storage.Tests.ExtensionsTests; 12 | 13 | public class ReplaceExtensionsTests 14 | { 15 | [Fact] 16 | public async Task ReplaceAzureStorageAsDefault() 17 | { 18 | var options = new AzureStorageOptions 19 | { 20 | Container = "test", 21 | ConnectionString = "ConnectionString" 22 | }; 23 | 24 | var services = new ServiceCollection(); 25 | 26 | services.AddAzureStorageAsDefault(options); 27 | 28 | services.ReplaceAzureStorageAsDefault(); 29 | 30 | var build = services.BuildServiceProvider(); 31 | build.GetService() 32 | !.GetType() 33 | .Should() 34 | .Be(typeof(FakeAzureStorage)); 35 | } 36 | 37 | [Fact] 38 | public async Task ReplaceAzureStorage() 39 | { 40 | var options = new AzureStorageOptions 41 | { 42 | Container = "test", 43 | ConnectionString = "ConnectionString" 44 | }; 45 | 46 | var services = new ServiceCollection(); 47 | 48 | services.AddAzureStorage(options); 49 | 50 | services.ReplaceAzureStorage(); 51 | 52 | var build = services.BuildServiceProvider(); 53 | build.GetService() 54 | !.GetType() 55 | .Should() 56 | .Be(typeof(FakeAzureStorage)); 57 | } 58 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/ExtensionsTests/StoragePrivderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Amazon.S3; 5 | using FluentAssertions; 6 | using Google.Cloud.Storage.V1; 7 | using ManagedCode.MimeTypes; 8 | using ManagedCode.Storage.Aws; 9 | using ManagedCode.Storage.Aws.Extensions; 10 | using ManagedCode.Storage.Aws.Options; 11 | using ManagedCode.Storage.Azure; 12 | using ManagedCode.Storage.Azure.Extensions; 13 | using ManagedCode.Storage.Azure.Options; 14 | using ManagedCode.Storage.Core; 15 | using ManagedCode.Storage.Core.Extensions; 16 | using ManagedCode.Storage.Core.Models; 17 | using ManagedCode.Storage.Core.Providers; 18 | using ManagedCode.Storage.FileSystem; 19 | using ManagedCode.Storage.FileSystem.Extensions; 20 | using ManagedCode.Storage.FileSystem.Options; 21 | using ManagedCode.Storage.Google; 22 | using ManagedCode.Storage.Google.Extensions; 23 | using ManagedCode.Storage.Google.Options; 24 | using ManagedCode.Storage.Server; 25 | using ManagedCode.Storage.Server.Extensions.Storage; 26 | using ManagedCode.Storage.Tests.Common; 27 | using Microsoft.Extensions.DependencyInjection; 28 | using Xunit; 29 | 30 | namespace ManagedCode.Storage.Tests.ExtensionsTests; 31 | 32 | public class StorageFactoryTests 33 | { 34 | public StorageFactoryTests() 35 | { 36 | ServiceProvider = ConfigureServices(); 37 | } 38 | 39 | public ServiceProvider ServiceProvider { get; } 40 | 41 | public static ServiceProvider ConfigureServices() 42 | { 43 | var services = new ServiceCollection(); 44 | 45 | services.AddFileSystemStorage(new FileSystemStorageOptions 46 | { 47 | BaseFolder = Path.Combine(Environment.CurrentDirectory, "managed-code-bucket") 48 | }); 49 | 50 | services.AddAzureStorage(new AzureStorageOptions 51 | { 52 | Container = "managed-code-bucket", 53 | ConnectionString = "UseDevelopmentStorage=true" 54 | }); 55 | 56 | services.AddGCPStorage(new GCPStorageOptions 57 | { 58 | BucketOptions = new BucketOptions 59 | { 60 | ProjectId = "api-project-0000000000000", 61 | Bucket = "managed-code-bucket" 62 | }, 63 | StorageClientBuilder = new StorageClientBuilder 64 | { 65 | UnauthenticatedAccess = true, 66 | BaseUri = "http://localhost:4443" 67 | } 68 | }); 69 | 70 | 71 | var config = new AmazonS3Config(); 72 | config.ServiceURL = "http://localhost:4443"; 73 | 74 | services.AddAWSStorage(new AWSStorageOptions 75 | { 76 | PublicKey = "localkey", 77 | SecretKey = "localsecret", 78 | Bucket = "managed-code-bucket", 79 | OriginalOptions = config 80 | }); 81 | 82 | 83 | // Add factory 84 | services.AddStorageFactory(); 85 | 86 | return services.BuildServiceProvider(); 87 | } 88 | 89 | [Fact] 90 | public void CreateAzureStorage() 91 | { 92 | var factory = ServiceProvider.GetRequiredService(); 93 | var storage = factory.CreateStorage(new AzureStorageOptions 94 | { 95 | Container = "managed-code-bucket", 96 | ConnectionString = "UseDevelopmentStorage=true" 97 | }); 98 | storage.GetType().Should().Be(typeof(AzureStorage)); 99 | } 100 | 101 | [Fact] 102 | public void CreateAwsStorage() 103 | { 104 | var config = new AmazonS3Config(); 105 | config.ServiceURL = "http://localhost:4443"; 106 | var factory = ServiceProvider.GetRequiredService(); 107 | var storage = factory.CreateStorage(new AWSStorageOptions 108 | { 109 | PublicKey = "localkey", 110 | SecretKey = "localsecret", 111 | Bucket = "managed-code-bucket", 112 | OriginalOptions = config 113 | }); 114 | storage.GetType().Should().Be(typeof(AWSStorage)); 115 | } 116 | 117 | [Fact] 118 | public void CreateGcpStorage() 119 | { 120 | var factory = ServiceProvider.GetRequiredService(); 121 | var storage = factory.CreateStorage(new GCPStorageOptions 122 | { 123 | BucketOptions = new BucketOptions 124 | { 125 | ProjectId = "api-project-0000000000000", 126 | Bucket = "managed-code-bucket" 127 | }, 128 | StorageClientBuilder = new StorageClientBuilder 129 | { 130 | UnauthenticatedAccess = true, 131 | BaseUri = "http://localhost:4443" 132 | } 133 | }); 134 | storage.GetType().Should().Be(typeof(GCPStorage)); 135 | } 136 | 137 | [Fact] 138 | public void UpdateAzureStorage() 139 | { 140 | var containerName = Guid.NewGuid().ToString(); 141 | var factory = ServiceProvider.GetRequiredService(); 142 | var storage = factory.CreateAzureStorage(containerName); 143 | storage.StorageClient 144 | .Should() 145 | .NotBeNull(); 146 | storage.StorageClient.Name 147 | .Should() 148 | .Be(containerName); 149 | 150 | } 151 | 152 | [Fact] 153 | public void UpdateAwsStorage() 154 | { 155 | var containerName = Guid.NewGuid().ToString(); 156 | var factory = ServiceProvider.GetRequiredService(); 157 | var storage = factory.CreateAWSStorage(containerName); 158 | storage.StorageClient 159 | .Should() 160 | .NotBeNull(); 161 | } 162 | 163 | [Fact] 164 | public void UpdateGcpStorage() 165 | { 166 | var containerName = Guid.NewGuid().ToString(); 167 | var factory = ServiceProvider.GetRequiredService(); 168 | var storage = factory.CreateGCPStorage(containerName); 169 | storage.StorageClient 170 | .Should() 171 | .NotBeNull(); 172 | } 173 | 174 | } 175 | 176 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/ManagedCode.Storage.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | false 5 | false 6 | 7 | 8 | 9 | trx%3bLogFileName=$(MSBuildProjectName).trx 10 | $(MSBuildThisFileDirectory) 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | all 41 | 42 | 43 | runtime; build; native; contentfiles; analyzers; buildtransitive 44 | all 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AWSBlobTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.LocalStack; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.AWS; 7 | 8 | public class AWSBlobTests : BlobTests 9 | { 10 | protected override LocalStackContainer Build() 11 | { 12 | return new LocalStackBuilder().WithImage(ContainerImages.LocalStack) 13 | .Build(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return AWSConfigurator.ConfigureServices(Container.GetConnectionString()); 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AWSConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Amazon.S3; 2 | using ManagedCode.Storage.Aws.Extensions; 3 | using ManagedCode.Storage.Aws.Options; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | // ReSharper disable MethodHasAsyncOverload 7 | 8 | namespace ManagedCode.Storage.Tests.Storages.AWS; 9 | 10 | public class AWSConfigurator 11 | { 12 | public static ServiceProvider ConfigureServices(string connectionString) 13 | { 14 | var services = new ServiceCollection(); 15 | 16 | var config = new AmazonS3Config(); 17 | config.ServiceURL = connectionString; 18 | 19 | services.AddAWSStorageAsDefault(opt => 20 | { 21 | opt.PublicKey = "localkey"; 22 | opt.SecretKey = "localsecret"; 23 | opt.Bucket = "managed-code-bucket"; 24 | opt.OriginalOptions = config; 25 | }); 26 | 27 | services.AddAWSStorage(new AWSStorageOptions 28 | { 29 | PublicKey = "localkey", 30 | SecretKey = "localsecret", 31 | Bucket = "managed-code-bucket", 32 | OriginalOptions = config 33 | }); 34 | return services.BuildServiceProvider(); 35 | } 36 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AWSContainerTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.LocalStack; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.AWS; 7 | 8 | public class AWSContainerTests : ContainerTests 9 | { 10 | protected override LocalStackContainer Build() 11 | { 12 | return new LocalStackBuilder() 13 | .WithImage(ContainerImages.LocalStack) 14 | .Build(); 15 | } 16 | 17 | protected override ServiceProvider ConfigureServices() 18 | { 19 | return AWSConfigurator.ConfigureServices(Container.GetConnectionString()); 20 | } 21 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AWSDownloadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.LocalStack; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.AWS; 7 | 8 | public class AWSDownloadTests : DownloadTests 9 | { 10 | protected override LocalStackContainer Build() 11 | { 12 | return new LocalStackBuilder().WithImage(ContainerImages.LocalStack) 13 | .Build(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return AWSConfigurator.ConfigureServices(Container.GetConnectionString()); 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AWSUploadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.LocalStack; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.AWS; 7 | 8 | public class AWSUploadTests : UploadTests 9 | { 10 | protected override LocalStackContainer Build() 11 | { 12 | return new LocalStackBuilder().WithImage(ContainerImages.LocalStack) 13 | .Build(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return AWSConfigurator.ConfigureServices(Container.GetConnectionString()); 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/AWS/AwsConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using ManagedCode.Storage.Aws; 4 | using ManagedCode.Storage.Aws.Extensions; 5 | using ManagedCode.Storage.Aws.Options; 6 | using ManagedCode.Storage.Core; 7 | using ManagedCode.Storage.Core.Exceptions; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Xunit; 10 | 11 | namespace ManagedCode.Storage.Tests.Storages.AWS; 12 | 13 | public class AwsConfigTests 14 | { 15 | [Fact] 16 | public void BadConfigurationForStorage_WithoutPublicKey_ThrowException() 17 | { 18 | var services = new ServiceCollection(); 19 | 20 | Action action = () => services.AddAWSStorage(opt => 21 | { 22 | opt.SecretKey = "localsecret"; 23 | opt.Bucket = "managed-code-bucket"; 24 | }); 25 | 26 | action.Should() 27 | .Throw(); 28 | } 29 | 30 | [Fact] 31 | public void BadConfigurationForStorage_WithoutSecretKey_ThrowException() 32 | { 33 | var services = new ServiceCollection(); 34 | 35 | Action action = () => services.AddAWSStorageAsDefault(opt => 36 | { 37 | opt.PublicKey = "localkey"; 38 | opt.Bucket = "managed-code-bucket"; 39 | }); 40 | 41 | action.Should() 42 | .Throw(); 43 | } 44 | 45 | [Fact] 46 | public void BadConfigurationForStorage_WithoutBucket_ThrowException() 47 | { 48 | var services = new ServiceCollection(); 49 | 50 | Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions 51 | { 52 | PublicKey = "localkey", 53 | SecretKey = "localsecret" 54 | }); 55 | 56 | action.Should() 57 | .Throw(); 58 | } 59 | 60 | [Fact] 61 | public void BadInstanceProfileConfigurationForStorage_WithoutBucket_ThrowException() 62 | { 63 | var services = new ServiceCollection(); 64 | 65 | Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions 66 | { 67 | RoleName = "my-role-name", 68 | UseInstanceProfileCredentials = true 69 | }); 70 | 71 | action.Should() 72 | .Throw(); 73 | } 74 | 75 | [Fact] 76 | public void ValidInstanceProfileConfigurationForStorage_WithRoleName_DoesNotThrowException() 77 | { 78 | var services = new ServiceCollection(); 79 | 80 | Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions 81 | { 82 | Bucket = "managed-code-bucket", 83 | RoleName = "my-role-name", 84 | UseInstanceProfileCredentials = true 85 | }); 86 | 87 | action.Should() 88 | .NotThrow(); 89 | } 90 | 91 | [Fact] 92 | public void ValidInstanceProfileConfigurationForStorage_WithoutRoleName_DoesNotThrowException() 93 | { 94 | var services = new ServiceCollection(); 95 | 96 | Action action = () => services.AddAWSStorageAsDefault(new AWSStorageOptions 97 | { 98 | Bucket = "managed-code-bucket", 99 | UseInstanceProfileCredentials = true 100 | }); 101 | 102 | action.Should() 103 | .NotThrow(); 104 | } 105 | 106 | [Fact] 107 | public void StorageAsDefaultTest() 108 | { 109 | var storage = AWSConfigurator.ConfigureServices("http://localhost") 110 | .GetService(); 111 | var defaultStorage = AWSConfigurator.ConfigureServices("http://localhost") 112 | .GetService(); 113 | storage?.GetType() 114 | .FullName 115 | .Should() 116 | .Be(defaultStorage?.GetType() 117 | .FullName); 118 | } 119 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Abstracts/BlobTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using DotNet.Testcontainers.Containers; 5 | using FluentAssertions; 6 | using ManagedCode.Storage.Core.Models; 7 | using ManagedCode.Storage.Tests.Common; 8 | using Xunit; 9 | 10 | namespace ManagedCode.Storage.Tests.Storages.Abstracts; 11 | 12 | public abstract class BlobTests : BaseContainer where T : IContainer 13 | { 14 | [Fact] 15 | public async Task GetBlobListAsync_WithoutOptions() 16 | { 17 | // Arrange 18 | var fileList = await UploadTestFileListAsync(); 19 | 20 | // Act 21 | var result = await Storage.GetBlobMetadataListAsync() 22 | .ToListAsync(); 23 | 24 | // Assert 25 | result.Count 26 | .Should() 27 | .Be(fileList.Count); 28 | 29 | foreach (var item in fileList) 30 | { 31 | var file = result.FirstOrDefault(f => f.Name == item.Name); 32 | file.Should() 33 | .NotBeNull(); 34 | 35 | await Storage.DeleteAsync(item.Name); 36 | } 37 | } 38 | 39 | [Fact] 40 | public virtual async Task GetBlobMetadataAsync_ShouldBeTrue() 41 | { 42 | // Arrange 43 | var fileInfo = await UploadTestFileAsync(); 44 | 45 | // Act 46 | var result = await Storage.GetBlobMetadataAsync(fileInfo.Name); 47 | 48 | // Assert 49 | result.IsSuccess 50 | .Should() 51 | .BeTrue(); 52 | result.Value!.Length 53 | .Should() 54 | .Be((ulong)fileInfo.Length); 55 | result.Value!.Name 56 | .Should() 57 | .Be(fileInfo.Name); 58 | 59 | await Storage.DeleteAsync(fileInfo.Name); 60 | } 61 | 62 | [Fact] 63 | public async Task DeleteAsync_WithoutOptions_ShouldTrue() 64 | { 65 | // Arrange 66 | var file = await UploadTestFileAsync(); 67 | 68 | // Act 69 | var result = await Storage.DeleteAsync(file.Name); 70 | 71 | // Assert 72 | result.IsSuccess 73 | .Should() 74 | .BeTrue(); 75 | result.Value 76 | .Should() 77 | .BeTrue(); 78 | } 79 | 80 | [Fact] 81 | public async Task DeleteAsync_WithoutOptions_IfFileDontExist_ShouldFalse() 82 | { 83 | // Arrange 84 | var blob = Guid.NewGuid() 85 | .ToString(); 86 | 87 | // Act 88 | var result = await Storage.DeleteAsync(blob); 89 | 90 | // Assert 91 | result.IsSuccess 92 | .Should() 93 | .BeTrue(); 94 | result.Value 95 | .Should() 96 | .BeFalse(); 97 | } 98 | 99 | [Fact] 100 | public async Task DeleteAsync_WithOptions_FromDirectory_ShouldTrue() 101 | { 102 | // Arrange 103 | var directory = "test-directory"; 104 | var file = await UploadTestFileAsync(directory); 105 | DeleteOptions options = new() { FileName = file.Name, Directory = directory }; 106 | 107 | // Act 108 | var result = await Storage.DeleteAsync(options); 109 | 110 | // Assert 111 | result.IsSuccess 112 | .Should() 113 | .BeTrue(); 114 | result.Value 115 | .Should() 116 | .BeTrue(); 117 | } 118 | 119 | [Fact] 120 | public async Task DeleteAsync_WithOptions_IfFileDontExist_FromDirectory_ShouldFalse() 121 | { 122 | // Arrange 123 | var directory = "test-directory"; 124 | DeleteOptions options = new() 125 | { 126 | FileName = Guid.NewGuid() 127 | .ToString(), 128 | Directory = directory 129 | }; 130 | 131 | // Act 132 | var result = await Storage.DeleteAsync(options); 133 | 134 | // Assert 135 | result.IsSuccess 136 | .Should() 137 | .BeTrue(); 138 | result.Value 139 | .Should() 140 | .BeFalse(); 141 | } 142 | 143 | [Fact] 144 | public async Task ExistsAsync_WithoutOptions_ShouldBeTrue() 145 | { 146 | // Arrange 147 | var fileInfo = await UploadTestFileAsync(); 148 | 149 | // Act 150 | var result = await Storage.ExistsAsync(fileInfo.Name); 151 | 152 | // Assert 153 | result.IsSuccess 154 | .Should() 155 | .BeTrue(); 156 | result.Value 157 | .Should() 158 | .BeTrue(); 159 | 160 | await Storage.DeleteAsync(fileInfo.Name); 161 | } 162 | 163 | [Fact] 164 | public async Task ExistsAsync_WithOptions_InDirectory_ShouldBeTrue() 165 | { 166 | // Arrange 167 | var directory = "test-directory"; 168 | var fileInfo = await UploadTestFileAsync(directory); 169 | ExistOptions options = new() { FileName = fileInfo.Name, Directory = directory }; 170 | 171 | // Act 172 | var result = await Storage.ExistsAsync(options); 173 | 174 | // Assert 175 | result.IsSuccess 176 | .Should() 177 | .BeTrue(); 178 | result.Value 179 | .Should() 180 | .BeTrue(); 181 | 182 | await Storage.DeleteAsync(fileInfo.Name); 183 | } 184 | 185 | [Fact] 186 | public async Task ExistsAsync_IfFileDontExist_WithoutOptions_ShouldBeFalse() 187 | { 188 | // Act 189 | var result = await Storage.ExistsAsync(Guid.NewGuid() 190 | .ToString()); 191 | 192 | // Assert 193 | result.IsSuccess 194 | .Should() 195 | .BeTrue(); 196 | result.Value 197 | .Should() 198 | .BeFalse(); 199 | } 200 | 201 | [Fact] 202 | public async Task ExistsAsync_IfFileFileExistInAnotherDirectory_WithOptions_ShouldBeFalse() 203 | { 204 | // Arrange 205 | var directory = "test-directory"; 206 | var fileInfo = await UploadTestFileAsync(directory); 207 | ExistOptions options = new() { FileName = fileInfo.Name, Directory = "another-directory" }; 208 | 209 | // Act 210 | var result = await Storage.ExistsAsync(options); 211 | 212 | // Assert 213 | result.IsSuccess 214 | .Should() 215 | .BeTrue(); 216 | result.Value 217 | .Should() 218 | .BeFalse(); 219 | 220 | await Storage.DeleteAsync(fileInfo.Name); 221 | } 222 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Abstracts/ContainerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using DotNet.Testcontainers.Containers; 5 | using FluentAssertions; 6 | using ManagedCode.Storage.Tests.Common; 7 | using Xunit; 8 | 9 | namespace ManagedCode.Storage.Tests.Storages.Abstracts; 10 | 11 | public abstract class ContainerTests : BaseContainer where T : IContainer 12 | { 13 | [Fact] 14 | public async Task CreateContainer_ShouldBeSuccess() 15 | { 16 | var container = await Storage.CreateContainerAsync(); 17 | container.IsSuccess 18 | .Should() 19 | .BeTrue(); 20 | } 21 | 22 | [Fact] 23 | public async Task CreateContainerAsync() 24 | { 25 | await FluentActions.Awaiting(() => Storage.CreateContainerAsync()) 26 | .Should() 27 | .NotThrowAsync(); 28 | } 29 | 30 | [Fact] 31 | public async Task RemoveContainer_ShouldBeSuccess() 32 | { 33 | var createResult = await Storage.CreateContainerAsync(); 34 | createResult.IsSuccess 35 | .Should() 36 | .BeTrue(); 37 | 38 | var result = await Storage.RemoveContainerAsync(); 39 | 40 | result.IsSuccess 41 | .Should() 42 | .BeTrue(); 43 | } 44 | 45 | [Fact] 46 | public async Task GetFileListAsyncTest() 47 | { 48 | await UploadTestFileAsync(); 49 | await UploadTestFileAsync(); 50 | await UploadTestFileAsync(); 51 | 52 | var files = await Storage.GetBlobMetadataListAsync() 53 | .ToListAsync(); 54 | files.Count 55 | .Should() 56 | .BeGreaterThanOrEqualTo(3); 57 | } 58 | 59 | [Fact] 60 | public async Task DeleteDirectory_ShouldBeSuccess() 61 | { 62 | // Arrange 63 | var directory = "test-directory"; 64 | await UploadTestFileListAsync(directory, 3); 65 | 66 | // Act 67 | var result = await Storage.DeleteDirectoryAsync(directory); 68 | var blobs = await Storage.GetBlobMetadataListAsync(directory) 69 | .ToListAsync(); 70 | 71 | // Assert 72 | result.IsSuccess 73 | .Should() 74 | .BeTrue(result.GetError().ToString()); 75 | 76 | blobs.Count 77 | .Should() 78 | .Be(0); 79 | } 80 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Abstracts/DownloadTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DotNet.Testcontainers.Containers; 3 | using FluentAssertions; 4 | using ManagedCode.Storage.Tests.Common; 5 | using Xunit; 6 | 7 | namespace ManagedCode.Storage.Tests.Storages.Abstracts; 8 | 9 | public abstract class DownloadTests : BaseContainer where T : IContainer 10 | { 11 | [Fact] 12 | public async Task DownloadAsync_WithoutOptions_AsLocalFile() 13 | { 14 | // Arrange 15 | var fileInfo = await UploadTestFileAsync(); 16 | 17 | // Act 18 | var result = await Storage.DownloadAsync(fileInfo.Name); 19 | 20 | // Assert 21 | result.IsSuccess 22 | .Should() 23 | .BeTrue(); 24 | result.Value!.FileInfo 25 | .Length 26 | .Should() 27 | .Be(fileInfo.Length); 28 | 29 | await Storage.DeleteAsync(fileInfo.Name); 30 | } 31 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Abstracts/StorageClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using DotNet.Testcontainers.Containers; 8 | using FluentAssertions; 9 | using ManagedCode.Storage.Client; 10 | using ManagedCode.Storage.Tests.Common; 11 | using Xunit; 12 | 13 | namespace ManagedCode.Storage.Tests.Storages.Abstracts; 14 | 15 | public abstract class StorageClientTests : BaseContainer where T : IContainer 16 | { 17 | private readonly HttpClient _httpClient; 18 | 19 | private readonly StorageClient _storageClient; 20 | 21 | public StorageClientTests() 22 | { 23 | _httpClient = new HttpClient(new FakeHttpMessageHandler(request => 24 | { 25 | var response = new HttpResponseMessage(HttpStatusCode.OK); 26 | if (request.Method == HttpMethod.Get && request.RequestUri.AbsoluteUri.Contains("loader.com")) 27 | { 28 | var contentStream = new MemoryStream(); 29 | using (var writer = new StreamWriter(contentStream)) 30 | { 31 | writer.Write("Test content"); 32 | writer.Flush(); 33 | contentStream.Position = 0; 34 | } 35 | 36 | response.Content = new StreamContent(contentStream); 37 | } 38 | 39 | return response; 40 | })); 41 | 42 | _storageClient = new StorageClient(_httpClient); 43 | } 44 | 45 | [Fact] 46 | public async Task DownloadFile_Successful() 47 | { 48 | var fileName = "testFile.txt"; 49 | var apiUrl = "https://loader.com"; 50 | 51 | var result = await _storageClient.DownloadFile(fileName, apiUrl); 52 | 53 | result.IsSuccess 54 | .Should() 55 | .BeTrue(); 56 | result.Should() 57 | .BeNull(); 58 | } 59 | 60 | [Fact] 61 | public async Task DownloadFile_HttpRequestException() 62 | { 63 | var fileName = "testFile.txt"; 64 | var apiUrl = "https://invalid-url.com"; 65 | 66 | var result = await _storageClient.DownloadFile(fileName, apiUrl); 67 | 68 | result.IsSuccess 69 | .Should() 70 | .BeFalse(); 71 | result.Value 72 | .Should() 73 | .BeNull(); 74 | } 75 | 76 | [Fact] 77 | public async Task DownloadFile_OtherException() 78 | { 79 | var fileName = "testFile.txt"; 80 | var apiUrl = "https://loader.com"; 81 | 82 | var result = await _storageClient.DownloadFile(fileName, apiUrl + "/invalid-endpoint"); 83 | 84 | result.IsSuccess 85 | .Should() 86 | .BeFalse(); 87 | result.Value 88 | .Should() 89 | .BeNull(); 90 | } 91 | 92 | private class FakeHttpMessageHandler : HttpMessageHandler 93 | { 94 | private readonly Func _responseProvider; 95 | 96 | public FakeHttpMessageHandler(Func responseProvider) 97 | { 98 | _responseProvider = responseProvider; 99 | } 100 | 101 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 102 | { 103 | return Task.FromResult(_responseProvider(request)); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Abstracts/StreamTests.cs: -------------------------------------------------------------------------------- 1 | using DotNet.Testcontainers.Containers; 2 | using ManagedCode.Storage.Tests.Common; 3 | 4 | namespace ManagedCode.Storage.Tests.Storages.Abstracts; 5 | 6 | public abstract class StreamTests : BaseContainer where T : IContainer 7 | { 8 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureBlobTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.Azurite; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.Azure; 7 | 8 | public class AzureBlobTests : BlobTests 9 | { 10 | protected override AzuriteContainer Build() 11 | { 12 | return new AzuriteBuilder() 13 | .WithImage(ContainerImages.Azurite) 14 | .WithCommand("--skipApiVersionCheck") 15 | .Build(); 16 | } 17 | 18 | protected override ServiceProvider ConfigureServices() 19 | { 20 | return AzureConfigurator.ConfigureServices(Container.GetConnectionString()); 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using ManagedCode.Storage.Azure; 4 | using ManagedCode.Storage.Azure.Extensions; 5 | using ManagedCode.Storage.Core; 6 | using ManagedCode.Storage.Core.Exceptions; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Xunit; 9 | 10 | namespace ManagedCode.Storage.Tests.Storages.Azure; 11 | 12 | public class AzureConfigTests 13 | { 14 | [Fact] 15 | public void BadConfigurationForStorage_WithoutContainer_ThrowException() 16 | { 17 | var services = new ServiceCollection(); 18 | 19 | Action action = () => services.AddAzureStorage(opt => { opt.ConnectionString = "test"; }); 20 | 21 | action.Should() 22 | .Throw(); 23 | } 24 | 25 | [Fact] 26 | public void BadConfigurationForStorage_WithoutConnectionString_ThrowException() 27 | { 28 | var services = new ServiceCollection(); 29 | 30 | Action action = () => services.AddAzureStorageAsDefault(options => 31 | { 32 | options.Container = "managed-code-bucket"; 33 | options.ConnectionString = null; 34 | }); 35 | 36 | action.Should() 37 | .Throw(); 38 | } 39 | 40 | [Fact] 41 | public void StorageAsDefaultTest() 42 | { 43 | var connectionString = "UseDevelopmentStorage=true"; 44 | var storage = AzureConfigurator.ConfigureServices(connectionString) 45 | .GetService(); 46 | var defaultStorage = AzureConfigurator.ConfigureServices(connectionString) 47 | .GetService(); 48 | storage?.GetType() 49 | .FullName 50 | .Should() 51 | .Be(defaultStorage?.GetType() 52 | .FullName); 53 | } 54 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureConfigurator.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Azure.Extensions; 2 | using ManagedCode.Storage.Azure.Options; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | // ReSharper disable MethodHasAsyncOverload 6 | 7 | namespace ManagedCode.Storage.Tests.Storages.Azure; 8 | 9 | public class AzureConfigurator 10 | { 11 | public static ServiceProvider ConfigureServices(string connectionString) 12 | { 13 | var services = new ServiceCollection(); 14 | 15 | services.AddAzureStorageAsDefault(opt => 16 | { 17 | opt.Container = "managed-code-bucket"; 18 | opt.ConnectionString = connectionString; 19 | }); 20 | 21 | services.AddAzureStorage(new AzureStorageOptions 22 | { 23 | Container = "managed-code-bucket", 24 | ConnectionString = connectionString 25 | }); 26 | return services.BuildServiceProvider(); 27 | } 28 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureContainerTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.Azurite; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.Azure; 7 | 8 | public class AzureContainerTests : ContainerTests 9 | { 10 | protected override AzuriteContainer Build() 11 | { 12 | return new AzuriteBuilder() 13 | .WithImage(ContainerImages.Azurite) 14 | .WithCommand("--skipApiVersionCheck") 15 | .Build(); 16 | } 17 | 18 | protected override ServiceProvider ConfigureServices() 19 | { 20 | return AzureConfigurator.ConfigureServices(Container.GetConnectionString()); 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureDataLakeTests.cs: -------------------------------------------------------------------------------- 1 | // using ManagedCode.Storage.Azure.DataLake.Extensions; 2 | // using Microsoft.Extensions.DependencyInjection; 3 | // 4 | // namespace ManagedCode.Storage.Tests.Azure; 5 | // 6 | // public class AzureDataLakeTests : StorageBaseTests 7 | // { 8 | // protected override ServiceProvider ConfigureServices() 9 | // { 10 | // var services = new ServiceCollection(); 11 | // services.AddLogging(); 12 | // 13 | // services.AddAzureDataLakeStorageAsDefault(opt => 14 | // { 15 | // opt.FileSystem = ""; 16 | // 17 | // opt.ConnectionString = 18 | // ""; 19 | // }); 20 | // 21 | // return services.BuildServiceProvider(); 22 | // } 23 | // } 24 | 25 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureDownloadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.Azurite; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.Azure; 7 | 8 | public class AzureDownloadTests : DownloadTests 9 | { 10 | protected override AzuriteContainer Build() 11 | { 12 | return new AzuriteBuilder() 13 | .WithImage(ContainerImages.Azurite) 14 | .WithCommand("--skipApiVersionCheck") 15 | .Build(); 16 | } 17 | 18 | protected override ServiceProvider ConfigureServices() 19 | { 20 | return AzureConfigurator.ConfigureServices(Container.GetConnectionString()); 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/Azure/AzureUploadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.Azurite; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.Azure; 7 | 8 | public class AzureUploadTests : UploadTests 9 | { 10 | protected override AzuriteContainer Build() 11 | { 12 | return new AzuriteBuilder() 13 | .WithImage(ContainerImages.Azurite) 14 | .WithCommand("--skipApiVersionCheck") 15 | .Build(); 16 | } 17 | 18 | protected override ServiceProvider ConfigureServices() 19 | { 20 | return AzureConfigurator.ConfigureServices(Container.GetConnectionString()); 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemBlobTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | // ReSharper disable MethodHasAsyncOverload 6 | 7 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 8 | 9 | public class FileSystemBlobTests : BlobTests 10 | { 11 | protected override EmptyContainer Build() 12 | { 13 | return new EmptyContainer(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return FileSystemConfigurator.ConfigureServices("managed-code-blob"); 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using ManagedCode.Storage.FileSystem.Extensions; 4 | using ManagedCode.Storage.FileSystem.Options; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 8 | 9 | public class FileSystemConfigurator 10 | { 11 | public static ServiceProvider ConfigureServices(string connectionString) 12 | { 13 | connectionString += Random.Shared.NextInt64(); 14 | var services = new ServiceCollection(); 15 | 16 | services.AddFileSystemStorageAsDefault(opt => { opt.BaseFolder = Path.Combine(Environment.CurrentDirectory, connectionString); }); 17 | services.AddFileSystemStorage(new FileSystemStorageOptions 18 | { 19 | BaseFolder = Path.Combine(Environment.CurrentDirectory, connectionString) 20 | }); 21 | return services.BuildServiceProvider(); 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemContainerTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 6 | 7 | public class FileSystemContainerTests : ContainerTests 8 | { 9 | protected override EmptyContainer Build() 10 | { 11 | return new EmptyContainer(); 12 | } 13 | 14 | protected override ServiceProvider ConfigureServices() 15 | { 16 | return FileSystemConfigurator.ConfigureServices("managed-code-blob"); 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemDownloadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 6 | 7 | public class FileSystemDownloadTests : DownloadTests 8 | { 9 | protected override EmptyContainer Build() 10 | { 11 | return new EmptyContainer(); 12 | } 13 | 14 | protected override ServiceProvider ConfigureServices() 15 | { 16 | return FileSystemConfigurator.ConfigureServices("managed-code-blob"); 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using ManagedCode.Storage.Core; 3 | using ManagedCode.Storage.FileSystem; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Xunit; 6 | 7 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 8 | 9 | public class FileSystemTests 10 | { 11 | [Fact] 12 | public void StorageAsDefaultTest() 13 | { 14 | var storage = FileSystemConfigurator.ConfigureServices("test") 15 | .GetService(); 16 | var defaultStorage = FileSystemConfigurator.ConfigureServices("test") 17 | .GetService(); 18 | storage?.GetType() 19 | .FullName 20 | .Should() 21 | .Be(defaultStorage?.GetType() 22 | .FullName); 23 | } 24 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/FileSystem/FileSystemUploadTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using FluentAssertions; 6 | using ManagedCode.Storage.Tests.Common; 7 | using ManagedCode.Storage.Tests.Storages.Abstracts; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Xunit; 10 | 11 | namespace ManagedCode.Storage.Tests.Storages.FileSystem; 12 | 13 | public class FileSystemUploadTests : UploadTests 14 | { 15 | protected override EmptyContainer Build() 16 | { 17 | return new EmptyContainer(); 18 | } 19 | 20 | protected override ServiceProvider ConfigureServices() 21 | { 22 | return FileSystemConfigurator.ConfigureServices("managed-code-blob"); 23 | } 24 | 25 | [Fact] 26 | public async Task UploadAsync_AsStream_CorrectlyOverwritesFiles() 27 | { 28 | // Arrange 29 | 30 | var uploadStream1 = new MemoryStream(90*1024); 31 | var buffer = new byte[90 * 1024]; 32 | var random = new Random(); 33 | random.NextBytes(buffer); 34 | uploadStream1.Write(buffer, 0, buffer.Length); 35 | 36 | var uploadStream2 = new MemoryStream(512); 37 | var zeroByteBuffer = new byte[512]; 38 | uploadStream2.Write(zeroByteBuffer); 39 | var filenameToUse = "UploadAsync_AsStream_CorrectlyOverwritesFiles.bin"; 40 | 41 | var temporaryDirectory = Path.GetTempPath(); 42 | 43 | // Act 44 | var firstResult = await Storage.UploadAsync(uploadStream1, options => 45 | { 46 | options.FileName = filenameToUse; 47 | options.Directory = temporaryDirectory; 48 | }); 49 | 50 | firstResult.IsSuccess.Should().BeTrue(); 51 | 52 | // let's download it 53 | var downloadedResult = await Storage.DownloadAsync(options => 54 | { 55 | options.FileName = filenameToUse; 56 | options.Directory = temporaryDirectory; 57 | }); 58 | downloadedResult.IsSuccess.Should().BeTrue(); 59 | // size 60 | downloadedResult.Value!.FileInfo.Length.Should().Be(90*1024); 61 | 62 | 63 | var secondResult = await Storage.UploadAsync(uploadStream2, options => 64 | { 65 | options.FileName = filenameToUse; 66 | options.Directory = temporaryDirectory; 67 | }); 68 | 69 | secondResult.IsSuccess.Should().BeTrue(); 70 | 71 | // let's download it 72 | downloadedResult = await Storage.DownloadAsync(options => 73 | { 74 | options.FileName = filenameToUse; 75 | options.Directory = temporaryDirectory; 76 | }); 77 | downloadedResult.IsSuccess.Should().BeTrue(); 78 | // size 79 | downloadedResult.Value!.FileInfo.Length.Should().Be(512); 80 | 81 | // content 82 | using var ms = new MemoryStream(); 83 | await downloadedResult.Value!.FileStream.CopyToAsync(ms); 84 | ms.ToArray().Should().BeEquivalentTo(zeroByteBuffer); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSBlobTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.FakeGcsServer; 5 | 6 | // ReSharper disable MethodHasAsyncOverload 7 | 8 | namespace ManagedCode.Storage.Tests.Storages.GCS; 9 | 10 | public class GCSBlobTests : BlobTests 11 | { 12 | protected override FakeGcsServerContainer Build() 13 | { 14 | return new FakeGcsServerBuilder().WithImage(ContainerImages.FakeGCSServer) 15 | .Build(); 16 | } 17 | 18 | protected override ServiceProvider ConfigureServices() 19 | { 20 | return GCSConfigurator.ConfigureServices(Container.GetConnectionString()); 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Google.Cloud.Storage.V1; 4 | using ManagedCode.Storage.Core; 5 | using ManagedCode.Storage.Core.Exceptions; 6 | using ManagedCode.Storage.Google; 7 | using ManagedCode.Storage.Google.Extensions; 8 | using ManagedCode.Storage.Google.Options; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Xunit; 11 | 12 | namespace ManagedCode.Storage.Tests.Storages.GCS; 13 | 14 | public class GCSConfigTests 15 | { 16 | [Fact] 17 | public void BadConfigurationForStorage_WithoutProjectId_ThrowException() 18 | { 19 | var services = new ServiceCollection(); 20 | 21 | Action action = () => services.AddGCPStorage(opt => 22 | { 23 | opt.BucketOptions = new BucketOptions 24 | { 25 | Bucket = "managed-code-bucket" 26 | }; 27 | opt.StorageClientBuilder = new StorageClientBuilder 28 | { 29 | UnauthenticatedAccess = true, 30 | BaseUri = "http://localhost:4443/storage/v1/" 31 | }; 32 | }); 33 | 34 | action.Should() 35 | .Throw(); 36 | } 37 | 38 | [Fact] 39 | public void BadConfigurationForStorage_WithoutBucket_ThrowException() 40 | { 41 | var services = new ServiceCollection(); 42 | 43 | Action action = () => services.AddGCPStorage(opt => 44 | { 45 | opt.BucketOptions = new BucketOptions 46 | { 47 | ProjectId = "api-project-0000000000000" 48 | }; 49 | opt.StorageClientBuilder = new StorageClientBuilder 50 | { 51 | UnauthenticatedAccess = true, 52 | BaseUri = "http://localhost:4443/storage/v1/" 53 | }; 54 | }); 55 | 56 | action.Should() 57 | .Throw(); 58 | } 59 | 60 | [Fact] 61 | public void BadConfigurationForStorage_WithoutStorageClientBuilderAndGoogleCredential_ThrowException() 62 | { 63 | var services = new ServiceCollection(); 64 | 65 | Action action = () => services.AddGCPStorageAsDefault(opt => 66 | { 67 | opt.BucketOptions = new BucketOptions 68 | { 69 | ProjectId = "api-project-0000000000000", 70 | Bucket = "managed-code-bucket" 71 | }; 72 | }); 73 | 74 | action.Should() 75 | .Throw(); 76 | } 77 | 78 | [Fact] 79 | public void StorageAsDefaultTest() 80 | { 81 | var storage = GCSConfigurator.ConfigureServices("test") 82 | .GetService(); 83 | var defaultStorage = GCSConfigurator.ConfigureServices("test") 84 | .GetService(); 85 | storage?.GetType() 86 | .FullName 87 | .Should() 88 | .Be(defaultStorage?.GetType() 89 | .FullName); 90 | } 91 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Google.Cloud.Storage.V1; 2 | using ManagedCode.Storage.Google.Extensions; 3 | using ManagedCode.Storage.Google.Options; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.GCS; 7 | 8 | public class GCSConfigurator 9 | { 10 | public static ServiceProvider ConfigureServices(string connectionString) 11 | { 12 | var services = new ServiceCollection(); 13 | 14 | services.AddGCPStorageAsDefault(opt => 15 | { 16 | opt.BucketOptions = new BucketOptions 17 | { 18 | ProjectId = "api-project-0000000000000", 19 | Bucket = "managed-code-bucket" 20 | }; 21 | opt.StorageClientBuilder = new StorageClientBuilder 22 | { 23 | UnauthenticatedAccess = true, 24 | BaseUri = connectionString 25 | }; 26 | }); 27 | 28 | services.AddGCPStorage(new GCPStorageOptions 29 | { 30 | BucketOptions = new BucketOptions 31 | { 32 | ProjectId = "api-project-0000000000000", 33 | Bucket = "managed-code-bucket" 34 | }, 35 | StorageClientBuilder = new StorageClientBuilder 36 | { 37 | UnauthenticatedAccess = true, 38 | BaseUri = connectionString 39 | } 40 | }); 41 | return services.BuildServiceProvider(); 42 | } 43 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSContainerTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.FakeGcsServer; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.GCS; 7 | 8 | public class GCSContainerTests : ContainerTests 9 | { 10 | protected override FakeGcsServerContainer Build() 11 | { 12 | return new FakeGcsServerBuilder() 13 | .WithImage(ContainerImages.FakeGCSServer) 14 | .Build(); 15 | } 16 | 17 | protected override ServiceProvider ConfigureServices() 18 | { 19 | return GCSConfigurator.ConfigureServices(Container.GetConnectionString()); 20 | } 21 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSDownloadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.FakeGcsServer; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.GCS; 7 | 8 | public class GCSDownloadTests : DownloadTests 9 | { 10 | protected override FakeGcsServerContainer Build() 11 | { 12 | return new FakeGcsServerBuilder().WithImage(ContainerImages.FakeGCSServer) 13 | .Build(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return GCSConfigurator.ConfigureServices(Container.GetConnectionString()); 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/ManagedCode.Storage.Tests/Storages/GCS/GCSUploadTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Storage.Tests.Common; 2 | using ManagedCode.Storage.Tests.Storages.Abstracts; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Testcontainers.FakeGcsServer; 5 | 6 | namespace ManagedCode.Storage.Tests.Storages.GCS; 7 | 8 | public class GCSUploadTests : UploadTests 9 | { 10 | protected override FakeGcsServerContainer Build() 11 | { 12 | return new FakeGcsServerBuilder().WithImage(ContainerImages.FakeGCSServer) 13 | .Build(); 14 | } 15 | 16 | protected override ServiceProvider ConfigureServices() 17 | { 18 | return GCSConfigurator.ConfigureServices(Container.GetConnectionString()); 19 | } 20 | } -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcode/Storage/622d2ab47123452f1e9ca47b5fb7a335cd55b358/logo.png --------------------------------------------------------------------------------