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:
Pranav K 2015-01-26 18:03:28 -08:00
parent 19ca77ac2d
commit 59db8143da
26 changed files with 366 additions and 50 deletions

41
Mvc.sln
View File

@ -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

View File

@ -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,

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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), " ");

View 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);
}
}
}

View File

@ -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",

View File

@ -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];
}
}
}

View File

@ -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();
}
}
}

View File

@ -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));

View File

@ -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-*",

View File

@ -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");
}
}
}

View File

@ -1 +1 @@
index:@GetType().GetTypeInfo().Assembly.GetName()
index:@GetType().GetTypeInfo().Assembly.GetName()

View File

@ -1,2 +1,2 @@
Layout:@GetType().GetTypeInfo().Assembly.FullName
@RenderBody()
@RenderBody()

View File

@ -1,4 +1,3 @@
@using System.Reflection
@{ Layout = "/views/Home/Layout.cshtml";}
@{ Layout = "/Views/Home/Layout.cshtml";}
_viewstart:@GetType().GetTypeInfo().Assembly.FullName

View File

@ -0,0 +1 @@
Test

View File

@ -0,0 +1,2 @@
@using System.Reflection;
@GetType().GetTypeInfo().Assembly.FullName

View File

@ -0,0 +1 @@
_ViewStart content

View File

@ -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");
}
}
}

View File

@ -0,0 +1 @@
Hello from view!

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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"
}

View File

@ -0,0 +1 @@
A functional test website to verify that the Razor compiler cache gets lazily initialized.