-
-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolving issue with FileVersionProvider #403
- Loading branch information
1 parent
092e460
commit 638cb17
Showing
2 changed files
with
125 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
using Microsoft.AspNetCore.Antiforgery.Internal; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.WebUtilities; | ||
using Microsoft.Extensions.Caching.Memory; | ||
using Microsoft.Extensions.FileProviders; | ||
|
||
namespace BundlerMinifier.TagHelpers | ||
{ | ||
// On ASP.NET Core 2.1, FileVersionProvider can be resolved from DI | ||
// However this class disappeared in .NET Core 2.2 because they introduced IFileVersionProvider | ||
// The problem is that FileVersionProvider became internal. So using Meziantou.AspNetCore.BundleTagHelpers | ||
// in .NET Core 2.2 will crash at runtime if we keep using FileVersionProvider from 2.1. | ||
// We can't change the code to use IFileVersionProvider instead as it will not be backward compatible with 2.1. | ||
internal class FileVersionProvider | ||
{ | ||
private const string VersionKey = "v"; | ||
private static readonly char[] QueryStringAndFragmentTokens = new[] { '?', '#' }; | ||
private readonly IFileProvider _fileProvider; | ||
private readonly IMemoryCache _cache; | ||
private readonly PathString _requestPathBase; | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="Microsoft.AspNetCore.Mvc.TagHelpers.Internal.FileVersionProvider"/>. | ||
/// </summary> | ||
/// <param name="fileProvider">The file provider to get and watch files.</param> | ||
/// <param name="cache"><see cref="IMemoryCache"/> where versioned urls of files are cached.</param> | ||
/// <param name="requestPathBase">The base path for the current HTTP request.</param> | ||
public FileVersionProvider( | ||
IFileProvider fileProvider, | ||
IMemoryCache cache, | ||
PathString requestPathBase) | ||
{ | ||
if (fileProvider == null) | ||
{ | ||
throw new ArgumentNullException(nameof(fileProvider)); | ||
} | ||
|
||
if (cache == null) | ||
{ | ||
throw new ArgumentNullException(nameof(cache)); | ||
} | ||
|
||
_fileProvider = fileProvider; | ||
_cache = cache; | ||
_requestPathBase = requestPathBase; | ||
} | ||
|
||
/// <summary> | ||
/// Adds version query parameter to the specified file path. | ||
/// </summary> | ||
/// <param name="path">The path of the file to which version should be added.</param> | ||
/// <returns>Path containing the version query string.</returns> | ||
/// <remarks> | ||
/// The version query string is appended with the key "v". | ||
/// </remarks> | ||
public string AddFileVersionToPath(string path) | ||
{ | ||
if (path == null) | ||
{ | ||
throw new ArgumentNullException(nameof(path)); | ||
} | ||
|
||
var resolvedPath = path; | ||
|
||
var queryStringOrFragmentStartIndex = path.IndexOfAny(QueryStringAndFragmentTokens); | ||
if (queryStringOrFragmentStartIndex != -1) | ||
{ | ||
resolvedPath = path.Substring(0, queryStringOrFragmentStartIndex); | ||
} | ||
|
||
Uri uri; | ||
if (Uri.TryCreate(resolvedPath, UriKind.Absolute, out uri) && !uri.IsFile) | ||
{ | ||
// Don't append version if the path is absolute. | ||
return path; | ||
} | ||
|
||
string value; | ||
if (!_cache.TryGetValue(path, out value)) | ||
{ | ||
var cacheEntryOptions = new MemoryCacheEntryOptions(); | ||
cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(resolvedPath)); | ||
var fileInfo = _fileProvider.GetFileInfo(resolvedPath); | ||
|
||
if (!fileInfo.Exists && | ||
_requestPathBase.HasValue && | ||
resolvedPath.StartsWith(_requestPathBase.Value, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
var requestPathBaseRelativePath = resolvedPath.Substring(_requestPathBase.Value.Length); | ||
cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(requestPathBaseRelativePath)); | ||
fileInfo = _fileProvider.GetFileInfo(requestPathBaseRelativePath); | ||
} | ||
|
||
if (fileInfo.Exists) | ||
{ | ||
value = QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo)); | ||
} | ||
else | ||
{ | ||
// if the file is not in the current server. | ||
value = path; | ||
} | ||
|
||
value = _cache.Set(path, value, cacheEntryOptions); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
private static string GetHashForFile(IFileInfo fileInfo) | ||
{ | ||
using (var sha256 = CryptographyAlgorithms.CreateSHA256()) | ||
{ | ||
using (var readStream = fileInfo.CreateReadStream()) | ||
{ | ||
var hash = sha256.ComputeHash(readStream); | ||
return WebEncoders.Base64UrlEncode(hash); | ||
} | ||
} | ||
} | ||
} | ||
} |