Skip to content

Commit

Permalink
Resolving issue with FileVersionProvider #403
Browse files Browse the repository at this point in the history
  • Loading branch information
rockstardev committed Mar 23, 2019
1 parent 092e460 commit 638cb17
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/BundlerMinifier.TagHelpers/BundleTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
Expand Down
125 changes: 125 additions & 0 deletions src/BundlerMinifier.TagHelpers/FileVersionProvider.cs
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);
}
}
}
}
}

0 comments on commit 638cb17

Please sign in to comment.