diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs index 77919b80ef..e5a8a853d7 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs @@ -57,9 +57,9 @@ public class AwsBlobProviderConfiguration set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Policy, value); } - public string Region { - get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Region); - set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Region, Check.NotNull(value, nameof(value))); + public string? Region { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.Region); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Region, value); } /// diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs index dc2a7fe56b..1572adca7c 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs @@ -30,7 +30,9 @@ public class DefaultAmazonS3ClientFactory : IAmazonS3ClientFactory, ITransientDe public virtual async Task GetAmazonS3Client( AwsBlobProviderConfiguration configuration) { - var region = RegionEndpoint.GetBySystemName(configuration.Region); + var region = !configuration.Region.IsNullOrWhiteSpace() + ? RegionEndpoint.GetBySystemName(configuration.Region) + : null; var clientConfig = CreateS3ClientConfig(configuration, region); if (configuration.UseCredentials) @@ -58,12 +60,15 @@ public class DefaultAmazonS3ClientFactory : IAmazonS3ClientFactory, ITransientDe return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey, clientConfig); } - protected virtual AmazonS3Config CreateS3ClientConfig(AwsBlobProviderConfiguration configuration, RegionEndpoint region) + protected virtual AmazonS3Config CreateS3ClientConfig(AwsBlobProviderConfiguration configuration, RegionEndpoint? region) { - var clientConfig = new AmazonS3Config + var clientConfig = new AmazonS3Config(); + + // Set region only if it's provided (for AWS S3) + if (region != null) { - RegionEndpoint = region - }; + clientConfig.RegionEndpoint = region; + } if (!configuration.ServiceURL.IsNullOrWhiteSpace()) { diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs index 6edd0fc669..984a66bbc1 100644 --- a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs @@ -69,23 +69,39 @@ public class AbpBlobStoringAwsTestModule : AbpModule private async Task DeleteBucketAsync(ApplicationShutdownContext context) { - var amazonS3Client = await context.ServiceProvider.GetRequiredService() - .GetAmazonS3Client(_configuration); + // Skip bucket deletion if configuration is not properly set (e.g., in unit tests) + if (_configuration == null || + string.IsNullOrWhiteSpace(_configuration.AccessKeyId) || + string.IsNullOrWhiteSpace(_configuration.SecretAccessKey) || + (string.IsNullOrWhiteSpace(_configuration.Region) && string.IsNullOrWhiteSpace(_configuration.ServiceURL))) + { + return; + } - if (await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, _randomContainerName)) + try { - var blobs = await amazonS3Client.ListObjectsAsync(_randomContainerName); + var amazonS3Client = await context.ServiceProvider.GetRequiredService() + .GetAmazonS3Client(_configuration); - if (blobs.S3Objects.Any()) + if (await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, _randomContainerName)) { - await amazonS3Client.DeleteObjectsAsync(new DeleteObjectsRequest + var blobs = await amazonS3Client.ListObjectsAsync(_randomContainerName); + + if (blobs.S3Objects.Any()) { - BucketName = _randomContainerName, - Objects = blobs.S3Objects.Select(o => new KeyVersion { Key = o.Key }).ToList() - }); - } + await amazonS3Client.DeleteObjectsAsync(new DeleteObjectsRequest + { + BucketName = _randomContainerName, + Objects = blobs.S3Objects.Select(o => new KeyVersion { Key = o.Key }).ToList() + }); + } - await amazonS3Client.DeleteBucketAsync(_randomContainerName); + await amazonS3Client.DeleteBucketAsync(_randomContainerName); + } + } + catch + { + // Ignore errors during test cleanup } } } diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory_Tests.cs index cf5b973ee0..5f7539de9e 100644 --- a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory_Tests.cs +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory_Tests.cs @@ -61,4 +61,27 @@ public class DefaultAmazonS3ClientFactory_Tests : AbpBlobStoringAwsTestBase s3Client.Config.ServiceURL.ShouldBeNull(); // Should use default AWS S3 service ((AmazonS3Config)s3Client.Config).ForcePathStyle.ShouldBeFalse(); // Should be false for AWS S3 } + + [Fact] + public async Task Should_Create_S3Client_Without_Region_For_S3Compatible_Services() + { + // Arrange + var containerConfiguration = new BlobContainerConfiguration(); + + var awsConfiguration = new AwsBlobProviderConfiguration(containerConfiguration) + { + AccessKeyId = "test-access-key", + SecretAccessKey = "test-secret-key", + ServiceURL = "https://minio.example.com:9000" + // Region not set - should work for S3-compatible services + }; + + // Act + using var s3Client = await _amazonS3ClientFactory.GetAmazonS3Client(awsConfiguration); + + // Assert + s3Client.ShouldNotBeNull(); + s3Client.Config.ServiceURL.ShouldBe("https://minio.example.com:9000/"); // AWS SDK automatically appends trailing slash + ((AmazonS3Config)s3Client.Config).ForcePathStyle.ShouldBeTrue(); // Should be enabled for S3-compatible services + } } \ No newline at end of file