Skip to content

Commit 97852c1

Browse files
Add Azure cache implementation
1 parent 3554640 commit 97852c1

16 files changed

+335
-98
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
using Azure.Storage.Blobs;
7+
using Azure.Storage.Blobs.Models;
8+
using Microsoft.Extensions.Options;
9+
using SixLabors.ImageSharp.Web.Resolvers;
10+
using SixLabors.ImageSharp.Web.Resolvers.Azure;
11+
12+
namespace SixLabors.ImageSharp.Web.Caching.Azure
13+
{
14+
/// <summary>
15+
/// Implements an Azure Blob Storage based cache.
16+
/// </summary>
17+
public class AzureBlobStorageCache : IImageCache
18+
{
19+
private readonly BlobContainerClient container;
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="AzureBlobStorageCache"/> class.
23+
/// </summary>
24+
/// <param name="cacheOptions">The cache options.</param>
25+
public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions)
26+
{
27+
Guard.NotNull(cacheOptions, nameof(cacheOptions));
28+
AzureBlobStorageCacheOptions options = cacheOptions.Value;
29+
30+
this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName);
31+
this.container.CreateIfNotExistsAsync(PublicAccessType.None);
32+
}
33+
34+
/// <inheritdoc/>
35+
public async Task<IImageCacheResolver> GetAsync(string key)
36+
{
37+
BlobClient blob = this.container.GetBlobClient(key);
38+
39+
if (!await blob.ExistsAsync())
40+
{
41+
return null;
42+
}
43+
44+
return new AzureBlobStorageCacheResolver(blob);
45+
}
46+
47+
/// <inheritdoc/>
48+
public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
49+
{
50+
BlobClient blob = this.container.GetBlobClient(key);
51+
52+
var headers = new BlobHttpHeaders
53+
{
54+
ContentType = metadata.ContentType,
55+
};
56+
57+
return blob.UploadAsync(stream, httpHeaders: headers, metadata: metadata.ToDictionary());
58+
}
59+
}
60+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Web.Caching.Azure
5+
{
6+
/// <summary>
7+
/// Configuration options for the <see cref="AzureBlobStorageCache"/>.
8+
/// </summary>
9+
public class AzureBlobStorageCacheOptions
10+
{
11+
/// <summary>
12+
/// Gets or sets the Azure Blob Storage connection string.
13+
/// </summary>
14+
public string ConnectionString { get; set; }
15+
16+
/// <summary>
17+
/// Gets or sets the Azure Blob Storage container name.
18+
/// Must conform to Azure Blob Storage containiner naming guidlines.
19+
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"/>
20+
/// </summary>
21+
public string ContainerName { get; set; }
22+
}
23+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
using Azure.Storage.Blobs;
7+
using Azure.Storage.Blobs.Models;
8+
using SixLabors.ImageSharp.Web.Caching.Azure;
9+
10+
namespace SixLabors.ImageSharp.Web.Resolvers.Azure
11+
{
12+
/// <summary>
13+
/// Provides means to manage image buffers within the <see cref="AzureBlobStorageCache"/>.
14+
/// </summary>
15+
public class AzureBlobStorageCacheResolver : IImageCacheResolver
16+
{
17+
private readonly BlobClient blob;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="AzureBlobStorageCacheResolver"/> class.
21+
/// </summary>
22+
/// <param name="blob">The Azure blob.</param>
23+
public AzureBlobStorageCacheResolver(BlobClient blob)
24+
=> this.blob = blob;
25+
26+
/// <inheritdoc/>
27+
public async Task<ImageCacheMetadata> GetMetaDataAsync()
28+
{
29+
// I've had a good read through the SDK source and I believe we cannot get
30+
// a 304 here since 'If-Modified-Since' header is not set by default.
31+
BlobProperties properties = await this.blob.GetPropertiesAsync();
32+
return ImageCacheMetadata.FromDictionary(properties.Metadata);
33+
}
34+
35+
/// <inheritdoc/>
36+
public async Task<Stream> OpenReadAsync()
37+
=> (await this.blob.DownloadAsync()).Value.Content;
38+
}
39+
}

src/ImageSharp.Web.Providers.Azure/ImageSharp.Web.Providers.Azure.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<ItemGroup>
1616
<PackageReference Include="Azure.Storage.Blobs" />
17-
<PackageReference Include="Microsoft.SourceLink.GitHub"/>
17+
<PackageReference Include="Microsoft.SourceLink.GitHub" />
1818
<PackageReference Include="MinVer" PrivateAssets="All" />
1919
</ItemGroup>
2020

src/ImageSharp.Web.Providers.Azure/Resolvers/AzureBlobStorageImageResolver.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.IO;
6+
using System.Net.Http.Headers;
57
using System.Threading.Tasks;
6-
using Azure;
78
using Azure.Storage.Blobs;
89
using Azure.Storage.Blobs.Models;
910

@@ -20,15 +21,30 @@ public class AzureBlobStorageImageResolver : IImageResolver
2021
/// Initializes a new instance of the <see cref="AzureBlobStorageImageResolver"/> class.
2122
/// </summary>
2223
/// <param name="blob">The Azure blob.</param>
23-
public AzureBlobStorageImageResolver(BlobClient blob) => this.blob = blob;
24+
public AzureBlobStorageImageResolver(BlobClient blob)
25+
=> this.blob = blob;
2426

2527
/// <inheritdoc/>
2628
public async Task<ImageMetadata> GetMetaDataAsync()
2729
{
2830
// I've had a good read through the SDK source and I believe we cannot get
2931
// a 304 here since 'If-Modified-Since' header is not set by default.
30-
Response<BlobProperties> properties = await this.blob.GetPropertiesAsync();
31-
return new ImageMetadata(properties.Value.LastModified.DateTime);
32+
BlobProperties properties = (await this.blob.GetPropertiesAsync()).Value;
33+
34+
// Try to parse the max age from the source. If it's not zero then we pass it along
35+
// to set the cache control headers for the response.
36+
TimeSpan maxAge = TimeSpan.MinValue;
37+
if (CacheControlHeaderValue.TryParse(properties.CacheControl, out CacheControlHeaderValue cacheControl))
38+
{
39+
// Weirdly passing null to TryParse returns true.
40+
TimeSpan sourceMaxAge = cacheControl?.MaxAge.GetValueOrDefault() ?? TimeSpan.Zero;
41+
if (sourceMaxAge != TimeSpan.Zero)
42+
{
43+
maxAge = sourceMaxAge;
44+
}
45+
}
46+
47+
return new ImageMetadata(properties.LastModified.UtcDateTime, maxAge, properties.ContentLength);
3248
}
3349

3450
/// <inheritdoc/>

src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class PhysicalFileSystemCache : IImageCache
4747
/// <summary>
4848
/// Contains various format helper methods based on the current configuration.
4949
/// </summary>
50-
private readonly FormatUtilities formatUtilies;
50+
private readonly FormatUtilities formatUtilities;
5151

5252
/// <summary>
5353
/// Initializes a new instance of the <see cref="PhysicalFileSystemCache"/> class.
@@ -81,7 +81,7 @@ public PhysicalFileSystemCache(
8181
this.fileProvider = new PhysicalFileProvider(this.cacheRootPath);
8282
this.options = options.Value;
8383
this.cachedNameLength = (int)this.options.CachedNameLength;
84-
this.formatUtilies = formatUtilities;
84+
this.formatUtilities = formatUtilities;
8585
}
8686

8787
/// <inheritdoc/>
@@ -143,7 +143,7 @@ public async Task SetAsync(string key, Stream stream, ImageCacheMetadata metadat
143143
/// <param name="metaData">The image metadata.</param>
144144
/// <returns>The <see cref="string"/>.</returns>
145145
private string ToImageFilePath(string path, in ImageCacheMetadata metaData)
146-
=> $"{path}.{this.formatUtilies.GetExtensionFromContentType(metaData.ContentType)}";
146+
=> $"{path}.{this.formatUtilities.GetExtensionFromContentType(metaData.ContentType)}";
147147

148148
/// <summary>
149149
/// Gets the path to the image file based on the supplied root.

src/ImageSharp.Web/Caching/PhysicalFileSystemCacheResolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
using System.IO;
55
using System.Threading.Tasks;
66
using Microsoft.Extensions.FileProviders;
7+
using SixLabors.ImageSharp.Web.Caching;
78

89
namespace SixLabors.ImageSharp.Web.Resolvers
910
{
1011
/// <summary>
11-
/// Provides means to manage image buffers within the physical file system cache.
12+
/// Provides means to manage image buffers within the <see cref="PhysicalFileSystemCache"/>.
1213
/// </summary>
1314
public class PhysicalFileSystemCacheResolver : IImageCacheResolver
1415
{

0 commit comments

Comments
 (0)