Skip to content

Commit 43b2a8a

Browse files
Merge branch 'master' into patch-1
2 parents 05a31ca + 2313fbb commit 43b2a8a

34 files changed

+936
-664
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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;
7+
using Azure.Storage.Blobs;
8+
using Azure.Storage.Blobs.Models;
9+
using Microsoft.Extensions.Options;
10+
using SixLabors.ImageSharp.Web.Resolvers;
11+
using SixLabors.ImageSharp.Web.Resolvers.Azure;
12+
13+
namespace SixLabors.ImageSharp.Web.Caching.Azure
14+
{
15+
/// <summary>
16+
/// Implements an Azure Blob Storage based cache.
17+
/// </summary>
18+
public class AzureBlobStorageCache : IImageCache
19+
{
20+
private readonly BlobContainerClient container;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="AzureBlobStorageCache"/> class.
24+
/// </summary>
25+
/// <param name="cacheOptions">The cache options.</param>
26+
public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions)
27+
{
28+
Guard.NotNull(cacheOptions, nameof(cacheOptions));
29+
AzureBlobStorageCacheOptions options = cacheOptions.Value;
30+
31+
this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName);
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+
/// <summary>
61+
/// Creates a new container under the specified account if a container
62+
/// with the same name does not already exist.
63+
/// </summary>
64+
/// <param name="options">The Azure Blob Storage cache options.</param>
65+
/// <param name="accessType">
66+
/// Optionally specifies whether data in the container may be accessed publicly and
67+
/// the level of access. <see cref="PublicAccessType.BlobContainer"/>
68+
/// specifies full public read access for container and blob data. Clients can enumerate
69+
/// blobs within the container via anonymous request, but cannot enumerate containers
70+
/// within the storage account. <see cref="PublicAccessType.Blob"/>
71+
/// specifies public read access for blobs. Blob data within this container can be
72+
/// read via anonymous request, but container data is not available. Clients cannot
73+
/// enumerate blobs within the container via anonymous request. <see cref="PublicAccessType.None"/>
74+
/// specifies that the container data is private to the account owner.
75+
/// </param>
76+
/// <returns>
77+
/// If the container does not already exist, a <see cref="Response{T}"/> describing the newly
78+
/// created container. If the container already exists, <see langword="null"/>.
79+
/// </returns>
80+
public static Response<BlobContainerInfo> CreateIfNotExists(
81+
AzureBlobStorageCacheOptions options,
82+
PublicAccessType accessType)
83+
=> new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType);
84+
}
85+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
/// <see href="https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string."/>
14+
/// </summary>
15+
public string ConnectionString { get; set; }
16+
17+
/// <summary>
18+
/// Gets or sets the Azure Blob Storage container name.
19+
/// Must conform to Azure Blob Storage containiner naming guidlines.
20+
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"/>
21+
/// </summary>
22+
public string ContainerName { get; set; }
23+
}
24+
}

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/Providers/AzureBlobStorageImageProviderOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class AzureBlobContainerClientOptions
2323
{
2424
/// <summary>
2525
/// Gets or sets the Azure Blob Storage connection string.
26+
/// <see href="https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string."/>
2627
/// </summary>
2728
public string ConnectionString { get; set; }
2829

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/Resolvers/AzureBlobStorageImageResolver.cs

Lines changed: 19 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,29 @@ 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+
if (cacheControl?.MaxAge.HasValue == true)
41+
{
42+
maxAge = cacheControl.MaxAge.Value;
43+
}
44+
}
45+
46+
return new ImageMetadata(properties.LastModified.UtcDateTime, maxAge, properties.ContentLength);
3247
}
3348

3449
/// <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
{

src/ImageSharp.Web/Commands/Converters/EnumConverter.cs

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

44
using System;
@@ -15,10 +15,9 @@ internal sealed class EnumConverter : CommandConverter
1515
/// <inheritdoc/>
1616
public override object ConvertFrom(CultureInfo culture, string value, Type propertyType)
1717
{
18-
if (value == null)
18+
if (string.IsNullOrWhiteSpace(value))
1919
{
20-
// TODO: How can we get the default enum, or should we return null?
21-
return base.ConvertFrom(culture, null, propertyType);
20+
return Activator.CreateInstance(propertyType);
2221
}
2322

2423
try
@@ -51,4 +50,4 @@ public override object ConvertFrom(CultureInfo culture, string value, Type prope
5150
/// <returns>The <see cref="T:String[]"/>.</returns>
5251
private static string[] GetStringArray(string input, char separator) => input.Split(separator).Select(s => s.Trim()).ToArray();
5352
}
54-
}
53+
}

src/ImageSharp.Web/Commands/Converters/IntegralNumberConverter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ internal sealed class IntegralNumberConverter<T> : CommandConverter
1616
/// <inheritdoc/>
1717
public override object ConvertFrom(CultureInfo culture, string value, Type propertyType)
1818
{
19-
if (value == null || Array.IndexOf(TypeConstants.IntegralTypes, propertyType) < 0)
19+
if (string.IsNullOrWhiteSpace(value)
20+
|| Array.IndexOf(TypeConstants.IntegralTypes, propertyType) < 0)
2021
{
21-
return base.ConvertFrom(culture, null, propertyType);
22+
return default(T);
2223
}
2324

2425
// Round the value to the nearest decimal value

0 commit comments

Comments
 (0)