Skip to content

Commit b86a247

Browse files
Merge pull request #225 from ronaldbarendse/feature/remove-fileprovider-abstraction
Replace PhysicalFileProvider with File in PhysicalFileSystemCache and PhysicalFileSystemProvider
2 parents 4f0fe2b + d6c7189 commit b86a247

11 files changed

Lines changed: 227 additions & 147 deletions

File tree

samples/ImageSharp.Web.Sample/Startup.cs

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.Hosting;
11-
using Microsoft.Extensions.Options;
1211
using SixLabors.ImageSharp.Web.Caching;
1312
using SixLabors.ImageSharp.Web.Commands;
1413
using SixLabors.ImageSharp.Web.DependencyInjection;
@@ -43,17 +42,17 @@ public void ConfigureServices(IServiceCollection services)
4342
.SetRequestParser<QueryCollectionRequestParser>()
4443
.Configure<PhysicalFileSystemCacheOptions>(options =>
4544
{
45+
options.CacheRootPath = null;
4646
options.CacheFolder = "is-cache";
47+
options.CacheFolderDepth = 8;
4748
})
48-
.SetCache(provider =>
49-
{
50-
return new PhysicalFileSystemCache(
51-
provider.GetRequiredService<IOptions<PhysicalFileSystemCacheOptions>>(),
52-
provider.GetRequiredService<IWebHostEnvironment>(),
53-
provider.GetRequiredService<FormatUtilities>());
54-
})
49+
.SetCache<PhysicalFileSystemCache>()
5550
.SetCacheKey<UriRelativeLowerInvariantCacheKey>()
5651
.SetCacheHash<SHA256CacheHash>()
52+
.Configure<PhysicalFileSystemProviderOptions>(options =>
53+
{
54+
options.ProviderRootPath = null;
55+
})
5756
.AddProvider<PhysicalFileSystemProvider>()
5857
.AddProcessor<ResizeWebProcessor>()
5958
.AddProcessor<FormatWebProcessor>()
@@ -80,18 +79,17 @@ public void ConfigureServices(IServiceCollection services)
8079

8180
private void ConfigureDefaultServicesAndCustomOptions(IServiceCollection services)
8281
{
83-
services.AddImageSharp(
84-
options =>
85-
{
86-
options.Configuration = Configuration.Default;
87-
options.BrowserMaxAge = TimeSpan.FromDays(7);
88-
options.CacheMaxAge = TimeSpan.FromDays(365);
89-
options.CacheHashLength = 8;
90-
options.OnParseCommandsAsync = _ => Task.CompletedTask;
91-
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
92-
options.OnProcessedAsync = _ => Task.CompletedTask;
93-
options.OnPrepareResponseAsync = _ => Task.CompletedTask;
94-
});
82+
services.AddImageSharp(options =>
83+
{
84+
options.Configuration = Configuration.Default;
85+
options.BrowserMaxAge = TimeSpan.FromDays(7);
86+
options.CacheMaxAge = TimeSpan.FromDays(365);
87+
options.CacheHashLength = 8;
88+
options.OnParseCommandsAsync = _ => Task.CompletedTask;
89+
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
90+
options.OnProcessedAsync = _ => Task.CompletedTask;
91+
options.OnPrepareResponseAsync = _ => Task.CompletedTask;
92+
});
9593
}
9694

