Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Merge pull request #6376 from aspnet/rel/2.0.0-preview2
Browse files Browse the repository at this point in the history
Perform case insensitive lookups for precompiled views
  • Loading branch information
pranavkm authored Jun 7, 2017
2 parents 32e21e2 + 1124eb5 commit 8662422
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 10 deletions.
51 changes: 43 additions & 8 deletions src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class RazorViewCompiler : IViewCompiler
{
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;
Expand Down Expand Up @@ -81,12 +82,23 @@ public RazorViewCompiler(
_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));
}
}

Expand All @@ -98,17 +110,40 @@ public Task<CompiledViewDescriptor> CompileAsync(string 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
// 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;
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -178,6 +199,49 @@ public async Task CompileAsync_ReturnsPrecompiledViews()
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()
{
Expand All @@ -200,7 +264,6 @@ public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompile
Assert.Same(precompiledView, result);
}


[Fact]
public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages()
{
Expand Down Expand Up @@ -465,7 +528,7 @@ private static TestRazorViewCompiler GetViewCompiler(
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;
Expand Down

0 comments on commit 8662422

Please sign in to comment.