diff --git a/Mvc.sln b/Mvc.sln
index 2e42a3e926..87891aba29 100644
--- a/Mvc.sln
+++ b/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
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index b11d3784fd..0c3ab8b8a6 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -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
{
- ///
+ ///
+ /// Caches the result of runtime compilation of Razor files for the duration of the app lifetime.
+ ///
public class CompilerCache : ICompilerCache
{
- private readonly ConcurrentDictionary _cache;
private readonly IFileProvider _fileProvider;
+ private readonly IMemoryCache _cache;
///
/// Initializes a new instance of populated with precompiled views
@@ -33,36 +35,39 @@ namespace Microsoft.AspNet.Mvc.Razor
}
// Internal for unit testing
- internal CompilerCache(IEnumerable viewCollections, IFileProvider fileProvider)
+ internal CompilerCache(IEnumerable razorFileInfoCollection,
+ IFileProvider fileProvider)
{
_fileProvider = fileProvider;
- _cache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
-
- foreach (var viewCollection in viewCollections)
+ _cache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
+ var cacheEntries = new List();
+ 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(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 compile,
out CompilationResult result)
{
- CompilerCacheEntry cacheEntry;
var normalizedPath = NormalizePath(relativeFileInfo.RelativePath);
- if (!_cache.TryGetValue(normalizedPath, out cacheEntry))
+ var cacheEntry = _cache.Get(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,
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs
index 598e23412a..5e51be5605 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs
@@ -82,5 +82,10 @@ namespace Microsoft.AspNet.Mvc.Razor
/// depends on.
///
public CompilerCacheEntry AssociatedViewStartEntry { get; set; }
+
+ ///
+ /// Gets or sets a flag that determines if the validity of this cache entry was performed at runtime.
+ ///
+ public bool IsValidatedPreCompiled { get; set; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
index e4451ef060..326e6b5ad8 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs
@@ -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);
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
index 307fb7751e..b07461b97d 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs
@@ -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();
+
+ 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), " ");
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorCompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorCompilerCacheTest.cs
new file mode 100644
index 0000000000..c7aa0c0159
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorCompilerCacheTest.cs
@@ -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 _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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
index 0d419bf356..b39ba2771f 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
@@ -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",
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
index 319be33af5..db6859de33 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
@@ -13,6 +13,8 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly Dictionary _lookup =
new Dictionary(StringComparer.Ordinal);
+ private readonly Dictionary _fileTriggers =
+ new Dictionary(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];
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs
new file mode 100644
index 0000000000..48ba37da23
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs
@@ -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