9795
private void ConfigureCustomServicesAndDefaultOptions(IServiceCollection services)
@@ -103,30 +101,23 @@ private void ConfigureCustomServicesAndDefaultOptions(IServiceCollection service
103101

104102
private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services)
105103
{
106-
services.AddImageSharp(
107-
options =>
108-
{
109-
options.Configuration = Configuration.Default;
110-
options.BrowserMaxAge = TimeSpan.FromDays(7);
111-
options.CacheMaxAge = TimeSpan.FromDays(365);
112-
options.CacheHashLength = 8;
113-
options.OnParseCommandsAsync = _ => Task.CompletedTask;
114-
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
115-
options.OnProcessedAsync = _ => Task.CompletedTask;
116-
options.OnPrepareResponseAsync = _ => Task.CompletedTask;
117-
})
104+
services.AddImageSharp(options =>
105+
{
106+
options.Configuration = Configuration.Default;
107+
options.BrowserMaxAge = TimeSpan.FromDays(7);
108+
options.CacheMaxAge = TimeSpan.FromDays(365);
109+
options.CacheHashLength = 8;
110+
options.OnParseCommandsAsync = _ => Task.CompletedTask;
111+
options.OnBeforeSaveAsync = _ => Task.CompletedTask;
112+
options.OnProcessedAsync = _ => Task.CompletedTask;
113+
options.OnPrepareResponseAsync = _ => Task.CompletedTask;
114+
})
118115
.SetRequestParser<QueryCollectionRequestParser>()
119116
.Configure<PhysicalFileSystemCacheOptions>(options =>
120117
{
121118
options.CacheFolder = "different-cache";
122119
})
123-
.SetCache(provider =>
124-
{
125-
return new PhysicalFileSystemCache(
126-
provider.GetRequiredService<IOptions<PhysicalFileSystemCacheOptions>>(),
127-
provider.GetRequiredService<IWebHostEnvironment>(),
128-
provider.GetRequiredService<FormatUtilities>());
129-
})
120+
.SetCache<PhysicalFileSystemCache>()
130121
.SetCacheKey<UriRelativeLowerInvariantCacheKey>()
131122
.SetCacheHash<SHA256CacheHash>()
132123
.ClearProviders()

src/Directory.Build.targets

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,5 @@
3535
<PackageReference Update="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
3636
<PackageReference Update="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
3737
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
38-
<PackageReference Update="Microsoft.Extensions.FileProviders.Physical" Version="2.2.0"/>
3938
</ItemGroup>
4039
</Project>

src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Runtime.InteropServices;
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Hosting;
10-
using Microsoft.Extensions.FileProviders;
1110
using Microsoft.Extensions.Options;
1211
using SixLabors.ImageSharp.Web.Resolvers;
1312

@@ -28,11 +27,6 @@ public class PhysicalFileSystemCache : IImageCache
2827
/// </summary>
2928
private readonly int cacheFolderDepth;
3029

31-
/// <summary>
32-
/// The file provider abstraction.
33-
/// </summary>
34-
private readonly IFileProvider fileProvider;
35-
3630
/// <summary>
3731
/// Contains various format helper methods based on the current configuration.
3832
/// </summary>
@@ -53,19 +47,11 @@ public PhysicalFileSystemCache(
5347
#endif
5448
FormatUtilities formatUtilities)
5549
{
56-
Guard.NotNull(environment, nameof(environment));
5750
Guard.NotNull(options, nameof(options));
58-
Guard.NotNullOrWhiteSpace(environment.WebRootPath, nameof(environment.WebRootPath));
59-
60-
// Allow configuration of the cache without having to register everything
61-
PhysicalFileSystemCacheOptions cacheOptions = options != null ? options.Value : new();
62-
this.cacheRootPath = GetCacheRoot(cacheOptions, environment.WebRootPath, environment.ContentRootPath);
63-
this.cacheFolderDepth = (int)cacheOptions.CacheFolderDepth;
64-
65-
// Ensure cache directory is created before initializing the file provider
66-
Directory.CreateDirectory(this.cacheRootPath);
51+
Guard.NotNull(environment, nameof(environment));
6752

68-
this.fileProvider = new PhysicalFileProvider(this.cacheRootPath);
53+
this.cacheRootPath = GetCacheRoot(options.Value, environment.WebRootPath, environment.ContentRootPath);
54+
this.cacheFolderDepth = (int)options.Value.CacheFolderDepth;
6955
this.formatUtilities = formatUtilities;
7056
}
7157

@@ -78,21 +64,29 @@ public PhysicalFileSystemCache(
7864
/// <returns><see cref="string"/> representing the fully qualified cache root path.</returns>
7965
internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions, string webRootPath, string contentRootPath)
8066
{
81-
string cacheRoot = string.IsNullOrWhiteSpace(cacheOptions.CacheRootPath)
82-
? webRootPath
83-
: cacheOptions.CacheRootPath;
67+
string cacheRootPath = cacheOptions.CacheRootPath ?? webRootPath;
68+
if (string.IsNullOrEmpty(cacheRootPath))
69+
{
70+
throw new InvalidOperationException("The cache root path can't be determined, make sure it's explicitly configured or the webroot is set.");
71+
}
72+
73+
if (!Path.IsPathFullyQualified(cacheRootPath))
74+
{
75+
// Ensure this is an absolute path (resolved to the content root path)
76+
cacheRootPath = Path.GetFullPath(cacheRootPath, contentRootPath);
77+
}
8478

85-
return Path.IsPathFullyQualified(cacheRoot)
86-
? Path.Combine(cacheRoot, cacheOptions.CacheFolder)
87-
: Path.GetFullPath(Path.Combine(cacheRoot, cacheOptions.CacheFolder), contentRootPath);
79+
string cacheFolderPath = Path.Combine(cacheRootPath, cacheOptions.CacheFolder);
80+
81+
return PathUtils.EnsureTrailingSlash(cacheFolderPath);
8882
}
8983

9084
/// <inheritdoc/>
9185
public Task<IImageCacheResolver> GetAsync(string key)
9286
{
9387
string path = ToFilePath(key, this.cacheFolderDepth);
9488

95-
IFileInfo metaFileInfo = this.fileProvider.GetFileInfo(this.ToMetaDataFilePath(path));
89+
var metaFileInfo = new FileInfo(this.ToMetaDataFilePath(path));
9690
if (!metaFileInfo.Exists)
9791
{
9892
return Task.FromResult<IImageCacheResolver>(null);
@@ -110,7 +104,10 @@ public async Task SetAsync(string key, Stream stream, ImageCacheMetadata metadat
110104
string directory = Path.GetDirectoryName(path);
111105

112106
// Ensure cache directory is created before creating files
113-
Directory.CreateDirectory(directory);
107+
if (!Directory.Exists(directory))
108+
{
109+
Directory.CreateDirectory(directory);
110+
}
114111

115112
using (FileStream fileStream = File.Create(imagePath))
116113
{

src/ImageSharp.Web/Caching/PhysicalFileSystemCacheOptions.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@ namespace SixLabors.ImageSharp.Web.Caching
88
/// </summary>
99
public class PhysicalFileSystemCacheOptions
1010
{
11-
/// <summary>
12-
/// Gets or sets the cache folder name.
13-
/// </summary>
14-
public string CacheFolder { get; set; } = "is-cache";
15-
16-
/// <summary>
17-
/// Gets or sets the depth of the nested cache folders structure to store the images. Defaults to 8.
18-
/// </summary>
19-
public uint CacheFolderDepth { get; set; } = 8;
20-
2111
/// <summary>
2212
/// Gets or sets the optional cache root folder path.
2313
/// <para>
@@ -31,5 +21,15 @@ public class PhysicalFileSystemCacheOptions
3121
/// </para>
3222
/// </summary>
3323
public string CacheRootPath { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the cache folder name.
27+
/// </summary>
28+
public string CacheFolder { get; set; } = "is-cache";
29+
30+
/// <summary>
31+
/// Gets or sets the depth of the nested cache folders structure to store the images. Defaults to 8.
32+
/// </summary>
33+
public uint CacheFolderDepth { get; set; } = 8;
3434
}
3535
}

src/ImageSharp.Web/ImageSharp.Web.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" />
3333
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" />
3434
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
35-
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" />
3635
</ItemGroup>
3736

3837
<ItemGroup>

src/ImageSharp.Web/PathUtils.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.IO;
6+
7+
namespace SixLabors.ImageSharp.Web
8+
{
9+
internal static class PathUtils
10+
{
11+
/// <summary>
12+
/// Ensures the path ends with a trailing slash (directory separator).
13+
/// </summary>
14+
/// <param name="path">The path.</param>
15+
/// <returns>
16+
/// The path with a trailing slash.
17+
/// </returns>
18+
internal static string EnsureTrailingSlash(string path)
19+
{
20+
if (!string.IsNullOrEmpty(path) &&
21+
path[path.Length - 1] != Path.DirectorySeparatorChar)
22+
{
23+
return path + Path.DirectorySeparatorChar;
24+
}
25+
26+
return path;
27+
}
28+
29+
/// <summary>
30+
/// Determines whether the <paramref name="path" /> is located underneath the specified <paramref name="rootPath" />.
31+
/// </summary>
32+
/// <param name="path">The fully qualified path to test.</param>
33+
/// <param name="rootPath">The root path (needs to end with a directory separator).</param>
34+
/// <returns>
35+
/// <c>true</c> if the path is located underneath the specified root path; otherwise, <c>false</c>.
36+
/// </returns>
37+
internal static bool IsUnderneathRoot(string path, string rootPath)
38+
=> path.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase);
39+
}
40+
}

src/ImageSharp.Web/Providers/PhysicalFileSystemProvider.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using Microsoft.AspNetCore.Hosting;
88
using Microsoft.AspNetCore.Http;
99
using Microsoft.AspNetCore.Http.Extensions;
10-
using Microsoft.Extensions.FileProviders;
1110
using Microsoft.Extensions.Options;
1211
using SixLabors.ImageSharp.Web.Resolvers;
1312

@@ -19,9 +18,9 @@ namespace SixLabors.ImageSharp.Web.Providers
1918
public class PhysicalFileSystemProvider : IImageProvider
2019
{
2120
/// <summary>
22-
/// The file provider abstraction.
21+
/// The root path for the provider.
2322
/// </summary>
24-
private readonly IFileProvider fileProvider;
23+
private readonly string providerRootPath;
2524

2625
/// <summary>
2726
/// Contains various format helper methods based on the current configuration.
@@ -43,19 +42,10 @@ public PhysicalFileSystemProvider(
4342
#endif
4443
FormatUtilities formatUtilities)
4544
{
45+
Guard.NotNull(options, nameof(options));
4646
Guard.NotNull(environment, nameof(environment));
4747

48-
// ContentRootPath is never null.
49-
// https://github.com/dotnet/aspnetcore/blob/b89eba6c3cda331ee98063e3c4a04267ec540315/src/Hosting/Hosting/src/WebHostBuilder.cs#L262
50-
Guard.NotNullOrWhiteSpace(environment.WebRootPath, nameof(environment.WebRootPath));
51-
52-
// Allow configuration of the provider without having to register everything
53-
PhysicalFileSystemProviderOptions providerOptions = options != null ? options.Value : new();
54-
string providerRootPath = GetProviderRoot(providerOptions, environment.WebRootPath, environment.ContentRootPath);
55-
56-
// Ensure provider directory is created before initializing the file provider
57-
Directory.CreateDirectory(providerRootPath);
58-
this.fileProvider = new PhysicalFileProvider(providerRootPath);
48+
this.providerRootPath = GetProviderRoot(options.Value, environment.WebRootPath, environment.ContentRootPath);
5949
this.formatUtilities = formatUtilities;
6050
}
6151

@@ -72,17 +62,20 @@ public bool IsValidRequest(HttpContext context)
7262
/// <inheritdoc/>
7363
public Task<IImageResolver> GetAsync(HttpContext context)
7464
{
75-
// Path has already been correctly parsed before here.
76-
IFileInfo fileInfo = this.fileProvider.GetFileInfo(context.Request.Path.Value);
77-
78-
// Check to see if the file exists.
79-
if (!fileInfo.Exists)
65+
// Use Join because request path starts with a slash
66+
string fullPath = Path.GetFullPath(Path.Join(this.providerRootPath, context.Request.Path.Value));
67+
if (PathUtils.IsUnderneathRoot(fullPath, this.providerRootPath))
8068
{
81-
return Task.FromResult<IImageResolver>(null);
69+
// Check to see if the file exists
70+
var fileInfo = new FileInfo(fullPath);
71+
if (fileInfo.Exists)
72+
{
73+
var metadata = new ImageMetadata(fileInfo.LastWriteTimeUtc, fileInfo.Length);
74+
return Task.FromResult<IImageResolver>(new PhysicalFileSystemResolver(fileInfo, metadata));
75+
}
8276
}
8377

84-
var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length);
85-
return Task.FromResult<IImageResolver>(new PhysicalFileSystemResolver(fileInfo, metadata));
78+
return Task.FromResult<IImageResolver>(null);
8679
}
8780

8881
/// <summary>
@@ -94,13 +87,19 @@ public Task<IImageResolver> GetAsync(HttpContext context)
9487
/// <returns><see cref="string"/> representing the fully qualified provider root path.</returns>
9588
internal static string GetProviderRoot(PhysicalFileSystemProviderOptions providerOptions, string webRootPath, string contentRootPath)
9689
{
97-
string providerRoot = string.IsNullOrWhiteSpace(providerOptions.ProviderRootPath)
98-
? webRootPath
99-
: providerOptions.ProviderRootPath;
90+
string providerRootPath = providerOptions.ProviderRootPath ?? webRootPath;
91+
if (string.IsNullOrEmpty(providerRootPath))
92+
{
93+
throw new InvalidOperationException("The provider root path can't be determined, make sure it's explicitly configured or the webroot is set.");
94+
}
95+
96+
if (!Path.IsPathFullyQualified(providerRootPath))
97+
{
98+
// Ensure this is an absolute path (resolved to the content root path)
99+
providerRootPath = Path.GetFullPath(providerRootPath, contentRootPath);
100+
}
100101

101-
return Path.IsPathFullyQualified(providerRoot)
102-
? providerRoot
103-
: Path.GetFullPath(providerRoot, contentRootPath);
102+
return PathUtils.EnsureTrailingSlash(providerRootPath);
104103
}
105104
}
106105
}

0 commit comments

Comments
 (0)