Merge pull request #6376 from aspnet/rel/2.0.0-preview2
Perform case insensitive lookups for precompiled views
This commit is contained in:
commit
8662422e18
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
private readonly object _initializeLock = new object();
|
private readonly object _initializeLock = new object();
|
||||||
private readonly object _cacheLock = new object();
|
private readonly object _cacheLock = new object();
|
||||||
|
private readonly Dictionary<string, Task<CompiledViewDescriptor>> _precompiledViewLookup;
|
||||||
private readonly ConcurrentDictionary<string, string> _normalizedPathLookup;
|
private readonly ConcurrentDictionary<string, string> _normalizedPathLookup;
|
||||||
private readonly IFileProvider _fileProvider;
|
private readonly IFileProvider _fileProvider;
|
||||||
private readonly RazorTemplateEngine _templateEngine;
|
private readonly RazorTemplateEngine _templateEngine;
|
||||||
|
|
@ -81,12 +82,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
_normalizedPathLookup = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
_normalizedPathLookup = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
||||||
_cache = new MemoryCache(new MemoryCacheOptions());
|
_cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
|
|
||||||
|
_precompiledViewLookup = new Dictionary<string, Task<CompiledViewDescriptor>>(
|
||||||
|
precompiledViews.Count,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var precompiledView in precompiledViews)
|
foreach (var precompiledView in precompiledViews)
|
||||||
{
|
{
|
||||||
_cache.Set(
|
if (_precompiledViewLookup.TryGetValue(precompiledView.RelativePath, out var otherValue))
|
||||||
precompiledView.RelativePath,
|
{
|
||||||
Task.FromResult(precompiledView),
|
var message = string.Join(
|
||||||
new MemoryCacheEntryOptions { Priority = CacheItemPriority.NeverRemove });
|
Environment.NewLine,
|
||||||
|
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
|
||||||
|
otherValue.Result.RelativePath,
|
||||||
|
precompiledView.RelativePath);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_precompiledViewLookup.Add(precompiledView.RelativePath, Task.FromResult(precompiledView));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,17 +110,40 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
throw new ArgumentNullException(nameof(relativePath));
|
throw new ArgumentNullException(nameof(relativePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lookup precompiled views first.
|
||||||
|
|
||||||
// Attempt to lookup the cache entry using the passed in path. This will succeed if the path is already
|
// Attempt to lookup the cache entry using the passed in path. This will succeed if the path is already
|
||||||
// normalized and a cache entry exists.
|
// normalized and a cache entry exists.
|
||||||
if (!_cache.TryGetValue(relativePath, out Task<CompiledViewDescriptor> cachedResult))
|
string normalizedPath = null;
|
||||||
|
Task<CompiledViewDescriptor> cachedResult;
|
||||||
|
|
||||||
|
if (_precompiledViewLookup.Count > 0)
|
||||||
{
|
{
|
||||||
var normalizedPath = GetNormalizedPath(relativePath);
|
if (_precompiledViewLookup.TryGetValue(relativePath, out cachedResult))
|
||||||
if (!_cache.TryGetValue(normalizedPath, out cachedResult))
|
|
||||||
{
|
{
|
||||||
cachedResult = CreateCacheEntry(normalizedPath);
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedPath = GetNormalizedPath(relativePath);
|
||||||
|
if (_precompiledViewLookup.TryGetValue(normalizedPath, out cachedResult))
|
||||||
|
{
|
||||||
|
return cachedResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(relativePath, out cachedResult))
|
||||||
|
{
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedPath = normalizedPath ?? GetNormalizedPath(relativePath);
|
||||||
|
if (_cache.TryGetValue(normalizedPath, out cachedResult))
|
||||||
|
{
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry does not exist. Attempt to create one.
|
||||||
|
cachedResult = CreateCacheEntry(normalizedPath);
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
internal static string FormatPropertyMustBeSet(object p0, object p1)
|
internal static string FormatPropertyMustBeSet(object p0, object p1)
|
||||||
=> string.Format(CultureInfo.CurrentCulture, GetString("PropertyMustBeSet"), p0, p1);
|
=> string.Format(CultureInfo.CurrentCulture, GetString("PropertyMustBeSet"), p0, p1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following precompiled view paths differ only in case, which is not supported:
|
||||||
|
/// </summary>
|
||||||
|
internal static string RazorViewCompiler_ViewPathsDifferOnlyInCase
|
||||||
|
{
|
||||||
|
get => GetString("RazorViewCompiler_ViewPathsDifferOnlyInCase");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following precompiled view paths differ only in case, which is not supported:
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRazorViewCompiler_ViewPathsDifferOnlyInCase()
|
||||||
|
=> GetString("RazorViewCompiler_ViewPathsDifferOnlyInCase");
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -191,4 +191,7 @@
|
||||||
<data name="PropertyMustBeSet" xml:space="preserve">
|
<data name="PropertyMustBeSet" xml:space="preserve">
|
||||||
<value>The property '{0}' of '{1}' must not be null.</value>
|
<value>The property '{0}' of '{1}' must not be null.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RazorViewCompiler_ViewPathsDifferOnlyInCase" xml:space="preserve">
|
||||||
|
<value>The following precompiled view paths differ only in case, which is not supported:</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -16,6 +16,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
public class RazorViewCompilerTest
|
public class RazorViewCompilerTest
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ThrowsIfMultiplePrecompiledViewsHavePathsDifferingOnlyInCase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var precompiledViews = new[]
|
||||||
|
{
|
||||||
|
new CompiledViewDescriptor { RelativePath = "/Views/Home/About.cshtml" },
|
||||||
|
new CompiledViewDescriptor { RelativePath = "/Views/home/About.cshtml" },
|
||||||
|
};
|
||||||
|
var message = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
"The following precompiled view paths differ only in case, which is not supported:",
|
||||||
|
precompiledViews[0].RelativePath,
|
||||||
|
precompiledViews[1].RelativePath);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => GetViewCompiler(fileProvider, precompiledViews: precompiledViews));
|
||||||
|
Assert.Equal(message, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem()
|
public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem()
|
||||||
{
|
{
|
||||||
|
|
@ -178,6 +199,49 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
Assert.Same(precompiledView, result);
|
Assert.Same(precompiledView, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/views/home/index.cshtml")]
|
||||||
|
[InlineData("/VIEWS/HOME/INDEX.CSHTML")]
|
||||||
|
[InlineData("/viEws/HoME/inDex.cshtml")]
|
||||||
|
public async Task CompileAsync_PerformsCaseInsensitiveLookupsForPrecompiledViews(string lookupPath)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
|
var precompiledView = new CompiledViewDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = path,
|
||||||
|
};
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await viewCompiler.CompileAsync(lookupPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(precompiledView, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompileAsync_PerformsCaseInsensitiveLookupsForPrecompiledViews_WithNonNormalizedPaths()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
|
var precompiledView = new CompiledViewDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = path,
|
||||||
|
};
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await viewCompiler.CompileAsync("Views\\Home\\Index.cshtml");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(precompiledView, result);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledView()
|
public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledView()
|
||||||
{
|
{
|
||||||
|
|
@ -200,7 +264,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
Assert.Same(precompiledView, result);
|
Assert.Same(precompiledView, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages()
|
public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages()
|
||||||
{
|
{
|
||||||
|
|
@ -465,7 +528,7 @@ this should fail";
|
||||||
fileProvider = fileProvider ?? new TestFileProvider();
|
fileProvider = fileProvider ?? new TestFileProvider();
|
||||||
compilationCallback = compilationCallback ?? (_ => { });
|
compilationCallback = compilationCallback ?? (_ => { });
|
||||||
var options = new TestOptionsManager<RazorViewEngineOptions>();
|
var options = new TestOptionsManager<RazorViewEngineOptions>();
|
||||||
if (referenceManager == null)
|
if (referenceManager == null)
|
||||||
{
|
{
|
||||||
var applicationPartManager = new ApplicationPartManager();
|
var applicationPartManager = new ApplicationPartManager();
|
||||||
var assembly = typeof(RazorViewCompilerTest).Assembly;
|
var assembly = typeof(RazorViewCompilerTest).Assembly;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue