Updating CompilerCache to use IMemoryCache and file system watcher to
expire files in razor file cache. Add a functional test to ensure the compiler cache does not get initialized until the first request to a View. Fixes #1708
This commit is contained in:
parent
19ca77ac2d
commit
59db8143da
41
Mvc.sln
41
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22512.0
|
||||
VisualStudioVersion = 14.0.22604.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||
EndProject
|
||||
|
|
@ -132,6 +132,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Xml.Te
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatFilterWebSite", "test\WebSites\FormatFilterWebSite\FormatFilterWebSite.kproj", "{AC9BE567-540E-4C70-90C2-AAF021307A80}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorCompilerCacheWebSite", "test\WebSites\RazorCompilerCacheWebSite\RazorCompilerCacheWebSite.kproj", "{42C5D417-4060-48F4-BB28-E9E179007779}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -694,18 +696,6 @@ Global
|
|||
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -730,6 +720,18 @@ Global
|
|||
{C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -754,6 +756,18 @@ Global
|
|||
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{AC9BE567-540E-4C70-90C2-AAF021307A80}.Release|x86.Build.0 = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -818,5 +832,6 @@ Global
|
|||
{87AB84B2-22C1-43C6-BB8A-1D327B446FB0} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{22019146-BDFA-442E-8C8E-345FB9644578} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||
{AC9BE567-540E-4C70-90C2-AAF021307A80} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
{42C5D417-4060-48F4-BB28-E9E179007779} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.Framework.Cache.Memory;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Caches the result of runtime compilation of Razor files for the duration of the app lifetime.
|
||||
/// </summary>
|
||||
public class CompilerCache : ICompilerCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CompilerCacheEntry> _cache;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
|
||||
|
|
@ -33,36 +35,39 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal CompilerCache(IEnumerable<RazorFileInfoCollection> viewCollections, IFileProvider fileProvider)
|
||||
internal CompilerCache(IEnumerable<RazorFileInfoCollection> razorFileInfoCollection,
|
||||
IFileProvider fileProvider)
|
||||
{
|
||||
_fileProvider = fileProvider;
|
||||
_cache = new ConcurrentDictionary<string, CompilerCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var viewCollection in viewCollections)
|
||||
_cache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
|
||||
var cacheEntries = new List<CompilerCacheEntry>();
|
||||
foreach (var viewCollection in razorFileInfoCollection)
|
||||
{
|
||||
var containingAssembly = viewCollection.GetType().GetTypeInfo().Assembly;
|
||||
foreach (var fileInfo in viewCollection.FileInfos)
|
||||
{
|
||||
var containingAssembly = viewCollection.GetType().GetTypeInfo().Assembly;
|
||||
var viewType = containingAssembly.GetType(fileInfo.FullTypeName);
|
||||
var cacheEntry = new CompilerCacheEntry(fileInfo, viewType);
|
||||
|
||||
// There shouldn't be any duplicates and if there are any the first will win.
|
||||
// If the result doesn't match the one on disk its going to recompile anyways.
|
||||
_cache.TryAdd(NormalizePath(fileInfo.RelativePath), cacheEntry);
|
||||
_cache.Set(NormalizePath(fileInfo.RelativePath), cacheEntry, PopulateCacheSetContext);
|
||||
|
||||
cacheEntries.Add(cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up ViewStarts
|
||||
foreach (var entry in _cache)
|
||||
foreach (var entry in cacheEntries)
|
||||
{
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(entry.Key);
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(entry.RelativePath);
|
||||
foreach (var location in viewStartLocations)
|
||||
{
|
||||
CompilerCacheEntry viewStartEntry;
|
||||
if (_cache.TryGetValue(location, out viewStartEntry))
|
||||
var viewStartEntry = _cache.Get<CompilerCacheEntry>(location);
|
||||
if (viewStartEntry != null)
|
||||
{
|
||||
// Add the the composite _ViewStart entry as a dependency.
|
||||
entry.Value.AssociatedViewStartEntry = viewStartEntry;
|
||||
entry.AssociatedViewStartEntry = viewStartEntry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -105,14 +110,19 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Func<RelativeFileInfo, CompilationResult> compile,
|
||||
out CompilationResult result)
|
||||
{
|
||||
CompilerCacheEntry cacheEntry;
|
||||
var normalizedPath = NormalizePath(relativeFileInfo.RelativePath);
|
||||
if (!_cache.TryGetValue(normalizedPath, out cacheEntry))
|
||||
var cacheEntry = _cache.Get<CompilerCacheEntry>(normalizedPath);
|
||||
if (cacheEntry == null)
|
||||
{
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
|
||||
}
|
||||
else
|
||||
else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled)
|
||||
{
|
||||
// For precompiled views, the first time the entry is read, we need to ensure that no changes were made
|
||||
// either to the file associated with this entry, or any _ViewStart associated with it between the time
|
||||
// the View was precompiled and the time EnsureInitialized was called. For later iterations, we can
|
||||
// rely on expiration triggers ensuring the validity of the entry.
|
||||
|
||||
var fileInfo = relativeFileInfo.FileInfo;
|
||||
if (cacheEntry.Length != fileInfo.Length)
|
||||
{
|
||||
|
|
@ -129,6 +139,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
if (cacheEntry.LastModified == fileInfo.LastModified)
|
||||
{
|
||||
result = CompilationResult.Successful(cacheEntry.CompiledType);
|
||||
// Assigning to IsValidatedPreCompiled is an atomic operation and will result in a safe race
|
||||
// if it is being concurrently updated and read.
|
||||
cacheEntry.IsValidatedPreCompiled = true;
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
|
|
@ -139,9 +152,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
StringComparison.Ordinal))
|
||||
{
|
||||
// Cache hit, but we need to update the entry.
|
||||
// Assigning to LastModified is an atomic operation and will result in a safe race if it is
|
||||
// being concurrently read and written or updated concurrently.
|
||||
// Assigning to LastModified and IsValidatedPreCompiled are atomic operations and will result in safe races
|
||||
// if the entry is being concurrently read or updated.
|
||||
cacheEntry.LastModified = fileInfo.LastModified;
|
||||
cacheEntry.IsValidatedPreCompiled = true;
|
||||
result = CompilationResult.Successful(cacheEntry.CompiledType);
|
||||
|
||||
return cacheEntry;
|
||||
|
|
@ -150,6 +164,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
// it's not a match, recompile
|
||||
return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
|
||||
}
|
||||
|
||||
result = CompilationResult.Successful(cacheEntry.CompiledType);
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
private CompilerCacheEntry OnCacheMiss(RelativeFileInfo file,
|
||||
|
|
@ -159,15 +176,24 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
result = compile(file);
|
||||
|
||||
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType)
|
||||
{
|
||||
AssociatedViewStartEntry = GetCompositeViewStartEntry(normalizedPath, compile)
|
||||
};
|
||||
var cacheEntry = new CompilerCacheEntry(file, result.CompiledType);
|
||||
|
||||
// The cache is a concurrent dictionary, so concurrent addition to it with the same key would result in a
|
||||
// safe race.
|
||||
_cache[normalizedPath] = cacheEntry;
|
||||
return cacheEntry;
|
||||
// Concurrent addition to MemoryCache with the same key result in safe race.
|
||||
return _cache.Set(normalizedPath, cacheEntry, PopulateCacheSetContext);
|
||||
}
|
||||
|
||||
private CompilerCacheEntry PopulateCacheSetContext(ICacheSetContext cacheSetContext)
|
||||
{
|
||||
var entry = (CompilerCacheEntry)cacheSetContext.State;
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(entry.RelativePath));
|
||||
|
||||
var viewStartLocations = ViewStartUtility.GetViewStartLocations(cacheSetContext.Key);
|
||||
foreach (var location in viewStartLocations)
|
||||
{
|
||||
cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(location));
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private bool AssociatedViewStartsChanged(CompilerCacheEntry entry,
|
||||
|
|
|
|||
|
|
@ -82,5 +82,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// depends on.
|
||||
/// </summary>
|
||||
public CompilerCacheEntry AssociatedViewStartEntry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.
|
||||
/// </summary>
|
||||
public bool IsValidatedPreCompiled { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,13 +167,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
if (fullTypeName != null)
|
||||
{
|
||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo, RazorFileHash.HashAlgorithmVersion1);
|
||||
var hashAlgorithmVersion = RazorFileHash.HashAlgorithmVersion1;
|
||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo, hashAlgorithmVersion);
|
||||
var razorFileInfo = new RazorFileInfo
|
||||
{
|
||||
RelativePath = fileInfo.RelativePath,
|
||||
LastModified = fileInfo.FileInfo.LastModified,
|
||||
Length = fileInfo.FileInfo.Length,
|
||||
FullTypeName = fullTypeName
|
||||
FullTypeName = fullTypeName,
|
||||
Hash = hash,
|
||||
HashAlgorithmVersion = hashAlgorithmVersion
|
||||
};
|
||||
|
||||
return new PrecompilationCacheEntry(razorFileInfo, syntaxTree);
|
||||
|
|
|
|||
|
|
@ -167,6 +167,41 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(expected, responseContent.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeletingPrecompiledViewStart_PriorToFirstRequestToAView_CausesViewToBeRecompiled()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString();
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var applicationEnvironment = _services.GetRequiredService<IApplicationEnvironment>();
|
||||
|
||||
var viewsDirectory = Path.Combine(applicationEnvironment.ApplicationBasePath, "Views", "ViewStartDelete");
|
||||
var viewStartPath = Path.Combine(viewsDirectory, "_ViewStart.cshtml");
|
||||
var viewStartContent = File.ReadAllText(viewStartPath);
|
||||
|
||||
// Act - 1
|
||||
// Query the Test view so we know the compiler cache gets populated.
|
||||
var response = await client.GetStringAsync("/Test");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal("Test", response.Trim());
|
||||
|
||||
try
|
||||
{
|
||||
// Act - 2
|
||||
var response2 = await client.GetStringAsync("http://localhost/Home/ViewStartDeletedPriorToFirstRequest");
|
||||
|
||||
// Assert - 2
|
||||
Assert.NotEqual(expected, response2.Trim());
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.WriteAllText(viewStartPath, viewStartContent);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task TouchFile(string viewsDir, string file)
|
||||
{
|
||||
File.AppendAllText(Path.Combine(viewsDir, file), " ");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using RazorCompilerCacheWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class RazorCompilerCacheTest
|
||||
{
|
||||
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(RazorCompilerCacheWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||
|
||||
[Fact]
|
||||
public async Task CompilerCache_IsNotInitializedUntilFirstViewRequest()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
client.BaseAddress = new Uri("http://localhost");
|
||||
|
||||
// Act - 1
|
||||
// Visit a sampling of controller actions that do not produce ViewResult
|
||||
var result1 = await client.GetAsync("/file");
|
||||
var result2 = await client.GetAsync("/statuscode");
|
||||
var result3 = await client.GetStringAsync("/cache-status");
|
||||
|
||||
// Assert - 1
|
||||
Assert.Equal(HttpStatusCode.OK, result1.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.OK, result2.StatusCode);
|
||||
// Ensure the cache was not initialized.
|
||||
Assert.Equal(bool.FalseString, result3);
|
||||
|
||||
// Act - 2
|
||||
var result4 = await client.GetStringAsync("/view");
|
||||
var result5 = await client.GetStringAsync("/cache-status");
|
||||
|
||||
// Assert - 2
|
||||
Assert.Equal("Hello from view!", result4);
|
||||
Assert.Equal(bool.TrueString, result5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
"PrecompilationWebSite": "1.0.0",
|
||||
"RoutingWebSite": "1.0.0",
|
||||
"RazorWebSite": "1.0.0",
|
||||
"RazorCompilerCacheWebSite": "1.0.0",
|
||||
"RazorInstrumentationWebsite": "1.0.0",
|
||||
"RazorViewEngineOptionsWebsite": "1.0.0",
|
||||
"RequestServicesWebSite": "1.0.0",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
private readonly Dictionary<string, IFileInfo> _lookup =
|
||||
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, TestFileTrigger> _fileTriggers =
|
||||
new Dictionary<string, TestFileTrigger>(StringComparer.Ordinal);
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
|
|
@ -56,7 +58,19 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
public IExpirationTrigger Watch(string filter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
TestFileTrigger trigger;
|
||||
if (!_fileTriggers.TryGetValue(filter, out trigger) || trigger.IsExpired)
|
||||
{
|
||||
trigger = new TestFileTrigger();
|
||||
_fileTriggers[filter] = trigger;
|
||||
}
|
||||
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public TestFileTrigger GetTrigger(string filter)
|
||||
{
|
||||
return _fileTriggers[filter];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Framework.Expiration.Interfaces;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public class TestFileTrigger : IExpirationTrigger
|
||||
{
|
||||
public bool ActiveExpirationCallbacks { get; } = false;
|
||||
|
||||
public bool IsExpired { get; set; }
|
||||
|
||||
public IDisposable RegisterExpirationCallback(Action<object> callback, object state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,7 +262,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
fileProvider.AddFile("Views\\_ViewStart.cshtml", "");
|
||||
var viewStartTrigger = fileProvider.GetTrigger("Views\\_ViewStart.cshtml");
|
||||
viewStartTrigger.IsExpired = true;
|
||||
var actual2 = cache.GetOrAdd(relativeFile,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
|
|
@ -318,7 +319,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
|
||||
|
||||
// Act 2
|
||||
fileProvider.DeleteFile(viewStartFileInfo.PhysicalPath);
|
||||
var trigger = fileProvider.GetTrigger(viewStartFileInfo.PhysicalPath);
|
||||
trigger.IsExpired = true;
|
||||
var actual2 = cache.GetOrAdd(fileInfo,
|
||||
compile: _ => CompilationResult.Successful(expectedType));
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
"code": [
|
||||
"**/*.cs",
|
||||
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs",
|
||||
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs"
|
||||
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs",
|
||||
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs"
|
||||
],
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
|
||||
|
|
|
|||
|
|
@ -16,5 +16,16 @@ namespace PrecompilationWebSite.Controllers
|
|||
{
|
||||
return View("~/Views/ViewsConsumingCompilationOptions/Index");
|
||||
}
|
||||
|
||||
public IActionResult ViewStartDeletedPriorToFirstRequest()
|
||||
{
|
||||
return View("~/Views/ViewStartDelete/Index");
|
||||
}
|
||||
|
||||
[HttpGet("/Test")]
|
||||
public IActionResult TestView()
|
||||
{
|
||||
return View("~/Views/Test/Index");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
index:@GetType().GetTypeInfo().Assembly.GetName()
|
||||
index:@GetType().GetTypeInfo().Assembly.GetName()
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
Layout:@GetType().GetTypeInfo().Assembly.FullName
|
||||
@RenderBody()
|
||||
@RenderBody()
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
@using System.Reflection
|
||||
@{ Layout = "/views/Home/Layout.cshtml";}
|
||||
@{ Layout = "/Views/Home/Layout.cshtml";}
|
||||
_viewstart:@GetType().GetTypeInfo().Assembly.FullName
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
Test
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@using System.Reflection;
|
||||
@GetType().GetTypeInfo().Assembly.FullName
|
||||
|
|
@ -0,0 +1 @@
|
|||
_ViewStart content
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace RazorCompilerCacheWebSite
|
||||
{
|
||||
public class CompilerCacheController : Controller
|
||||
{
|
||||
[HttpGet("/cache-status")]
|
||||
[Produces("text/plain")]
|
||||
public ContentResult GetCompilerCacheInitializationStatus(
|
||||
[FromServices] CompilerCacheInitialiedService service)
|
||||
{
|
||||
return Content(service.Initialized.ToString());
|
||||
}
|
||||
|
||||
[HttpGet("/statuscode")]
|
||||
public IActionResult StatusCodeAction()
|
||||
{
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
[HttpGet("/file")]
|
||||
public IActionResult FileAction()
|
||||
{
|
||||
return File("readme.md", "application/text");
|
||||
}
|
||||
|
||||
[HttpGet("/view")]
|
||||
public ViewResult Index()
|
||||
{
|
||||
return View("~/Index");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Hello from view!
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>42c5d417-4060-48f4-bb28-e9e179007779</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>42473</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace RazorCompilerCacheWebSite
|
||||
{
|
||||
public class CompilerCacheInitialiedService
|
||||
{
|
||||
public bool Initialized { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
|
||||
namespace RazorCompilerCacheWebSite
|
||||
{
|
||||
public class CustomCompilerCache : CompilerCache
|
||||
{
|
||||
public CustomCompilerCache(IAssemblyProvider assemblyProvider,
|
||||
IRazorFileProviderCache fileProvider,
|
||||
CompilerCacheInitialiedService cacheInitializedService)
|
||||
: base(assemblyProvider, fileProvider)
|
||||
{
|
||||
cacheInitializedService.Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Mvc.Razor;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace RazorCompilerCacheWebSite
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var configuration = app.GetTestConfiguration();
|
||||
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc(configuration);
|
||||
services.AddSingleton<ICompilerCache, CustomCompilerCache>();
|
||||
services.AddSingleton<CompilerCacheInitialiedService>();
|
||||
});
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"commands": {
|
||||
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
|
||||
},
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
A functional test website to verify that the Razor compiler cache gets lazily initialized.
|
||||
Loading…
Reference in New Issue