diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f59ac85..1e2e823 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: "Build" on: pull_request: - branches: [ master ] + branches: [ master, development ] env: PROJECT_PATH: 'AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj' @@ -11,18 +11,18 @@ env: jobs: build: name: 'Test build' - runs-on: 'windows-latest' - steps: - - name: 'Checkout' - uses: actions/checkout@v2 + runs-on: 'ubuntu-latest' + steps: + - name: 'Checkout' + uses: actions/checkout@v3 - - name: 'Install dotnet' - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '6.0.x' + - name: 'Install dotnet' + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' - - name: 'Restore packages' - run: dotnet restore ${{ env.PROJECT_PATH }} + - name: 'Restore packages' + run: dotnet restore ${{ env.PROJECT_PATH }} - - name: 'Build project' - run: dotnet build ${{ env.PROJECT_PATH }} --no-restore --configuration Release \ No newline at end of file + - name: 'Build project' + run: dotnet build ${{ env.PROJECT_PATH }} --no-restore --configuration Release \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 01bfc38..13e04a0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,24 +6,24 @@ on: - 'v*' branches: [ master ] pull_request: - branches: [ master ] + branches: [ master, development ] env: PROJECT_PATH: 'AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj' TEST_PROJECT_PATH: './AspNetCore.Yandex.ObjectStorage.sln' - PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}\bin\Release + PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}\/bin\/Release NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json' jobs: deploy: name: 'Deploy' - runs-on: 'windows-latest' + runs-on: 'ubuntu-latest' steps: - name: 'Checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Install dotnet' - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: '6.0.x' @@ -44,4 +44,4 @@ jobs: run: dotnet pack ${{ env.PROJECT_PATH }} --no-restore --no-build --configuration Release --include-symbols --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} - name: 'Push package' - run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}\*.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s ${{ env.NUGET_SOURCE_URL }} --skip-duplicate \ No newline at end of file + run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}\/*.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s ${{ env.NUGET_SOURCE_URL }} --skip-duplicate \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/AspNetCore.Yandex.ObjectStorage.IntegrationTests.csproj b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/AspNetCore.Yandex.ObjectStorage.IntegrationTests.csproj index 85b08eb..35bbaff 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/AspNetCore.Yandex.ObjectStorage.IntegrationTests.csproj +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/AspNetCore.Yandex.ObjectStorage.IntegrationTests.csproj @@ -1,17 +1,17 @@ - net6.0 + net7.0 enable false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs index 8b94bb4..f45e866 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/BucketServiceTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using AspNetCore.Yandex.ObjectStorage.Bucket.Requests; @@ -8,101 +9,115 @@ using Xunit; -namespace AspNetCore.Yandex.ObjectStorage.IntegrationTests +namespace AspNetCore.Yandex.ObjectStorage.IntegrationTests; + +public class BucketServiceTests { - public class BucketServiceTests - { - private readonly Faker _faker; - private readonly IYandexStorageService _yandexStorageService; + private readonly Faker _faker; + private readonly IYandexStorageService _yandexStorageService; - public BucketServiceTests() - { - _faker = new Faker("en"); + public BucketServiceTests() + { + _faker = new Faker("en"); + var httpClient = new HttpClient(); + _yandexStorageService = new YandexStorageService(EnvironmentOptions.GetFromEnvironment(), httpClient); + } - _yandexStorageService = new YandexStorageService(EnvironmentOptions.GetFromEnvironment()); - } + [Fact(DisplayName = "[001] CreateAsync - adding new bucket with random valid name.")] + public async Task CreateBucket_ValidBucketName_SuccessStatusCode() + { + var bucketName = _faker.Random.String2(10); - [Fact(DisplayName = "[001] CreateBucket test")] - public async Task CreateBucket_Success() - { - var bucketName = _faker.Random.String2(10); + var result = await _yandexStorageService.BucketService.CreateAsync(bucketName); - var result = await _yandexStorageService.BucketService.CreateBucket(bucketName); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var createResult = await result.ReadResultAsStringAsync(); - var createResult = await result.ReadResultAsStringAsync(); + Assert.True(createResult.IsSuccess); - Assert.True(createResult.IsSuccess); + await DeleteBucketAsync(bucketName); + } - await DeleteBucketAsync(bucketName); - } + [Fact(DisplayName = "[002] DeleteAsync - deleting existing bucket.")] + public async Task DeleteBucket_ExistingBucket_SuccessStatusCode() + { + var bucketName = _faker.Random.String2(10); - [Fact(DisplayName = "[002] Delete Bucket test")] - public async Task DeleteBucket_Success() - { - var bucketName = _faker.Random.String2(10); + var result = await _yandexStorageService.BucketService.CreateAsync(bucketName); - var result = await _yandexStorageService.BucketService.CreateBucket(bucketName); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var response = await _yandexStorageService.BucketService.DeleteAsync(bucketName); - var response = await _yandexStorageService.BucketService.DeleteBucket(bucketName); + var deleteResult = await response.ReadResultAsStringAsync(); - var deleteResult = await response.ReadResultAsStringAsync(); + Assert.True(deleteResult.IsSuccess); + } - Assert.True(deleteResult.IsSuccess); - } + [Fact(DisplayName = "[003] GetBucketListObjectsAsync - get list of objects in test bucket. ")] + public async Task GetBucketListObjects_ExistingBucket_SuccessStatusCode() + { + const string bucketName = "testbucketlib"; - [Fact(DisplayName = "[003] List bucket objects")] - public async Task ListBucketObjects_Success() + var result = await _yandexStorageService.BucketService.GetBucketListObjectsAsync(new BucketListObjectsParameters() { - const string bucketName = "testbucketlib"; + BucketName = bucketName, + }); - var result = await _yandexStorageService.BucketService.GetBucketListObjects(new BucketListObjectsParameters() - { - BucketName = bucketName - }); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var listObjectsResult = await result.ReadResultAsync(); - var listObjectsResult = await result.ReadResultAsync(); + Assert.True(listObjectsResult.IsSuccess); + } - Assert.True(listObjectsResult.IsSuccess); + [Fact(DisplayName = "[004] GetBucketListObjectsAsync - check object count in test bucket.")] + public async Task GetBucketListObjects_WithTwoFiles_ObjectCountEqualsTwo() + { + const string bucketName = "testbucketlib"; - var listObjects = listObjectsResult.Value; + var result = await _yandexStorageService.BucketService.GetBucketListObjectsAsync(new BucketListObjectsParameters() + { + BucketName = bucketName + }); - Assert.Equal(2, listObjects.Contents.Count); - } + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var listObjectsResult = await result.ReadResultAsync(); + var listObjects = listObjectsResult.Value; - [Fact(DisplayName = "[004] Bucket list")] - public async Task BucketList_Success() - { - var result = await _yandexStorageService.BucketService.GetBucketList(); + Assert.Equal(2, listObjects.Contents.Count); + } - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); - var bucketListResult = await result.ReadResultAsync(); + [Fact(DisplayName = "[005] GetAllAsync - list all buckets.")] + public async Task GetAll_TestBucketExists_ContentHaveTestBucket() + { + var result = await _yandexStorageService.BucketService.GetAllAsync(); - Assert.True(bucketListResult.IsSuccess); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - var bucketList = bucketListResult.Value; + var bucketListResult = await result.ReadResultAsync(); - Assert.NotEmpty(bucketList.Buckets); - Assert.True(bucketList.Buckets.Any(p => p.Name == "testbucketlib")); - } + Assert.True(bucketListResult.IsSuccess); + var bucketList = bucketListResult.Value; - private async Task DeleteBucketAsync(string bucketName) - { - await _yandexStorageService.BucketService.CreateBucket(bucketName); - } + Assert.NotEmpty(bucketList.Buckets); + Assert.True(bucketList.Buckets.Any(p => p.Name == "testbucketlib")); + } + + + private async Task DeleteBucketAsync(string bucketName) + { + await _yandexStorageService.BucketService.DeleteAsync(bucketName); } } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs index 8a79322..d696c0b 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/EnvironmentOptions.cs @@ -12,7 +12,7 @@ public static YandexStorageOptions GetFromEnvironment() { BucketName = Environment.GetEnvironmentVariable("BucketName"), AccessKey = Environment.GetEnvironmentVariable("AccessKey"), - SecretKey = Environment.GetEnvironmentVariable("SecretKey") + SecretKey = Environment.GetEnvironmentVariable("SecretKey"), }; } @@ -24,7 +24,7 @@ public static YandexStorageOptions GetFromEnvironmentWithNotDefaultLocation() AccessKey = Environment.GetEnvironmentVariable("AccessKey"), SecretKey = Environment.GetEnvironmentVariable("SecretKey"), Location = "ru-central1", - Endpoint = "s3.yandexcloud.net" + Endpoint = "s3.yandexcloud.net", }; } } diff --git a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/ObjectServiceTests.cs b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/ObjectServiceTests.cs index 61c3ad4..82fd983 100644 --- a/AspNetCore.Yandex.ObjectStorage.IntegrationTests/ObjectServiceTests.cs +++ b/AspNetCore.Yandex.ObjectStorage.IntegrationTests/ObjectServiceTests.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using AspNetCore.Yandex.ObjectStorage.Object.Parameters; @@ -11,222 +12,221 @@ using Xunit; -namespace AspNetCore.Yandex.ObjectStorage.IntegrationTests +namespace AspNetCore.Yandex.ObjectStorage.IntegrationTests; + +public class ObjectServiceTests { - public class ObjectServiceTests - { - private readonly Faker _faker; - private readonly IYandexStorageService _yandexStorageService; - private readonly IYandexStorageService _anotherLocationService; + private readonly Faker _faker; + private readonly IYandexStorageService _yandexStorageService; + private readonly IYandexStorageService _anotherLocationService; - public ObjectServiceTests() - { - _faker = new Faker("en"); + public ObjectServiceTests() + { + _faker = new Faker("en"); + var httpClient = new HttpClient(); + _yandexStorageService = new YandexStorageService(EnvironmentOptions.GetFromEnvironment(), httpClient); + _anotherLocationService = new YandexStorageService(EnvironmentOptions.GetFromEnvironmentWithNotDefaultLocation(), httpClient); + } - _yandexStorageService = new YandexStorageService(EnvironmentOptions.GetFromEnvironment()); - _anotherLocationService = new YandexStorageService(EnvironmentOptions.GetFromEnvironmentWithNotDefaultLocation()); - } + [Fact(DisplayName = "[001] PutAsync - put object as byte array.")] + public async Task PutObject_AsByteArray_Success() + { + var fakeObject = _faker.Random.Bytes(100); - [Fact(DisplayName = "[001] PutObjectAsync - put object as byte array")] - public async Task PutObject_AsByteArray_Success() - { - var fakeObject = _faker.Random.Bytes(100); + var filename = _faker.Random.String2(15); - var filename = _faker.Random.String2(15); + var result = await _yandexStorageService.ObjectService.PutAsync(fakeObject, filename); - var result = await _yandexStorageService.ObjectService.PutAsync(fakeObject, filename); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + var getResult = await GetObjectAsync(filename); - var getResult = await GetObjectAsync(filename); + Assert.Equal(fakeObject, getResult); - Assert.Equal(fakeObject, getResult); + await ClearUploadedAsync(filename); + } - await ClearUploadedAsync(filename); - } + [Fact(DisplayName = "[002] PutAsync - put object as stream.")] + public async Task PutObject_AsStream_Success() + { + var fakeObject = _faker.Random.Bytes(100); + var filename = _faker.Random.String2(15); - [Fact(DisplayName = "[002] PutObjectAsync - put object as stream")] - public async Task PutObject_AsStream_Success() + await using (var ms = new MemoryStream(fakeObject)) { - var fakeObject = _faker.Random.Bytes(100); - var filename = _faker.Random.String2(15); + ms.Position = 0; + _ = await _yandexStorageService.ObjectService.PutAsync(ms, filename); + } - await using (var ms = new MemoryStream(fakeObject)) - { - ms.Position = 0; - _ = await _yandexStorageService.ObjectService.PutAsync(ms, filename); - } + var getResult = await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename); - var getResult = await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename); + Assert.Equal(fakeObject, getResult.Value); - Assert.Equal(fakeObject, getResult.Value); + await ClearUploadedAsync(filename); + } - await ClearUploadedAsync(filename); - } + [Fact(DisplayName = "[003] GetObjectAsync - get object as byte array.")] + public async Task GetObject_AsByteArray_Success() + { + var fakeObject = _faker.Random.Bytes(100); + var filename = _faker.Random.String2(15); - [Fact(DisplayName = "[003] GetObjectAsync - GetAsByteArray testing")] - public async Task GetObject_GetAsByteArray_Success() - { - var fakeObject = _faker.Random.Bytes(100); - var filename = _faker.Random.String2(15); + await UploadObjectAsync(fakeObject, filename); - await UploadObjectAsync(fakeObject, filename); + var getResult = await GetObjectAsync(filename); - var getResult = await GetObjectAsync(filename); + Assert.Equal(fakeObject, getResult); - Assert.Equal(fakeObject, getResult); + await ClearUploadedAsync(filename); + } - await ClearUploadedAsync(filename); - } + [Fact(DisplayName = "[004] GetObjectAsync - get object as stream.")] + public async Task GetObject_AsStream_Success() + { + const int size = 100; - [Fact(DisplayName = "[004] GetObjectAsync - GetAsStream testing")] - public async Task GetObject_GetAsStream_Success() - { - const int size = 100; + var fakeObject = _faker.Random.Bytes(size); + var filename = _faker.Random.String2(15); - var fakeObject = _faker.Random.Bytes(size); - var filename = _faker.Random.String2(15); + await UploadObjectAsync(fakeObject, filename); - await UploadObjectAsync(fakeObject, filename); + var streamResult = await _yandexStorageService.ObjectService.GetAsStreamAsync(filename); - var streamResult = await _yandexStorageService.ObjectService.GetAsStreamAsync(filename); + Assert.True(streamResult.IsSuccess); - Assert.True(streamResult.IsSuccess); + byte[] byteArr; + await using (MemoryStream ms = new()) + { + await streamResult.Value.CopyToAsync(ms); + byteArr = ms.ToArray(); + } - byte[] byteArr; - await using (MemoryStream ms = new()) - { - await streamResult.Value.CopyToAsync(ms); - byteArr = ms.ToArray(); - } + Assert.Equal(fakeObject, byteArr); - Assert.Equal(fakeObject, byteArr); + await ClearUploadedAsync(filename); + } - await ClearUploadedAsync(filename); - } + [Fact(DisplayName = "[005] DeleteAsync - deleting uploaded object.")] + public async Task DeleteObject_ExistingFile_Success() + { + const int size = 100; - [Fact(DisplayName = "[005] DeleteObjectAsync - DeleteObject testing")] - public async Task DeleteObject_Success() - { - const int size = 100; + var fakeObject = _faker.Random.Bytes(size); + var filename = _faker.Random.String2(15); - var fakeObject = _faker.Random.Bytes(size); - var filename = _faker.Random.String2(15); + await UploadObjectAsync(fakeObject, filename); - await UploadObjectAsync(fakeObject, filename); + var getResult = await GetObjectAsync(filename); - var getResult = await GetObjectAsync(filename); + Assert.NotEmpty(getResult); - Assert.NotEmpty(getResult); + await _yandexStorageService.ObjectService.DeleteAsync(filename); - await _yandexStorageService.ObjectService.DeleteAsync(filename); + var getResultAfterDelete = await TryGetObjectAsync(filename); - var getResultAfterDelete = await TryGetObjectAsync(filename); + Assert.False(getResultAfterDelete.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.NotFound, getResultAfterDelete.StatusCode); + } - Assert.False(getResultAfterDelete.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.NotFound, getResultAfterDelete.StatusCode); - } + [Fact(DisplayName = "[006] PutObjectAsync - put big object as stream")] + public async Task PutObject_BigObjectAsStream_Success() + { + var fakeObject = _faker.Random.Bytes(3_000_000); + var filename = _faker.Random.String2(15); - [Fact(DisplayName = "[006] PutObjectAsync - put big object as stream")] - public async Task PutObject_BigObjectAsStream_Success() + await using (var ms = new MemoryStream(fakeObject)) { - var fakeObject = _faker.Random.Bytes(3_000_000); - var filename = _faker.Random.String2(15); - - await using (var ms = new MemoryStream(fakeObject)) - { - ms.Position = 0; - _ = await _yandexStorageService.ObjectService.PutAsync(ms, filename); - } + ms.Position = 0; + _ = await _yandexStorageService.ObjectService.PutAsync(ms, filename); + } - var getResult = await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename); + var getResult = await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename); - Assert.Equal(fakeObject, getResult.Value); + Assert.Equal(fakeObject, getResult.Value); - await ClearUploadedAsync(filename); - } + await ClearUploadedAsync(filename); + } - [Fact(DisplayName = "[007] PutObjectAsync - not default location")] - public async Task PutObject_NotDefaultLocation_Success() - { - var fakeObject = _faker.Random.Bytes(100); + [Fact(DisplayName = "[007] PutObjectAsync - not default location")] + public async Task PutObject_NotDefaultLocation_Success() + { + var fakeObject = _faker.Random.Bytes(100); - var filename = _faker.Random.String2(15); + var filename = _faker.Random.String2(15); - var result = await _yandexStorageService.ObjectService.PutAsync(fakeObject, filename); + var result = await _yandexStorageService.ObjectService.PutAsync(fakeObject, filename); - Assert.True(result.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.True(result.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - var getResult = await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename); + var getResult = await _anotherLocationService.ObjectService.GetAsByteArrayAsync(filename); - Assert.Equal(fakeObject, getResult.Value); + Assert.Equal(fakeObject, getResult.Value); - await ClearUploadedAsync(filename); - } + await ClearUploadedAsync(filename); + } - [Fact(DisplayName = "[008] Delete Multiple objects - quite is true")] - public async Task DeleteMultipleObjects_Success() - { - var fakeObject1 = _faker.Random.Bytes(100); - var fakeObject2 = _faker.Random.Bytes(100); + [Fact(DisplayName = "[008] DeleteMultipleAsync - delete multiple objects with quite flag is true")] + public async Task DeleteMultipleObjects_QuiteIsTrue_Success() + { + var fakeObject1 = _faker.Random.Bytes(100); + var fakeObject2 = _faker.Random.Bytes(100); - var filename1 = _faker.Random.String2(15); - var filename2 = _faker.Random.String2(15); + var filename1 = _faker.Random.String2(15); + var filename2 = _faker.Random.String2(15); - await UploadObjectAsync(fakeObject1, filename1); - await UploadObjectAsync(fakeObject2, filename2); + await UploadObjectAsync(fakeObject1, filename1); + await UploadObjectAsync(fakeObject2, filename2); - var deleteRequest = new DeleteMultipleObjectsParameters + var deleteRequest = new DeleteMultipleObjectsParameters + { + IsQuite = true, + DeleteObjects = new List() { - IsQuite = true, - DeleteObjects = new List() - { - new() { Key = filename1 }, - new() { Key = filename2 } - } - }; + new() { Key = filename1 }, + new() { Key = filename2 } + } + }; - var response = await _yandexStorageService.ObjectService.DeleteMultipleAsync(deleteRequest); + var response = await _yandexStorageService.ObjectService.DeleteMultipleAsync(deleteRequest); - Assert.True(response.IsSuccessStatusCode); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var deleteResult = await response.ReadResultAsync(); + var deleteResult = await response.ReadResultAsync(); - Assert.True(deleteResult.IsSuccess); - Assert.True(deleteResult.Value.Deleted.Any(p => p.Key == filename1)); - Assert.True(deleteResult.Value.Deleted.Any(p => p.Key == filename2)); - } + Assert.True(deleteResult.IsSuccess); + Assert.True(deleteResult.Value.Deleted.Any(p => p.Key == filename1)); + Assert.True(deleteResult.Value.Deleted.Any(p => p.Key == filename2)); + } - #region Private + #region Private - private async Task TryGetObjectAsync(string filename) - { - return await _yandexStorageService.TryGetAsync(filename); - } + private async Task TryGetObjectAsync(string filename) + { + return await _yandexStorageService.TryGetAsync(filename); + } - private async Task GetObjectAsync(string filename) - { - return (await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename)).Value; - } + private async Task GetObjectAsync(string filename) + { + return (await _yandexStorageService.ObjectService.GetAsByteArrayAsync(filename)).Value; + } - private async Task ClearUploadedAsync(params string[] filenames) + private async Task ClearUploadedAsync(params string[] filenames) + { + var deleteTasks = filenames.Select(async filename => { - var deleteTasks = filenames.Select(async filename => - { - await _yandexStorageService.ObjectService.DeleteAsync(filename); - }); - - await Task.WhenAll(deleteTasks); - } + await _yandexStorageService.ObjectService.DeleteAsync(filename); + }); - private async Task UploadObjectAsync(byte[] file, string filename) - { - await _yandexStorageService.ObjectService.PutAsync(file, filename); - } + await Task.WhenAll(deleteTasks); + } - #endregion + private async Task UploadObjectAsync(byte[] file, string filename) + { + await _yandexStorageService.ObjectService.PutAsync(file, filename); } + + #endregion } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage.UnitTest/AspNetCore.Yandex.ObjectStorage.UnitTest.csproj b/AspNetCore.Yandex.ObjectStorage.UnitTest/AspNetCore.Yandex.ObjectStorage.UnitTest.csproj index 2bd8d87..89a97eb 100644 --- a/AspNetCore.Yandex.ObjectStorage.UnitTest/AspNetCore.Yandex.ObjectStorage.UnitTest.csproj +++ b/AspNetCore.Yandex.ObjectStorage.UnitTest/AspNetCore.Yandex.ObjectStorage.UnitTest.csproj @@ -1,20 +1,20 @@ - net6.0 + net7.0 enable false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -24,6 +24,8 @@ + + true diff --git a/AspNetCore.Yandex.ObjectStorage.UnitTest/YandexStorageOptionsValidatorTests.cs b/AspNetCore.Yandex.ObjectStorage.UnitTest/YandexStorageOptionsValidatorTests.cs new file mode 100644 index 0000000..a343365 --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage.UnitTest/YandexStorageOptionsValidatorTests.cs @@ -0,0 +1,82 @@ +using System; + +using AspNetCore.Yandex.ObjectStorage.Configuration; + +using Microsoft.Extensions.Options; + +using Xunit; +// ReSharper disable ConvertToLocalFunction + +namespace AspNetCore.Yandex.ObjectStorage.UnitTest; + +public class YandexStorageOptionsValidatorTests +{ + [Fact(DisplayName = "[001] Validate - not empty values of bucket, access key, secret key. ")] + public void Validate_NotEmptyValues_WithDefault_ReturnsValid() + { + // Arrange + var options = new YandexStorageOptions + { + BucketName = "test", + AccessKey = "test", + SecretKey = "test" + }; + + var validator = new YandexStorageOptionsValidator(); + + // Act + var result = validator.ValidateOrThrow(options); + + // Assert + Assert.True(result); + } + + [Fact(DisplayName = "[002] Validate - empty value of bucket.")] + public void Validate_BucketNotSet_WithDefault_ThrowsValidationException() + { + // Arrange + var options = new YandexStorageOptions + { + AccessKey = "test", + SecretKey = "test" + }; + + var validator = new YandexStorageOptionsValidator(); + + // Act + Action result = () => validator.ValidateOrThrow(options); + + // Assert + Assert.Throws(result); + } + + [Fact(DisplayName = "[003] Validate - default constructor.")] + public void Validate_DefaultConstructor_ThrowsValidationException() + { + // Arrange + var options = new YandexStorageOptions(); + + var validator = new YandexStorageOptionsValidator(); + + // Act + Action result = () => validator.ValidateOrThrow(options); + + // Assert + Assert.Throws(result); + } + + [Fact(DisplayName = "[004] Validate - options is null.")] + public void Validate_OptionsIsNull_ThrowsValidationException() + { + // Arrange + YandexStorageOptions options = null; + + var validator = new YandexStorageOptionsValidator(); + + // Act + Action result = () => validator.ValidateOrThrow(options); + + // Assert + Assert.Throws(result); + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage.sln b/AspNetCore.Yandex.ObjectStorage.sln index ba83e17..52214eb 100644 --- a/AspNetCore.Yandex.ObjectStorage.sln +++ b/AspNetCore.Yandex.ObjectStorage.sln @@ -16,6 +16,7 @@ ProjectSection(SolutionItems) = preProject LICENSE.md = LICENSE.md README.md = README.md .github\workflows\deploy.yml = .github\workflows\deploy.yml + .github\workflows\build.yml = .github\workflows\build.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{88BC1FD3-427E-4EF6-9A1C-C5F84CD53663}" diff --git a/AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj b/AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj index a34befb..3b14a57 100644 --- a/AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj +++ b/AspNetCore.Yandex.ObjectStorage/AspNetCore.Yandex.ObjectStorage.csproj @@ -2,7 +2,7 @@ true - 0.2.2.0 + 0.2.2.0-alpha003 Shumkin Alexandr S3 API realisation for Yandex Object Storage https://github.com/DubZero/AspNetCore.Yandex.ObjectStorage @@ -17,9 +17,10 @@ 0.2.2 LICENSE.md true - 0.2.2.0 + 0.2.2.0-alpha003 netstandard2.1 Yandex.ObjectStorage S3 API Client Library + enable @@ -31,12 +32,13 @@ - + - - - - + + + + + diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs index 011297d..1dc8825 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/BucketService.cs @@ -15,19 +15,21 @@ namespace AspNetCore.Yandex.ObjectStorage.Bucket internal class BucketService : IBucketService { private readonly YandexStorageOptions _options; - private static readonly HttpClient _client = new HttpClient(); + private readonly HttpClient _client; - public BucketService(IOptions options) + public BucketService(IOptions options, HttpClient client) { _options = options.Value; + _client = client; } - public BucketService(YandexStorageOptions options) + public BucketService(YandexStorageOptions options, HttpClient client) { _options = options; + _client = client; } - public async Task CreateBucket(string bucketName) + public async Task CreateAsync(string bucketName) { var builder = new BucketPutRequestBuilder(_options); await builder.BuildAsync(bucketName); @@ -38,7 +40,7 @@ public async Task CreateBucket(string bucketName) return new S3ObjectPutResponse(response, GetBucketUri(bucketName)); } - public async Task GetBucketMeta(string bucketName) + public async Task GetBucketMetaAsync(string bucketName) { var builder = await new BucketMetaRequestBuilder(_options).BuildAsync(bucketName); @@ -49,17 +51,17 @@ public async Task GetBucketMeta(string bucketName) return new S3Response(response); } - public async Task GetBucketListObjects(BucketListObjectsParameters parameters) + public async Task GetBucketListObjectsAsync(BucketListObjectsParameters parameters) { var builder = await new BucketListObjectsRequestBuilder(_options).BuildAsync(parameters); var requestMessage = builder.GetResult(); - var response = await _client.SendAsync(requestMessage); + var response = await _client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead); return new S3BucketObjectListResponse(response); } - public async Task GetBucketList() + public async Task GetAllAsync() { var builder = await new BucketListRequestBuilder(_options).BuildAsync(); var requestMessage = builder.GetResult(); @@ -69,7 +71,7 @@ public async Task GetBucketList() return new S3BucketListResponse(response); } - public async Task DeleteBucket(string bucketName) + public async Task DeleteAsync(string bucketName) { var builder = await new BucketDeleteRequestBuilder(_options).BuildAsync(bucketName); var requestMessage = builder.GetResult(); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs index 21901df..0a3cacb 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketDeleteRequestBuilder.cs @@ -11,15 +11,20 @@ internal class BucketDeleteRequestBuilder { private readonly YandexStorageOptions _options; private HttpRequestMessage _request; + private readonly Version _httpRequestVersion; internal BucketDeleteRequestBuilder(YandexStorageOptions options) { _options = options; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } internal async Task BuildAsync(string bucketName) { - var requestMessage = new HttpRequestMessage(HttpMethod.Delete, new Uri($"{_options.Protocol}://{_options.Endpoint}/{bucketName}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, new Uri($"{_options.Protocol}://{_options.Endpoint}/{bucketName}")) + { + Version = _httpRequestVersion + }; var dateAmz = DateTime.UtcNow; await requestMessage.AddBothHeadersAsync(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs index ee352e6..d56de0e 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListObjectsRequestBuilder.cs @@ -12,17 +12,22 @@ internal class BucketListObjectsRequestBuilder { private readonly YandexStorageOptions _options; private HttpRequestMessage _request; + private readonly Version _httpRequestVersion; internal BucketListObjectsRequestBuilder(YandexStorageOptions options) { _options = options; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } internal async Task BuildAsync(BucketListObjectsParameters parameters) { var url = $"{_options.Protocol}://{_options.Endpoint}/{parameters.BucketName}?{FormatParameters(parameters)}"; - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)) + { + Version = _httpRequestVersion + }; var dateAmz = DateTime.UtcNow; await requestMessage.AddBothHeadersAsync(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs index 3fe027a..396c616 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketListRequestBuilder.cs @@ -11,17 +11,22 @@ internal class BucketListRequestBuilder { private readonly YandexStorageOptions _options; private HttpRequestMessage _request; + private readonly Version _httpRequestVersion; internal BucketListRequestBuilder(YandexStorageOptions options) { _options = options; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } internal async Task BuildAsync() { var url = $"{_options.Protocol}://{_options.Endpoint}"; - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(url)) + { + Version = _httpRequestVersion + }; var dateAmz = DateTime.UtcNow; await requestMessage.AddBothHeadersAsync(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketMetaRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketMetaRequestBuilder.cs index e5b36f2..e7c722c 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketMetaRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketMetaRequestBuilder.cs @@ -11,17 +11,22 @@ internal class BucketMetaRequestBuilder { private readonly YandexStorageOptions _options; private HttpRequestMessage _request; + private readonly Version _httpRequestVersion; internal BucketMetaRequestBuilder(YandexStorageOptions options) { _options = options; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } internal async Task BuildAsync(string bucketName) { var url = $"{_options.Protocol}://{_options.Endpoint}/{bucketName}"; - var requestMessage = new HttpRequestMessage(HttpMethod.Head, new Uri(url)); + var requestMessage = new HttpRequestMessage(HttpMethod.Head, new Uri(url)) + { + Version = _httpRequestVersion + }; var dateAmz = DateTime.UtcNow; await requestMessage.AddBothHeadersAsync(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs index 9743897..4710b47 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/Builders/BucketPutRequestBuilder.cs @@ -11,15 +11,20 @@ internal class BucketPutRequestBuilder { private readonly YandexStorageOptions _options; private HttpRequestMessage _request; + private readonly Version _httpRequestVersion; internal BucketPutRequestBuilder(YandexStorageOptions options) { _options = options; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } internal async Task BuildAsync(string bucketName) { - var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_options.Protocol}://{_options.Endpoint}/{bucketName}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_options.Protocol}://{_options.Endpoint}/{bucketName}")) + { + Version = _httpRequestVersion + }; var dateAmz = DateTime.UtcNow; await requestMessage.AddBothHeadersAsync(_options, dateAmz); diff --git a/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs b/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs index 5d858ff..4ac65b3 100644 --- a/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs +++ b/AspNetCore.Yandex.ObjectStorage/Bucket/IBucketService.cs @@ -9,7 +9,7 @@ namespace AspNetCore.Yandex.ObjectStorage.Bucket { public interface IBucketService { - Task CreateBucket(string bucketName); + Task CreateAsync(string bucketName); /// /// Returns the bucket's metadata or an error. @@ -17,18 +17,18 @@ public interface IBucketService /// 1. Whether the bucket exists. /// 2. Whether the user has sufficient permissions to access the bucket /// - Task GetBucketMeta(string bucketName); + Task GetBucketMetaAsync(string bucketName); /// /// Get list of objects in bucket /// - Task GetBucketListObjects(BucketListObjectsParameters parameters); + Task GetBucketListObjectsAsync(BucketListObjectsParameters parameters); /// /// Get list of buckets /// - Task GetBucketList(); + Task GetAllAsync(); - Task DeleteBucket(string bucketName); + Task DeleteAsync(string bucketName); } } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexConfigurationReader.cs b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexConfigurationReader.cs index f1cfc2a..c39704c 100644 --- a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexConfigurationReader.cs +++ b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexConfigurationReader.cs @@ -3,27 +3,31 @@ namespace AspNetCore.Yandex.ObjectStorage.Configuration { - public static class YandexConfigurationReaderExtension + internal static class YandexConfigurationReaderExtension { - public static YandexStorageOptions GetYandexStorageOptions(this IConfiguration configuration, string sectionName) + private static YandexStorageOptions GetYandexStorageOptions(this IConfiguration configuration, string sectionName) { var section = configuration.GetSection(sectionName); return new YandexStorageOptions(section); } - public static IServiceCollection LoadYandexStorageOptions(this IServiceCollection services, IConfiguration configuration, string sectionName) + + internal static IServiceCollection LoadYandexStorageOptions(this IServiceCollection services, IConfiguration configuration, string sectionName) { - var readedOptions = configuration.GetYandexStorageOptions(sectionName); + var readOptions = configuration.GetYandexStorageOptions(sectionName); + + new YandexStorageOptionsValidator().ValidateOrThrow(readOptions); services.Configure(options => { - options.BucketName = readedOptions.BucketName; - options.Location = readedOptions.Location; - options.AccessKey = readedOptions.AccessKey; - options.SecretKey = readedOptions.SecretKey; - options.Endpoint = readedOptions.Endpoint; - options.Protocol = readedOptions.Protocol; + options.BucketName = readOptions.BucketName; + options.Location = readOptions.Location; + options.AccessKey = readOptions.AccessKey; + options.SecretKey = readOptions.SecretKey; + options.Endpoint = readOptions.Endpoint; + options.Protocol = readOptions.Protocol; + options.UseHttp2 = readOptions.UseHttp2; }); return services; diff --git a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageDefaults.cs b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageDefaults.cs index 223009c..fe72dea 100644 --- a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageDefaults.cs +++ b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageDefaults.cs @@ -1,6 +1,6 @@ namespace AspNetCore.Yandex.ObjectStorage.Configuration { - public class YandexStorageDefaults + internal static class YandexStorageDefaults { public const string Location = "us-east-1"; public const string Protocol = "https"; diff --git a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptions.cs b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptions.cs index 28c7eed..03ca371 100644 --- a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptions.cs +++ b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptions.cs @@ -10,13 +10,18 @@ public YandexStorageOptions() public YandexStorageOptions(IConfigurationSection section) { - BucketName = section.GetSection("Bucket").Value; - AccessKey = section.GetSection("AccessKey").Value; - SecretKey = section.GetSection("SecretKey").Value; - - Protocol = section.GetSection("Protocol")?.Value ?? YandexStorageDefaults.Protocol; - Location = section.GetSection("Location")?.Value ?? YandexStorageDefaults.Location; - Endpoint = section.GetSection("Endpoint")?.Value ?? YandexStorageDefaults.EndPoint; + BucketName = section.GetSection("Bucket").Value ?? string.Empty; + AccessKey = section.GetSection("AccessKey").Value ?? string.Empty; + SecretKey = section.GetSection("SecretKey").Value ?? string.Empty; + + Protocol = section.GetSection("Protocol").Value ?? YandexStorageDefaults.Protocol; + Location = section.GetSection("Location").Value ?? YandexStorageDefaults.Location; + Endpoint = section.GetSection("Endpoint").Value ?? YandexStorageDefaults.EndPoint; + + if(bool.TryParse(section.GetSection("UseHttp2").Value, out var useHttp2)) + { + UseHttp2 = useHttp2; + } } /// @@ -43,5 +48,10 @@ public YandexStorageOptions(IConfigurationSection section) public string SecretKey { get; set; } public string HostName => $"{Protocol}://{Endpoint}/{BucketName}"; + + /// + /// Use http2, true by default + /// + public bool UseHttp2 { get; set; } = true; } } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptionsValidator.cs b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptionsValidator.cs new file mode 100644 index 0000000..d988701 --- /dev/null +++ b/AspNetCore.Yandex.ObjectStorage/Configuration/YandexStorageOptionsValidator.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; + +using Microsoft.Extensions.Options; + +namespace AspNetCore.Yandex.ObjectStorage.Configuration +{ + internal class YandexStorageOptionsValidator + { + private readonly string[] _protocolCorrectValues = { "http", "https" }; + + internal bool ValidateOrThrow(YandexStorageOptions options) + { + var errors = new List(); + + if (options is null) + { + errors.Add($"Input value `{nameof(options)}` can't be `null`"); + throw new OptionsValidationException("YandexStorageOptions", typeof(YandexStorageOptions), + errors); + } + + if (string.IsNullOrWhiteSpace(options.BucketName)) + { + errors.Add($"Input value `{nameof(options.BucketName)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.HostName)) + { + errors.Add($"Input value `{nameof(options.HostName)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.Endpoint)) + { + errors.Add($"Input value `{nameof(options.Endpoint)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.Location)) + { + errors.Add($"Input value `{nameof(options.Location)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.SecretKey)) + { + errors.Add($"Input value `{nameof(options.SecretKey)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.AccessKey)) + { + errors.Add($"Input value `{nameof(options.AccessKey)}` can't be `null`"); + } + + if (string.IsNullOrWhiteSpace(options.Protocol)) + { + errors.Add($"Input value `{nameof(options.Protocol)}` can't be `null`"); + } + + if (!_protocolCorrectValues.Contains(options.Protocol)) + { + errors.Add($"`{nameof(options.Protocol)}` must be one of `{string.Join(", ", _protocolCorrectValues)}`"); + } + + return errors.Any() + ? throw new OptionsValidationException("YandexStorageOptions", typeof(YandexStorageOptions), errors) + : true; + } + } +} \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/Extensions/ServiceCollectionExtensions.cs b/AspNetCore.Yandex.ObjectStorage/Extensions/ServiceCollectionExtensions.cs index 197fb40..aa8ffaf 100644 --- a/AspNetCore.Yandex.ObjectStorage/Extensions/ServiceCollectionExtensions.cs +++ b/AspNetCore.Yandex.ObjectStorage/Extensions/ServiceCollectionExtensions.cs @@ -21,11 +21,17 @@ public static void AddYandexObjectStorage(this IServiceCollection services, Acti throw new ArgumentNullException(nameof(setupAction)); } - services.Configure(setupAction); - services.AddSingleton(); + services.AddOptions() + .Configure(setupAction) + .Validate(Validate); + + services.AddHttpClient(); + services.AddScoped(); } - public static void AddYandexObjectStorage(this IServiceCollection services, IConfiguration configuration, string sectionName = YandexConfigurationDefaults.DefaultSectionName) + public static void AddYandexObjectStorage(this IServiceCollection services, + IConfiguration configuration, + string sectionName = YandexConfigurationDefaults.DefaultSectionName) { if (services == null) { @@ -38,7 +44,14 @@ public static void AddYandexObjectStorage(this IServiceCollection services, ICon } services.LoadYandexStorageOptions(configuration, sectionName) - .AddSingleton(); + .AddHttpClient(); + + services.AddScoped(); + } + + private static bool Validate(YandexStorageOptions options) + { + return new YandexStorageOptionsValidator().ValidateOrThrow(options); } } } \ No newline at end of file diff --git a/AspNetCore.Yandex.ObjectStorage/IYandexStorageService.cs b/AspNetCore.Yandex.ObjectStorage/IYandexStorageService.cs index abcb60d..3059090 100644 --- a/AspNetCore.Yandex.ObjectStorage/IYandexStorageService.cs +++ b/AspNetCore.Yandex.ObjectStorage/IYandexStorageService.cs @@ -10,7 +10,6 @@ public interface IYandexStorageService { IObjectService ObjectService { get; } - // Not Implemented IBucketService BucketService { get; } // Not Implemented diff --git a/AspNetCore.Yandex.ObjectStorage/Object/ObjectService.cs b/AspNetCore.Yandex.ObjectStorage/Object/ObjectService.cs index c9837aa..1c5aad3 100644 --- a/AspNetCore.Yandex.ObjectStorage/Object/ObjectService.cs +++ b/AspNetCore.Yandex.ObjectStorage/Object/ObjectService.cs @@ -26,12 +26,14 @@ internal class ObjectService : IObjectService private readonly string _accessKey; private readonly string _secretKey; private readonly string _hostName; - private static readonly HttpClient _client = new HttpClient(); + private readonly HttpClient _client; + private readonly Version _httpRequestVersion; - public ObjectService(IOptions options) + public ObjectService(IOptions options, HttpClient client) { var yandexStorageOptions = options.Value; + _client = client; _protocol = yandexStorageOptions.Protocol; _bucketName = yandexStorageOptions.BucketName; _location = yandexStorageOptions.Location; @@ -39,9 +41,10 @@ public ObjectService(IOptions options) _accessKey = yandexStorageOptions.AccessKey; _secretKey = yandexStorageOptions.SecretKey; _hostName = yandexStorageOptions.HostName; + _httpRequestVersion = yandexStorageOptions.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } - public ObjectService(YandexStorageOptions options) + public ObjectService(YandexStorageOptions options, HttpClient client) { _protocol = options.Protocol; _bucketName = options.BucketName; @@ -50,15 +53,15 @@ public ObjectService(YandexStorageOptions options) _accessKey = options.AccessKey; _secretKey = options.SecretKey; _hostName = options.HostName; + _client = client; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } public async Task GetAsync(string filename) { var formattedPath = FormatPath(filename); - var requestMessage = await PrepareGetRequestAsync(formattedPath); - - var response = new S3ObjectGetResponse(await _client.SendAsync(requestMessage)); + var response = new S3ObjectGetResponse(await _client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead)); return response; } @@ -139,7 +142,10 @@ public async Task DeleteMultipleAsync(DeleteMult private async Task PrepareGetRequestAsync(string filename) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; requestMessage.Headers.Add("Host", _endpoint); requestMessage.Headers.Add("X-Amz-Content-Sha256", await AwsV4SignatureCalculator.GetPayloadHashAsync(requestMessage)); @@ -157,7 +163,10 @@ private async Task PrepareGetRequestAsync(string filename) private async Task PreparePutRequestAsync(Stream stream, string filename) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; ByteArrayContent content; if (stream is MemoryStream ms) @@ -191,7 +200,10 @@ private async Task PreparePutRequestAsync(Stream stream, str private async Task PreparePutRequestAsync(byte[] byteArr, string filename) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; var content = new ByteArrayContent(byteArr); @@ -213,7 +225,10 @@ private async Task PreparePutRequestAsync(byte[] byteArr, st private async Task PrepareDeleteRequestAsync(string storageFileName) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Delete, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{storageFileName}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{storageFileName}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; requestMessage.Headers.Add("Host", _endpoint); requestMessage.Headers.Add("X-Amz-Content-Sha256", await AwsV4SignatureCalculator.GetPayloadHashAsync(requestMessage)); @@ -231,7 +246,10 @@ private async Task PrepareDeleteRequestAsync(string storageF private async Task PrepareDeleteMultipleRequestAsync(DeleteMultipleObjectsParameters parameters) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri($"{_protocol}://{_endpoint}/{_bucketName}?delete")); + var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri($"{_protocol}://{_endpoint}/{_bucketName}?delete")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; var xmlSerializer = new XmlSerializer(typeof(DeleteMultipleObjectsParameters)); diff --git a/AspNetCore.Yandex.ObjectStorage/YandexStorageService.cs b/AspNetCore.Yandex.ObjectStorage/YandexStorageService.cs index 5d5eb8c..1f4340e 100644 --- a/AspNetCore.Yandex.ObjectStorage/YandexStorageService.cs +++ b/AspNetCore.Yandex.ObjectStorage/YandexStorageService.cs @@ -19,35 +19,39 @@ public class YandexStorageService : IYandexStorageService private readonly string _endpoint; private readonly string _accessKey; private readonly string _secretKey; - private static readonly HttpClient _client = new HttpClient(); + private readonly HttpClient _client; + private readonly Version _httpRequestVersion; public IObjectService ObjectService { get; } public IBucketService BucketService { get; } - public YandexStorageService(IOptions options) + public YandexStorageService(IOptions options, HttpClient client) { var yandexStorageOptions = options.Value; - ObjectService = new ObjectService(yandexStorageOptions); - BucketService = new BucketService(yandexStorageOptions); - + ObjectService = new ObjectService(yandexStorageOptions, client); + BucketService = new BucketService(yandexStorageOptions, client); + _client = client; _protocol = yandexStorageOptions.Protocol; _bucketName = yandexStorageOptions.BucketName; _location = yandexStorageOptions.Location; _endpoint = yandexStorageOptions.Endpoint; _accessKey = yandexStorageOptions.AccessKey; _secretKey = yandexStorageOptions.SecretKey; + _httpRequestVersion = yandexStorageOptions.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } - public YandexStorageService(YandexStorageOptions options) + public YandexStorageService(YandexStorageOptions options, HttpClient client) { - ObjectService = new ObjectService(options); - BucketService = new BucketService(options); + ObjectService = new ObjectService(options, client); + BucketService = new BucketService(options, client); _protocol = options.Protocol; _bucketName = options.BucketName; _location = options.Location; _endpoint = options.Endpoint; _accessKey = options.AccessKey; _secretKey = options.SecretKey; + _client = client; + _httpRequestVersion = options.UseHttp2 ? new Version(2, 0) : new Version(1, 1); } public async Task TryConnectAsync() @@ -75,7 +79,10 @@ public async Task TryGetAsync(string filename) private async Task PrepareGetRequestAsync() { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; requestMessage.Headers.Add("Host", _endpoint); @@ -94,7 +101,10 @@ private async Task PrepareGetRequestAsync() private async Task PrepareGetRequestAsync(string filename) { var calculator = new AwsV4SignatureCalculator(_secretKey, _location); - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_protocol}://{_endpoint}/{_bucketName}/{filename}")) + { + Version = _httpRequestVersion + }; var value = DateTime.UtcNow; requestMessage.Headers.Add("Host", _endpoint); requestMessage.Headers.Add("X-Amz-Content-Sha256", await AwsV4SignatureCalculator.GetPayloadHashAsync(requestMessage)); diff --git a/README.md b/README.md index 889cc8e..fe67af7 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ by default, it reads a section with the name `YandexObjectStorage`, for example, "AccessKey" : "your-access-key", "SecretKey" : "your-secret-key", "Protocol" : "https", - "Location" : "us-east-1" + "Location" : "us-east-1", + "UseHttp2" : true } ``` @@ -41,6 +42,7 @@ string Location - by default -> "us-east-1" string Endpoint - by default -> "storage.yandexcloud.net" string AccessKey string SecretKey +boolean UseHttp2 - by default -> true ``` ## Usage examples diff --git a/Sample/Sample.csproj b/Sample/Sample.csproj index b752a1a..80e5d59 100644 --- a/Sample/Sample.csproj +++ b/Sample/Sample.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 InProcess diff --git a/Sample/Startup.cs b/Sample/Startup.cs index 700ad55..d751c0d 100644 --- a/Sample/Startup.cs +++ b/Sample/Startup.cs @@ -21,6 +21,15 @@ public void ConfigureServices(IServiceCollection services) { services.AddOptions(); + // 1. Way one + // services.AddYandexObjectStorage(cfg => + // { + // cfg.AccessKey = "your-bucket"; + // cfg.BucketName = "your-access-key"; + // cfg.SecretKey = "your-secret-key"; + // }); + + // 2. Way two services.AddYandexObjectStorage(Configuration); }