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 _cacheLock = new object();
|
||||
private readonly Dictionary<string, Task<CompiledViewDescriptor>> _precompiledViewLookup;
|
||||
private readonly ConcurrentDictionary<string, string> _normalizedPathLookup;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly RazorTemplateEngine _templateEngine;
|
||||
|
|
@ -81,12 +82,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
_normalizedPathLookup = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
|
||||
_cache = new MemoryCache(new MemoryCacheOptions());
|
||||
|
||||
_precompiledViewLookup = new Dictionary<string, Task<CompiledViewDescriptor>>(
|
||||
precompiledViews.Count,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var precompiledView in precompiledViews)
|
||||
{
|
||||
_cache.Set(
|
||||
precompiledView.RelativePath,
|
||||
Task.FromResult(precompiledView),
|
||||
new MemoryCacheEntryOptions { Priority = CacheItemPriority.NeverRemove });
|
||||
if (_precompiledViewLookup.TryGetValue(precompiledView.RelativePath, out var otherValue))
|
||||
{
|
||||
var message = string.Join(
|
||||
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));
|
||||
}
|
||||
|
||||
// Lookup precompiled views first.
|
||||
|
||||
// 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.
|
||||
if (!_cache.TryGetValue(relativePath, out Task<CompiledViewDescriptor> cachedResult))
|
||||
string normalizedPath = null;
|
||||
Task<CompiledViewDescriptor> cachedResult;
|
||||
|
||||
if (_precompiledViewLookup.Count > 0)
|
||||
{
|
||||
var normalizedPath = GetNormalizedPath(relativePath);
|
||||
if (!_cache.TryGetValue(normalizedPath, out cachedResult))
|
||||
if (_precompiledViewLookup.TryGetValue(relativePath, 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -350,6 +350,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
internal static string FormatPropertyMustBeSet(object p0, object 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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -191,4 +191,7 @@
|
|||
<data name="PropertyMustBeSet" xml:space="preserve">
|
||||
<value>The property '{0}' of '{1}' must not be null.</value>
|
||||
</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>
|
||||
|
|
@ -16,6 +16,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
{
|
||||
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]
|
||||
public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem()
|
||||
{
|
||||
|
|
@ -178,6 +199,49 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
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]
|
||||
public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledView()
|
||||
{
|
||||
|
|
@ -200,7 +264,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
|||
Assert.Same(precompiledView, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages()
|
||||
{
|
||||
|
|
@ -465,7 +528,7 @@ this should fail";
|
|||
fileProvider = fileProvider ?? new TestFileProvider();
|
||||
compilationCallback = compilationCallback ?? (_ => { });
|
||||
var options = new TestOptionsManager<RazorViewEngineOptions>();
|
||||
if (referenceManager == null)
|
||||
if (referenceManager == null)
|
||||
{
|
||||
var applicationPartManager = new ApplicationPartManager();
|
||||
var assembly = typeof(RazorViewCompilerTest).Assembly;
|
||||
|
|
|
|||
Loading…
Reference in New Issue