Skip to content

Commit e06dac5

Browse files
Fix, cleanup, and optimize new cloud provider code.
1 parent 8509b73 commit e06dac5

14 files changed

+130
-68
lines changed

src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,13 @@ internal static class AmazonS3ClientFactory
1414
/// with the same name does not already exist.
1515
/// </summary>
1616
/// <param name="options">The AWS S3 Storage cache options.</param>
17-
/// <param name="serviceProvider">The current service provider.</param>
1817
/// <returns>
1918
/// A new <see cref="AmazonS3Client"/>.
2019
/// </returns>
2120
/// <exception cref="ArgumentException">Invalid configuration.</exception>
22-
public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options, IServiceProvider serviceProvider)
21+
public static AmazonS3Client CreateClient(IAWSS3BucketClientOptions options)
2322
{
24-
if (options.S3ClientProvider != null)
25-
{
26-
return options.S3ClientProvider(options, serviceProvider);
27-
}
28-
else if (!string.IsNullOrWhiteSpace(options.Endpoint))
23+
if (!string.IsNullOrWhiteSpace(options.Endpoint))
2924
{
3025
// AccessKey can be empty.
3126
// AccessSecret can be empty.

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
1313
/// <summary>
1414
/// Implements an AWS S3 Storage based cache.
1515
/// </summary>
16-
public class AWSS3StorageCache : IImageCache
16+
public class AWSS3StorageCache : IImageCache, IDisposable
1717
{
1818
private readonly IAmazonS3 amazonS3Client;
1919
private readonly string bucketName;
2020
private readonly string cacheFolder;
21+
private bool isDisposed;
2122

2223
/// <summary>
2324
/// Initializes a new instance of the <see cref="AWSS3StorageCache"/> class.
@@ -29,7 +30,11 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions, IServi
2930
Guard.NotNull(cacheOptions, nameof(cacheOptions));
3031
AWSS3StorageCacheOptions options = cacheOptions.Value;
3132
this.bucketName = options.BucketName;
32-
this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options, serviceProvider);
33+
34+
this.amazonS3Client =
35+
options.S3ClientFactory?.Invoke(options, serviceProvider)
36+
?? AmazonS3ClientFactory.CreateClient(options);
37+
3338
this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder)
3439
? string.Empty
3540
: options.CacheFolder.Trim().Trim('/') + '/';
@@ -84,23 +89,41 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
8489
/// and object data. <see cref="S3CannedACL.Private"/> specifies that the bucket
8590
/// data is private to the account owner.
8691
/// </param>
87-
/// <param name="serviceProvider">The current service provider.</param>
8892
/// <returns>
8993
/// If the bucket does not already exist, a <see cref="PutBucketResponse"/> describing the newly
9094
/// created bucket. If the container already exists, <see langword="null"/>.
9195
/// </returns>
92-
public static PutBucketResponse? CreateIfNotExists(
93-
AWSS3StorageCacheOptions options,
94-
S3CannedACL acl,
95-
IServiceProvider serviceProvider)
96-
=> AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl, serviceProvider));
97-
98-
private static async Task<PutBucketResponse?> CreateIfNotExistsAsync(
99-
AWSS3StorageCacheOptions options,
100-
S3CannedACL acl,
101-
IServiceProvider serviceProvider)
96+
public static PutBucketResponse? CreateIfNotExists(AWSS3StorageCacheOptions options, S3CannedACL acl)
97+
=> AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl));
98+
99+
/// <summary>
100+
/// Releases the unmanaged resources used by the <see cref="AWSS3StorageCache"/> and optionally releases the managed resources.
101+
/// </summary>
102+
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
103+
protected virtual void Dispose(bool disposing)
104+
{
105+
if (!this.isDisposed)
106+
{
107+
if (disposing)
108+
{
109+
this.amazonS3Client?.Dispose();
110+
}
111+
112+
this.isDisposed = true;
113+
}
114+
}
115+
116+
/// <inheritdoc/>
117+
public void Dispose()
118+
{
119+
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
120+
this.Dispose(disposing: true);
121+
GC.SuppressFinalize(this);
122+
}
123+
124+
private static async Task<PutBucketResponse?> CreateIfNotExistsAsync(AWSS3StorageCacheOptions options, S3CannedACL acl)
102125
{
103-
AmazonS3Client client = AmazonS3ClientFactory.CreateClient(options, serviceProvider);
126+
AmazonS3Client client = AmazonS3ClientFactory.CreateClient(options);
104127

105128
bool foundBucket = false;
106129
ListBucketsResponse listBucketsResponse = await client.ListBucketsAsync();

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
1111
public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
1212
{
1313
/// <inheritdoc/>
14-
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientProvider { get; set; } = null!;
14+
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientFactory { get; set; }
1515

1616
/// <inheritdoc/>
1717
public string? Region { get; set; }

src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,53 @@ namespace SixLabors.ImageSharp.Web;
1111
public interface IAWSS3BucketClientOptions
1212
{
1313
/// <summary>
14-
/// Gets or sets a custom Azure AmazonS3Client provider
14+
/// Gets or sets a custom a factory method to create an <see cref="AmazonS3Client"/>.
1515
/// </summary>
16-
Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientProvider { get; set; }
16+
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientFactory { get; set; }
1717

1818
/// <summary>
1919
/// Gets or sets the AWS region endpoint (us-east-1/us-west-1/ap-southeast-2).
2020
/// </summary>
21-
string? Region { get; set; }
21+
public string? Region { get; set; }
2222

2323
/// <summary>
24-
/// Gets or sets the AWS bucket name.
24+
/// Gets or sets the AWS bucket name. Cannot be <see langword="null"/>.
2525
/// </summary>
26-
string BucketName { get; set; }
26+
public string BucketName { get; set; }
2727

2828
/// <summary>
2929
/// Gets or sets the AWS key - Can be used to override keys provided by the environment.
3030
/// If deploying inside an EC2 instance AWS keys will already be available via environment
31-
/// variables and don't need to be specified. Follow AWS best security practices on <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
31+
/// variables and don't need to be specified. Follow AWS best security practices on
32+
/// <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
3233
/// </summary>
33-
string? AccessKey { get; set; }
34+
public string? AccessKey { get; set; }
3435

3536
/// <summary>
3637
/// Gets or sets the AWS endpoint - used to override the default service endpoint.
3738
/// If deploying inside an EC2 instance AWS keys will already be available via environment
38-
/// variables and don't need to be specified. Follow AWS best security practices on <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
39+
/// variables and don't need to be specified. Follow AWS best security practices on
40+
/// <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
3941
/// </summary>
40-
string? AccessSecret { get; set; }
42+
public string? AccessSecret { get; set; }
4143

4244
/// <summary>
4345
/// Gets or sets the AWS endpoint - used for testing to over region endpoint allowing it
4446
/// to be set to localhost.
4547
/// </summary>
46-
string? Endpoint { get; set; }
48+
public string? Endpoint { get; set; }
4749

4850
/// <summary>
4951
/// Gets or sets a value indicating whether the S3 accelerate endpoint is used.
50-
/// The feature must be enabled on the bucket. Follow AWS instruction on <see href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html"/>.
52+
/// The feature must be enabled on the bucket. Follow AWS instruction on
53+
/// <see href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html"/>.
5154
/// </summary>
52-
bool UseAccelerateEndpoint { get; set; }
55+
public bool UseAccelerateEndpoint { get; set; }
5356

5457
/// <summary>
5558
/// Gets or sets a value indicating the timeout for the S3 client.
56-
/// If the value is set, the value is assigned to the Timeout property of the HttpWebRequest/HttpClient object used to send requests.
59+
/// If the value is set, the value is assigned to the Timeout property of the HttpWebRequest/HttpClient
60+
/// object used to send requests.
5761
/// </summary>
58-
TimeSpan? Timeout { get; set; }
62+
public TimeSpan? Timeout { get; set; }
5963
}

src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Web.Providers.AWS;
1414
/// <summary>
1515
/// Returns images stored in AWS S3.
1616
/// </summary>
17-
public class AWSS3StorageImageProvider : IImageProvider
17+
public class AWSS3StorageImageProvider : IImageProvider, IDisposable
1818
{
1919
/// <summary>
2020
/// Character array to remove from paths.
@@ -29,6 +29,7 @@ private readonly Dictionary<string, AmazonS3Client> buckets
2929

3030
private readonly AWSS3StorageImageProviderOptions storageOptions;
3131
private Func<HttpContext, bool>? match;
32+
private bool isDisposed;
3233

3334
/// <summary>
3435
/// Contains various helper methods based on the current configuration.
@@ -41,7 +42,10 @@ private readonly Dictionary<string, AmazonS3Client> buckets
4142
/// <param name="storageOptions">The S3 storage options</param>
4243
/// <param name="formatUtilities">Contains various format helper methods based on the current configuration.</param>
4344
/// <param name="serviceProvider">The current service provider.</param>
44-
public AWSS3StorageImageProvider(IOptions<AWSS3StorageImageProviderOptions> storageOptions, FormatUtilities formatUtilities, IServiceProvider serviceProvider)
45+
public AWSS3StorageImageProvider(
46+
IOptions<AWSS3StorageImageProviderOptions> storageOptions,
47+
FormatUtilities formatUtilities,
48+
IServiceProvider serviceProvider)
4549
{
4650
Guard.NotNull(storageOptions, nameof(storageOptions));
4751

@@ -51,7 +55,11 @@ public AWSS3StorageImageProvider(IOptions<AWSS3StorageImageProviderOptions> stor
5155

5256
foreach (AWSS3BucketClientOptions bucket in this.storageOptions.S3Buckets)
5357
{
54-
this.buckets.Add(bucket.BucketName, AmazonS3ClientFactory.CreateClient(bucket, serviceProvider));
58+
AmazonS3Client s3Client =
59+
bucket.S3ClientFactory?.Invoke(bucket, serviceProvider)
60+
?? AmazonS3ClientFactory.CreateClient(bucket);
61+
62+
this.buckets.Add(bucket.BucketName, s3Client);
5563
}
5664
}
5765

@@ -88,7 +96,7 @@ public bool IsValidRequest(HttpContext context)
8896
}
8997

9098
int index = path.IndexOfAny(SlashChars);
91-
string nameToMatch = index != -1 ? path.Substring(0, index) : path;
99+
string nameToMatch = index != -1 ? path[..index] : path;
92100

93101
foreach (string k in this.buckets.Keys)
94102
{
@@ -107,7 +115,7 @@ public bool IsValidRequest(HttpContext context)
107115
}
108116

109117
// Key should be the remaining path string.
110-
string key = path.Substring(bucketName.Length).TrimStart(SlashChars);
118+
string key = path[bucketName.Length..].TrimStart(SlashChars);
111119

112120
if (string.IsNullOrWhiteSpace(key))
113121
{
@@ -125,7 +133,7 @@ public bool IsValidRequest(HttpContext context)
125133

126134
private bool IsMatch(HttpContext context)
127135
{
128-
// Only match loosly here for performance.
136+
// Only match loosely here for performance.
129137
// Path matching conflicts should be dealt with by configuration.
130138
string? path = context.Request.Path.Value?.TrimStart(SlashChars);
131139

@@ -178,6 +186,37 @@ private static async Task<KeyExistsResult> KeyExists(IAmazonS3 s3Client, string
178186
}
179187
}
180188

189+
/// <summary>
190+
/// Releases the unmanaged resources used by the <see cref="AWSS3StorageImageProvider"/> and optionally releases the managed resources.
191+
/// </summary>
192+
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
193+
194+
protected virtual void Dispose(bool disposing)
195+
{
196+
if (!this.isDisposed)
197+
{
198+
if (disposing)
199+
{
200+
foreach (AmazonS3Client client in this.buckets.Values)
201+
{
202+
client?.Dispose();
203+
}
204+
205+
this.buckets.Clear();
206+
}
207+
208+
this.isDisposed = true;
209+
}
210+
}
211+
212+
/// <inheritdoc/>
213+
public void Dispose()
214+
{
215+
this.Dispose(true);
216+
217+
GC.SuppressFinalize(this);
218+
}
219+
181220
private readonly record struct KeyExistsResult(GetObjectMetadataResponse Metadata)
182221
{
183222
public bool Exists => this.Metadata is not null;

src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class AWSS3StorageImageProviderOptions
2222
public class AWSS3BucketClientOptions : IAWSS3BucketClientOptions
2323
{
2424
/// <inheritdoc/>
25-
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientProvider { get; set; } = null!;
25+
public Func<IAWSS3BucketClientOptions, IServiceProvider, AmazonS3Client>? S3ClientFactory { get; set; }
2626

2727
/// <inheritdoc/>
2828
public string? Region { get; set; }

src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions
2828
Guard.NotNull(cacheOptions, nameof(cacheOptions));
2929
AzureBlobStorageCacheOptions options = cacheOptions.Value;
3030

31-
this.container = options.BlobContainerClientProvider == null
32-
? new BlobContainerClient(options.ConnectionString, options.ContainerName)
33-
: options.BlobContainerClientProvider(options, serviceProvider);
34-
this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder)
31+
this.container =
32+
options.BlobContainerClientFactory?.Invoke(options, serviceProvider)
33+
?? new BlobContainerClient(options.ConnectionString, options.ContainerName);
34+
35+
this.cacheFolder = string.IsNullOrWhiteSpace(options.CacheFolder)
3536
? string.Empty
3637
: options.CacheFolder.Trim().Trim('/') + '/';
3738
}

src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace SixLabors.ImageSharp.Web.Caching.Azure;
1111
public class AzureBlobStorageCacheOptions
1212
{
1313
/// <summary>
14-
/// Gets or sets a custom Azure BlobContainerClient provider
14+
/// Gets or sets a factory method to create an <see cref="BlobContainerClient"/>.
1515
/// </summary>
16-
public Func<AzureBlobStorageCacheOptions, IServiceProvider, BlobContainerClient>? BlobContainerClientProvider { get; set; } = null!;
16+
public Func<AzureBlobStorageCacheOptions, IServiceProvider, BlobContainerClient>? BlobContainerClientFactory { get; set; }
1717

1818
/// <summary>
1919
/// Gets or sets the Azure Blob Storage connection string.

src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProvider.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ public AzureBlobStorageImageProvider(
5353

5454
foreach (AzureBlobContainerClientOptions container in storageOptions.Value.BlobContainers)
5555
{
56-
BlobContainerClient containerClient = container.BlobContainerClientProvider == null
57-
? new BlobContainerClient(container.ConnectionString, container.ContainerName)
58-
: container.BlobContainerClientProvider(container, serviceProvider);
56+
BlobContainerClient client =
57+
container.BlobContainerClientFactory?.Invoke(container, serviceProvider)
58+
?? new BlobContainerClient(container.ConnectionString, container.ContainerName);
5959

60-
this.containers.Add(container.ContainerName!, containerClient);
60+
this.containers.Add(client.Name, client);
6161
}
6262
}
6363

@@ -109,7 +109,7 @@ public Func<HttpContext, bool> Match
109109
}
110110

111111
// Blob name should be the remaining path string.
112-
string blobName = path.Substring(containerName.Length).TrimStart(SlashChars);
112+
string blobName = path[containerName.Length..].TrimStart(SlashChars);
113113

114114
if (string.IsNullOrWhiteSpace(blobName))
115115
{
@@ -132,7 +132,7 @@ public bool IsValidRequest(HttpContext context)
132132

133133
private bool IsMatch(HttpContext context)
134134
{
135-
// Only match loosly here for performance.
135+
// Only match loosely here for performance.
136136
// Path matching conflicts should be dealt with by configuration.
137137
string? path = context.Request.Path.Value?.TrimStart(SlashChars);
138138

src/ImageSharp.Web.Providers.Azure/Providers/AzureBlobStorageImageProviderOptions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ public class AzureBlobStorageImageProviderOptions
2222
public class AzureBlobContainerClientOptions
2323
{
2424
/// <summary>
25-
/// Gets or sets a custom Azure BlobServiceClient provider
25+
/// Gets or sets a factory method to create an <see cref="BlobContainerClient"/>.
2626
/// </summary>
27-
public Func<AzureBlobContainerClientOptions, IServiceProvider, BlobContainerClient>? BlobContainerClientProvider { get; set; } = null!;
27+
public Func<AzureBlobContainerClientOptions, IServiceProvider, BlobContainerClient>? BlobContainerClientFactory { get; set; }
2828

2929
/// <summary>
3030
/// Gets or sets the Azure Blob Storage connection string.
3131
/// <see href="https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string."/>
3232
/// </summary>
33-
public string? ConnectionString { get; set; }
33+
public string ConnectionString { get; set; } = null!;
3434

3535
/// <summary>
3636
/// Gets or sets the Azure Blob Storage container name.
37-
/// Must conform to Azure Blob Storage container naming guidlines.
37+
/// Must conform to Azure Blob Storage container naming guidelines.
3838
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"/>
3939
/// </summary>
40-
public string? ContainerName { get; set; }
40+
public string ContainerName { get; set; } = null!;
4141
}

0 commit comments

Comments
 (0)