diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
index 2c9a8d89ac..bc60f8b2e7 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Cache.Memory;
+using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Razor
{
@@ -26,11 +27,10 @@ namespace Microsoft.AspNet.Mvc.Razor
/// An representing the assemblies
/// used to search for pre-compiled views.
///
- /// An instance that represents the application's
- /// file system.
- ///
- public CompilerCache(IAssemblyProvider provider, IRazorFileProviderCache fileProvider)
- : this(GetFileInfos(provider.CandidateAssemblies), fileProvider)
+ /// An accessor to the .
+ public CompilerCache(IAssemblyProvider provider,
+ IOptions optionsAccessor)
+ : this(GetFileInfos(provider.CandidateAssemblies), optionsAccessor.Options.FileProvider)
{
}
@@ -74,47 +74,34 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- internal static IEnumerable
- GetFileInfos(IEnumerable assemblies)
+ ///
+ public CompilerCacheResult GetOrAdd([NotNull] string relativePath,
+ [NotNull] Func compile)
{
- return assemblies.SelectMany(a => a.ExportedTypes)
- .Where(Match)
- .Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c));
- }
-
- private static bool Match(Type t)
- {
- var inAssemblyType = typeof(RazorFileInfoCollection);
- if (inAssemblyType.IsAssignableFrom(t))
+ var result = GetOrAddCore(relativePath, compile);
+ if (result == null)
{
- var hasParameterlessConstructor = t.GetConstructor(Type.EmptyTypes) != null;
-
- return hasParameterlessConstructor
- && !t.GetTypeInfo().IsAbstract
- && !t.GetTypeInfo().ContainsGenericParameters;
+ return CompilerCacheResult.FileNotFound;
}
- return false;
+ return new CompilerCacheResult(result.CompilationResult);
}
- ///
- public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
- [NotNull] Func compile)
+ private GetOrAddResult GetOrAddCore(string relativePath,
+ Func compile)
{
- CompilationResult result;
- var entry = GetOrAdd(fileInfo, compile, out result);
- return result;
- }
-
- private CompilerCacheEntry GetOrAdd(RelativeFileInfo relativeFileInfo,
- Func compile,
- out CompilationResult result)
- {
- var normalizedPath = NormalizePath(relativeFileInfo.RelativePath);
+ var normalizedPath = NormalizePath(relativePath);
var cacheEntry = _cache.Get(normalizedPath);
if (cacheEntry == null)
{
- return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
+ var fileInfo = _fileProvider.GetFileInfo(relativePath);
+ if (!fileInfo.Exists)
+ {
+ return null;
+ }
+
+ var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
+ return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
}
else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled)
{
@@ -123,26 +110,35 @@ namespace Microsoft.AspNet.Mvc.Razor
// 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;
+ var fileInfo = _fileProvider.GetFileInfo(relativePath);
+ if (!fileInfo.Exists)
+ {
+ return null;
+ }
+
+ var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
if (cacheEntry.Length != fileInfo.Length)
{
// Recompile if the file lengths differ
- return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
+ return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
}
if (AssociatedViewStartsChanged(cacheEntry, compile))
{
// Recompile if the view starts have changed since the entry was created.
- return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
+ return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
}
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;
+ return new GetOrAddResult
+ {
+ CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
+ CompilerCacheEntry = cacheEntry
+ };
}
// Timestamp doesn't match but it might be because of deployment, compare the hash.
@@ -156,30 +152,39 @@ namespace Microsoft.AspNet.Mvc.Razor
// if the entry is being concurrently read or updated.
cacheEntry.LastModified = fileInfo.LastModified;
cacheEntry.IsValidatedPreCompiled = true;
- result = CompilationResult.Successful(cacheEntry.CompiledType);
-
- return cacheEntry;
+ return new GetOrAddResult
+ {
+ CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
+ CompilerCacheEntry = cacheEntry
+ };
}
// it's not a match, recompile
- return OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result);
+ return OnCacheMiss(relativeFileInfo, normalizedPath, compile);
}
- result = CompilationResult.Successful(cacheEntry.CompiledType);
- return cacheEntry;
+ return new GetOrAddResult
+ {
+ CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
+ CompilerCacheEntry = cacheEntry
+ };
}
- private CompilerCacheEntry OnCacheMiss(RelativeFileInfo file,
- string normalizedPath,
- Func compile,
- out CompilationResult result)
+ private GetOrAddResult OnCacheMiss(RelativeFileInfo file,
+ string normalizedPath,
+ Func compile)
{
- result = compile(file);
-
- var cacheEntry = new CompilerCacheEntry(file, result.CompiledType);
+ var compilationResult = compile(file);
// Concurrent addition to MemoryCache with the same key result in safe race.
- return _cache.Set(normalizedPath, cacheEntry, PopulateCacheSetContext);
+ var cacheEntry = _cache.Set(normalizedPath,
+ new CompilerCacheEntry(file, compilationResult.CompiledType),
+ PopulateCacheSetContext);
+ return new GetOrAddResult
+ {
+ CompilationResult = compilationResult,
+ CompilerCacheEntry = cacheEntry
+ };
}
private CompilerCacheEntry PopulateCacheSetContext(ICacheSetContext cacheSetContext)
@@ -212,12 +217,11 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewStartLocations = ViewStartUtility.GetViewStartLocations(relativePath);
foreach (var viewStartLocation in viewStartLocations)
{
- var viewStartFileInfo = _fileProvider.GetFileInfo(viewStartLocation);
- if (viewStartFileInfo.Exists)
+ var getOrAddResult = GetOrAddCore(viewStartLocation, compile);
+ if (getOrAddResult != null)
{
- var relativeFileInfo = new RelativeFileInfo(viewStartFileInfo, viewStartLocation);
- CompilationResult result;
- return GetOrAdd(relativeFileInfo, compile, out result);
+ // This is the nearest _ViewStart that exists on disk.
+ return getOrAddResult.CompilerCacheEntry;
}
}
@@ -235,5 +239,36 @@ namespace Microsoft.AspNet.Mvc.Razor
return path;
}
+
+ internal static IEnumerable
+ GetFileInfos(IEnumerable assemblies)
+ {
+ return assemblies.SelectMany(a => a.ExportedTypes)
+ .Where(Match)
+ .Select(c => (RazorFileInfoCollection)Activator.CreateInstance(c));
+ }
+
+ private static bool Match(Type t)
+ {
+ var inAssemblyType = typeof(RazorFileInfoCollection);
+ if (inAssemblyType.IsAssignableFrom(t))
+ {
+ var hasParameterlessConstructor = t.GetConstructor(Type.EmptyTypes) != null;
+
+ return hasParameterlessConstructor
+ && !t.GetTypeInfo().IsAbstract
+ && !t.GetTypeInfo().ContainsGenericParameters;
+ }
+
+ return false;
+ }
+
+
+ private class GetOrAddResult
+ {
+ public CompilerCacheEntry CompilerCacheEntry { get; set; }
+
+ public CompilationResult CompilationResult { get; set; }
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs
new file mode 100644
index 0000000000..f9d8132649
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs
@@ -0,0 +1,40 @@
+// 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 Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Result of .
+ ///
+ public class CompilerCacheResult
+ {
+ ///
+ /// Result of when the specified file does not exist in the
+ /// file system.
+ ///
+ public static CompilerCacheResult FileNotFound { get; } = new CompilerCacheResult();
+
+ ///
+ /// Initializes a new instance of with the specified
+ /// .
+ ///
+ /// The
+ public CompilerCacheResult([NotNull] CompilationResult compilationResult)
+ {
+ CompilationResult = compilationResult;
+ }
+
+ ///
+ /// Initializes a new instance of for a failed file lookup.
+ ///
+ protected CompilerCacheResult()
+ {
+ }
+
+ ///
+ /// The .
+ ///
+ /// This property is null when file lookup failed.
+ public CompilationResult CompilationResult { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs
deleted file mode 100644
index 3801f4fec7..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.Collections.Concurrent;
-using Microsoft.AspNet.FileProviders;
-using Microsoft.Framework.Expiration.Interfaces;
-using Microsoft.Framework.OptionsModel;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// Default implementation for the interface that caches
- /// the results of .
- ///
- public class DefaultRazorFileProviderCache : IRazorFileProviderCache
- {
- private readonly ConcurrentDictionary _fileInfoCache =
- new ConcurrentDictionary(StringComparer.Ordinal);
-
- private readonly IFileProvider _fileProvider;
- private readonly TimeSpan _offset;
-
- ///
- /// Initializes a new instance of .
- ///
- /// Accessor to .
- public DefaultRazorFileProviderCache(IOptions optionsAccessor)
- {
- _fileProvider = optionsAccessor.Options.FileProvider;
- _offset = optionsAccessor.Options.ExpirationBeforeCheckingFilesOnDisk;
- }
-
- protected virtual DateTime UtcNow
- {
- get
- {
- return DateTime.UtcNow;
- }
- }
-
- ///
- public IDirectoryContents GetDirectoryContents(string subpath)
- {
- return _fileProvider.GetDirectoryContents(subpath);
- }
-
- ///
- public IFileInfo GetFileInfo(string subpath)
- {
- ExpiringFileInfo expiringFileInfo;
- var utcNow = UtcNow;
-
- if (_fileInfoCache.TryGetValue(subpath, out expiringFileInfo) &&
- expiringFileInfo.ValidUntil > utcNow)
- {
- return expiringFileInfo.FileInfo;
- }
- else
- {
- var fileInfo = _fileProvider.GetFileInfo(subpath);
-
- expiringFileInfo = new ExpiringFileInfo()
- {
- FileInfo = fileInfo,
- ValidUntil = _offset == TimeSpan.MaxValue ? DateTime.MaxValue : utcNow.Add(_offset),
- };
-
- _fileInfoCache[subpath] = expiringFileInfo;
-
- return fileInfo;
- }
- }
-
- ///
- public IExpirationTrigger Watch(string filter)
- {
- return _fileProvider.Watch(filter);
- }
-
- private class ExpiringFileInfo
- {
- public IFileInfo FileInfo { get; set; }
- public DateTime ValidUntil { get; set; }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilerCache.cs
index e7a08a82ec..f4c99db228 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilerCache.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilerCache.cs
@@ -14,10 +14,10 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Get an existing compilation result, or create and add a new one if it is
/// not available in the cache or is expired.
///
- /// A representing the file.
+ /// Application relative path to the file.
/// An delegate that will generate a compilation result.
/// A cached .
- CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
- [NotNull] Func compile);
+ CompilerCacheResult GetOrAdd([NotNull] string relativePath,
+ [NotNull] Func compile);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs
deleted file mode 100644
index 718f7cf567..0000000000
--- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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.FileProviders;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- ///
- /// An that caches the results of for a
- /// duration specified by .
- ///
- public interface IRazorFileProviderCache : IFileProvider
- {
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs b/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs
index f49f45ecb4..e906a8b483 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs
@@ -9,42 +9,12 @@ using Microsoft.AspNet.Mvc.Razor.OptionDescriptors;
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Provides programmatic configuration for the default .
+ /// Provides programmatic configuration for the default .
///
public class RazorViewEngineOptions
{
- private TimeSpan _expirationBeforeCheckingFilesOnDisk = TimeSpan.FromSeconds(2);
private IFileProvider _fileProvider;
- ///
- /// Gets or sets the that specifies the duration for which results of
- /// are cached by .
- /// is used to query for file changes during Razor compilation.
- ///
- ///
- /// of or less, means no caching.
- /// of means indefinite caching.
- ///
- public TimeSpan ExpirationBeforeCheckingFilesOnDisk
- {
- get
- {
- return _expirationBeforeCheckingFilesOnDisk;
- }
-
- set
- {
- if (value.TotalMilliseconds < 0)
- {
- _expirationBeforeCheckingFilesOnDisk = TimeSpan.Zero;
- }
- else
- {
- _expirationBeforeCheckingFilesOnDisk = value;
- }
- }
- }
-
///
/// Get a of descriptors for s used by this
/// application.
diff --git a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
index a2ba401b34..4793209885 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
@@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using Microsoft.AspNet.FileProviders;
-using Microsoft.AspNet.Http;
-using Microsoft.AspNet.PageExecutionInstrumentation;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Razor
@@ -17,19 +14,16 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly ITypeActivator _activator;
private readonly IServiceProvider _serviceProvider;
- private readonly IRazorFileProviderCache _fileProviderCache;
private readonly ICompilerCache _compilerCache;
private IRazorCompilationService _razorcompilationService;
public VirtualPathRazorPageFactory(ITypeActivator typeActivator,
IServiceProvider serviceProvider,
- ICompilerCache compilerCache,
- IRazorFileProviderCache fileProviderCache)
+ ICompilerCache compilerCache)
{
_activator = typeActivator;
_serviceProvider = serviceProvider;
_compilerCache = compilerCache;
- _fileProviderCache = fileProviderCache;
}
private IRazorCompilationService RazorCompilationService
@@ -57,23 +51,19 @@ namespace Microsoft.AspNet.Mvc.Razor
relativePath = relativePath.Substring(1);
}
- var fileInfo = _fileProviderCache.GetFileInfo(relativePath);
+ var result = _compilerCache.GetOrAdd(
+ relativePath,
+ RazorCompilationService.Compile);
- if (fileInfo.Exists)
+ if (result == CompilerCacheResult.FileNotFound)
{
- var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
-
- var result = _compilerCache.GetOrAdd(
- relativeFileInfo,
- RazorCompilationService.Compile);
-
- var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
- page.Path = relativePath;
-
- return page;
+ return null;
}
- return null;
+ var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompilationResult.CompiledType);
+ page.Path = relativePath;
+
+ return page;
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index 425d946b86..d929e1db63 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -107,13 +107,12 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient();
// Caches view locations that are valid for the lifetime of the application.
yield return describe.Singleton();
- yield return describe.Singleton();
// The host is designed to be discarded after consumption and is very inexpensive to initialize.
yield return describe.Transient(serviceProvider =>
{
- var cachedFileProvider = serviceProvider.GetRequiredService();
- return new MvcRazorHost(cachedFileProvider);
+ var cachedFileProvider = serviceProvider.GetRequiredService>();
+ return new MvcRazorHost(cachedFileProvider.Options.FileProvider);
});
// Caches compilation artifacts across the lifetime of the application.
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
index db6859de33..fc75460371 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs
@@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private readonly Dictionary _fileTriggers =
new Dictionary(StringComparer.Ordinal);
- public IDirectoryContents GetDirectoryContents(string subpath)
+ public virtual IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotSupportedException();
}
@@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor
_lookup.Remove(path);
}
- public IFileInfo GetFileInfo(string subpath)
+ public virtual IFileInfo GetFileInfo(string subpath)
{
if (_lookup.ContainsKey(subpath))
{
@@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- public IExpirationTrigger Watch(string filter)
+ public virtual IExpirationTrigger Watch(string filter)
{
TestFileTrigger trigger;
if (!_fileTriggers.TryGetValue(filter, out trigger) || trigger.IsExpired)
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
index 9ff9e6aefb..95140e525a 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs
@@ -7,37 +7,137 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using Microsoft.AspNet.FileProviders;
+using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class CompilerCacheTest
{
+ private const string ViewPath = "view-path";
+
+ [Fact]
+ public void GetOrAdd_ReturnsFileNotFoundResult_IfFileIsNotFoundInFileSystem()
+ {
+ // Arrange
+ var fileProvider = new TestFileProvider();
+ var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var type = GetType();
+
+ // Act
+ var result = cache.GetOrAdd("/some/path", _ => { throw new Exception("Shouldn't be called"); });
+
+ // Assert
+ Assert.Same(CompilerCacheResult.FileNotFound, result);
+ Assert.Null(result.CompilationResult);
+ }
+
[Fact]
public void GetOrAdd_ReturnsCompilationResultFromFactory()
{
// Arrange
var fileProvider = new TestFileProvider();
+ fileProvider.AddFile(ViewPath, "some content");
var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
- var fileInfo = new TestFileInfo
- {
- LastModified = DateTime.FromFileTimeUtc(10000)
- };
-
var type = GetType();
var expected = UncachedCompilationResult.Successful(type, "hello world");
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");
-
// Act
- var actual = cache.GetOrAdd(runtimeFileInfo, _ => expected);
+ var result = cache.GetOrAdd(ViewPath, _ => expected);
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ var actual = result.CompilationResult;
+ Assert.NotNull(actual);
Assert.Same(expected, actual);
Assert.Equal("hello world", actual.CompiledContent);
Assert.Same(type, actual.CompiledType);
}
+ [Fact]
+ public void GetOrAdd_ReturnsFileNotFoundIfFileWasDeleted()
+ {
+ // Arrange
+ var fileProvider = new TestFileProvider();
+ fileProvider.AddFile(ViewPath, "some content");
+ var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var type = typeof(RuntimeCompileIdentical);
+ var expected = UncachedCompilationResult.Successful(type, "hello world");
+
+ // Act 1
+ var result1 = cache.GetOrAdd(ViewPath, _ => expected);
+
+ // Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ Assert.Same(expected, result1.CompilationResult);
+
+ // Act 2
+ // Delete the file from the file system and set it's expiration trigger.
+ fileProvider.DeleteFile(ViewPath);
+ fileProvider.GetTrigger(ViewPath).IsExpired = true;
+ var result2 = cache.GetOrAdd(ViewPath, _ => { throw new Exception("shouldn't be called."); });
+
+ // Assert 2
+ Assert.Same(CompilerCacheResult.FileNotFound, result2);
+ Assert.Null(result2.CompilationResult);
+ }
+
+ [Fact]
+ public void GetOrAdd_ReturnsNewResultIfFileWasModified()
+ {
+ // Arrange
+ var fileProvider = new TestFileProvider();
+ fileProvider.AddFile(ViewPath, "some content");
+ var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var type = typeof(RuntimeCompileIdentical);
+ var expected1 = UncachedCompilationResult.Successful(type, "hello world");
+ var expected2 = UncachedCompilationResult.Successful(type, "different content");
+
+ // Act 1
+ var result1 = cache.GetOrAdd(ViewPath, _ => expected1);
+
+ // Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ Assert.Same(expected1, result1.CompilationResult);
+
+ // Act 2
+ fileProvider.GetTrigger(ViewPath).IsExpired = true;
+ var result2 = cache.GetOrAdd(ViewPath, _ => expected2);
+
+ // Assert 2
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.Same(expected2, result2.CompilationResult);
+ }
+
+ [Fact]
+ public void GetOrAdd_DoesNotQueryFileSystem_IfCachedFileTriggerWasNotSet()
+ {
+ // Arrange
+ var mockFileProvider = new Mock { CallBase = true };
+ var fileProvider = mockFileProvider.Object;
+ fileProvider.AddFile(ViewPath, "some content");
+ var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
+ var type = typeof(RuntimeCompileIdentical);
+ var expected = UncachedCompilationResult.Successful(type, "hello world");
+
+ // Act 1
+ var result1 = cache.GetOrAdd(ViewPath, _ => expected);
+
+ // Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ Assert.Same(expected, result1.CompilationResult);
+
+ // Act 2
+ var result2 = cache.GetOrAdd(ViewPath, _ => { throw new Exception("shouldn't be called"); });
+
+ // Assert 2
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ Assert.IsType(result2.CompilationResult);
+ Assert.Same(type, result2.CompilationResult.CompiledType);
+ mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
+ }
+
private abstract class View
{
public abstract string Content { get; }
@@ -87,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.Razor
HashAlgorithmVersion = 1,
LastModified = DateTime.FromFileTimeUtc(10000),
Length = length,
- RelativePath = "ab",
+ RelativePath = ViewPath,
});
}
@@ -122,25 +222,17 @@ namespace Microsoft.AspNet.Mvc.Razor
LastModified = DateTime.FromFileTimeUtc(fileTimeUTC),
Content = instance.Content
};
-
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");
-
+ fileProvider.AddFile(ViewPath, fileInfo);
var precompiledContent = new PreCompile().Content;
- var razorFileInfo = new RazorFileInfo
- {
- FullTypeName = typeof(PreCompile).FullName,
- Hash = Crc32.Calculate(GetMemoryStream(precompiledContent)).ToString(CultureInfo.InvariantCulture),
- HashAlgorithmVersion = 1,
- LastModified = DateTime.FromFileTimeUtc(10000),
- Length = Encoding.UTF8.GetByteCount(precompiledContent),
- RelativePath = "ab",
- };
// Act
- var actual = cache.GetOrAdd(runtimeFileInfo,
+ var result = cache.GetOrAdd(ViewPath,
compile: _ => { throw new Exception("Shouldn't be called."); });
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ var actual = result.CompilationResult;
+ Assert.NotNull(actual);
Assert.Equal(typeof(PreCompile), actual.CompiledType);
}
@@ -165,25 +257,16 @@ namespace Microsoft.AspNet.Mvc.Razor
LastModified = DateTime.FromFileTimeUtc(fileTimeUTC),
Content = instance.Content
};
-
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");
-
- var precompiledContent = new PreCompile().Content;
- var razorFileInfo = new RazorFileInfo
- {
- FullTypeName = typeof(PreCompile).FullName,
- Hash = Crc32.Calculate(GetMemoryStream(precompiledContent)).ToString(CultureInfo.InvariantCulture),
- HashAlgorithmVersion = 1,
- LastModified = DateTime.FromFileTimeUtc(10000),
- Length = Encoding.UTF8.GetByteCount(precompiledContent),
- RelativePath = "ab",
- };
+ fileProvider.AddFile(ViewPath, fileInfo);
// Act
- var actual = cache.GetOrAdd(runtimeFileInfo,
+ var result = cache.GetOrAdd(ViewPath,
compile: _ => CompilationResult.Successful(resultViewType));
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ var actual = result.CompilationResult;
+ Assert.NotNull(actual);
Assert.Equal(resultViewType, actual.CompiledType);
}
@@ -203,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.Razor
LastModified = lastModified,
Content = instance.Content
};
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");
+ fileProvider.AddFile(ViewPath, fileInfo);
var viewStartContent = "viewstart-content";
var viewStartFileInfo = new TestFileInfo
@@ -227,13 +310,73 @@ namespace Microsoft.AspNet.Mvc.Razor
var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
// Act
- var actual = cache.GetOrAdd(runtimeFileInfo,
+ var result = cache.GetOrAdd(ViewPath,
compile: _ => { throw new Exception("shouldn't be invoked"); });
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ var actual = result.CompilationResult;
+ Assert.NotNull(actual);
Assert.Equal(typeof(PreCompile), actual.CompiledType);
}
+ [Fact]
+ public void GetOrAdd_ReturnsFileNotFoundResult_IfPrecompiledViewWasRemovedFromFileSystem()
+ {
+ // Arrange
+ var precompiledViews = new ViewCollection();
+ var fileProvider = new TestFileProvider();
+ var precompiledView = precompiledViews.FileInfos[0];
+ var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
+
+ // Act
+ var result = cache.GetOrAdd(ViewPath,
+ compile: _ => { throw new Exception("shouldn't be invoked"); });
+
+ // Assert
+ Assert.Same(CompilerCacheResult.FileNotFound, result);
+ Assert.Null(result.CompilationResult);
+ }
+
+ [Fact]
+ public void GetOrAdd_DoesNotReadFileFromFileSystemAfterPrecompiledViewIsVerified()
+ {
+ // Arrange
+ var precompiledViews = new ViewCollection();
+ var mockFileProvider = new Mock { CallBase = true };
+ var fileProvider = mockFileProvider.Object;
+ var precompiledView = precompiledViews.FileInfos[0];
+ var fileInfo = new TestFileInfo
+ {
+ Length = precompiledView.Length,
+ LastModified = precompiledView.LastModified,
+ };
+ fileProvider.AddFile(ViewPath, fileInfo);
+ var cache = new CompilerCache(new[] { precompiledViews }, fileProvider);
+
+ // Act 1
+ var result1 = cache.GetOrAdd(ViewPath,
+ compile: _ => { throw new Exception("shouldn't be invoked"); });
+
+ // Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ var actual1 = result1.CompilationResult;
+ Assert.NotNull(actual1);
+ Assert.Equal(typeof(PreCompile), actual1.CompiledType);
+ mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
+
+ // Act 2
+ var result2 = cache.GetOrAdd(ViewPath,
+ compile: _ => { throw new Exception("shouldn't be invoked"); });
+
+ // Assert 2
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ var actual2 = result2.CompilationResult;
+ Assert.NotNull(actual2);
+ Assert.Equal(typeof(PreCompile), actual2.CompiledType);
+ mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once());
+ }
+
[Fact]
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasAdedSinceTheCacheWasCreated()
{
@@ -255,19 +398,25 @@ namespace Microsoft.AspNet.Mvc.Razor
var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath);
// Act 1
- var actual1 = cache.GetOrAdd(relativeFile,
+ var result1 = cache.GetOrAdd(testFile.PhysicalPath,
compile: _ => { throw new Exception("should not be called"); });
// Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ var actual1 = result1.CompilationResult;
+ Assert.NotNull(actual1);
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
// Act 2
var viewStartTrigger = fileProvider.GetTrigger("Views\\_ViewStart.cshtml");
viewStartTrigger.IsExpired = true;
- var actual2 = cache.GetOrAdd(relativeFile,
+ var result2 = cache.GetOrAdd(testFile.PhysicalPath,
compile: _ => CompilationResult.Successful(expectedType));
// Assert 2
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ var actual2 = result2.CompilationResult;
+ Assert.NotNull(actual2);
Assert.Equal(expectedType, actual2.CompiledType);
}
@@ -309,22 +458,27 @@ namespace Microsoft.AspNet.Mvc.Razor
viewCollection.Add(viewStart);
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
- var fileInfo = new RelativeFileInfo(viewFileInfo, viewFileInfo.PhysicalPath);
// Act 1
- var actual1 = cache.GetOrAdd(fileInfo,
+ var result1 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
compile: _ => { throw new Exception("should not be called"); });
// Assert 1
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ var actual1 = result1.CompilationResult;
+ Assert.NotNull(actual1);
Assert.Equal(typeof(PreCompile), actual1.CompiledType);
// Act 2
var trigger = fileProvider.GetTrigger(viewStartFileInfo.PhysicalPath);
trigger.IsExpired = true;
- var actual2 = cache.GetOrAdd(fileInfo,
+ var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
compile: _ => CompilationResult.Successful(expectedType));
// Assert 2
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+ var actual2 = result2.CompilationResult;
+ Assert.NotNull(actual2);
Assert.Equal(expectedType, actual2.CompiledType);
}
@@ -385,28 +539,20 @@ namespace Microsoft.AspNet.Mvc.Razor
PhysicalPath = "Views\\home\\index.cshtml"
};
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, fileInfo.PhysicalPath);
-
- var razorFileInfo = new RazorFileInfo
- {
- FullTypeName = typeof(PreCompile).FullName,
- Hash = RazorFileHash.GetHash(fileInfo, hashAlgorithmVersion: 1),
- HashAlgorithmVersion = 1,
- LastModified = lastModified,
- Length = Encoding.UTF8.GetByteCount(content),
- RelativePath = fileInfo.PhysicalPath,
- };
-
var fileProvider = new TestFileProvider();
+ fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo);
var viewCollection = new ViewCollection();
var cache = new CompilerCache(new[] { viewCollection }, fileProvider);
// Act
- var actual = cache.GetOrAdd(runtimeFileInfo,
+ var result = cache.GetOrAdd(fileInfo.PhysicalPath,
compile: _ => CompilationResult.Successful(expectedType));
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result);
+ var actual = result.CompilationResult;
+ Assert.NotNull(actual);
Assert.Equal(expectedType, actual.CompiledType);
}
@@ -415,23 +561,28 @@ namespace Microsoft.AspNet.Mvc.Razor
{
// Arrange
var lastModified = DateTime.UtcNow;
- var cache = new CompilerCache(Enumerable.Empty(), new TestFileProvider());
+ var fileProvider = new TestFileProvider();
+ var cache = new CompilerCache(Enumerable.Empty(), fileProvider);
var fileInfo = new TestFileInfo
{
PhysicalPath = "test",
LastModified = lastModified
};
+ fileProvider.AddFile("test", fileInfo);
var type = GetType();
var uncachedResult = UncachedCompilationResult.Successful(type, "hello world");
- var runtimeFileInfo = new RelativeFileInfo(fileInfo, "test");
-
// Act
- cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult);
- var actual1 = cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult);
- var actual2 = cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult);
+ cache.GetOrAdd("test", _ => uncachedResult);
+ var result1 = cache.GetOrAdd("test", _ => uncachedResult);
+ var result2 = cache.GetOrAdd("test", _ => uncachedResult);
// Assert
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
+ Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
+
+ var actual1 = result1.CompilationResult;
+ var actual2 = result2.CompilationResult;
Assert.NotSame(uncachedResult, actual1);
Assert.NotSame(uncachedResult, actual2);
var result = Assert.IsType(actual1);
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs
deleted file mode 100644
index 668fc76a50..0000000000
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs
+++ /dev/null
@@ -1,414 +0,0 @@
-// 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.Collections.Generic;
-using Microsoft.AspNet.FileProviders;
-using Microsoft.Framework.OptionsModel;
-using Microsoft.Framework.Expiration.Interfaces;
-using Moq;
-using Xunit;
-
-namespace Microsoft.AspNet.Mvc.Razor
-{
- public class DefaultRazorFileProviderCacheTest
- {
- private const string FileName = "myView.cshtml";
-
- public DummyFileProvider TestFileProvider { get; } = new DummyFileProvider();
-
- public IOptions OptionsAccessor
- {
- get
- {
- var options = new RazorViewEngineOptions
- {
- FileProvider = TestFileProvider
- };
-
- var mock = new Mock>(MockBehavior.Strict);
- mock.Setup(oa => oa.Options).Returns(options);
-
- return mock.Object;
- }
- }
-
- public ControllableExpiringFileInfoCache GetCache(IOptions optionsAccessor)
- {
- return new ControllableExpiringFileInfoCache(optionsAccessor);
- }
-
- public void CreateFile(string fileName)
- {
- var fileInfo = new TestFileInfo()
- {
- Name = fileName,
- LastModified = DateTime.Now,
- };
-
- TestFileProvider.AddFile(fileInfo);
- }
-
- public void Sleep(ControllableExpiringFileInfoCache cache, int offsetMilliseconds)
- {
- cache.Sleep(offsetMilliseconds);
- }
-
- public void Sleep(IOptions accessor, ControllableExpiringFileInfoCache cache, int offsetMilliSeconds)
- {
- var baseMilliSeconds = (int)accessor.Options.ExpirationBeforeCheckingFilesOnDisk.TotalMilliseconds;
-
- cache.Sleep(baseMilliSeconds + offsetMilliSeconds);
- }
-
- public void SetExpiration(IOptions accessor, TimeSpan expiration)
- {
- accessor.Options.ExpirationBeforeCheckingFilesOnDisk = expiration;
- }
-
- [Fact]
- public void VerifyDefaultOptionsAreSetupCorrectly()
- {
- var optionsAccessor = OptionsAccessor;
-
- // Assert
- Assert.Equal(2000, optionsAccessor.Options.ExpirationBeforeCheckingFilesOnDisk.TotalMilliseconds);
- }
-
- [Fact]
- public void GettingFileInfoReturnsTheSameDataWithDefaultOptions()
- {
- // Arrange
- var cache = GetCache(OptionsAccessor);
-
- CreateFile(FileName);
-
- // Act
- var fileInfo1 = cache.GetFileInfo(FileName);
- var fileInfo2 = cache.GetFileInfo(FileName);
-
- // Assert
- Assert.True(fileInfo1.Exists);
- Assert.True(fileInfo1.Exists);
-
- Assert.Same(fileInfo1, fileInfo2);
-
- Assert.Equal(FileName, fileInfo1.Name);
- }
-
- [Fact]
- public void GettingFileInfoReturnsTheSameDataWithDefaultOptionsEvenWhenFilesHaveChanged()
- {
- // Arrange
- var cache = GetCache(OptionsAccessor);
-
- CreateFile(FileName);
-
- // Act
- var fileInfo1 = cache.GetFileInfo(FileName);
-
- CreateFile(FileName);
-
- var fileInfo2 = cache.GetFileInfo(FileName);
-
- // Assert
- Assert.Same(fileInfo1, fileInfo2);
-
- Assert.Equal(fileInfo1.LastModified, fileInfo2.LastModified);
- Assert.Equal(FileName, fileInfo1.Name);
- Assert.Equal(FileName, fileInfo2.Name);
- }
-
- [Fact]
- public void GettingFileInfoReturnsNewDataWithDefaultOptionsAfterExpirationAndFileChange()
- {
- var optionsAccessor = OptionsAccessor;
-
- // Arrange
- var cache = GetCache(optionsAccessor);
-
- CreateFile(FileName);
-
- // Act
- var fileInfo1 = cache.GetFileInfo(FileName);
-
- Sleep(optionsAccessor, cache, 500);
- CreateFile(FileName);
-
- var fileInfo2 = cache.GetFileInfo(FileName);
-
- // Assert
- Assert.NotSame(fileInfo1, fileInfo2);
-
- Assert.Equal(FileName, fileInfo1.Name);
- Assert.Equal(FileName, fileInfo2.Name);
- }
-
- [Fact]
- public void GettingFileInfoReturnsNewDataWithDefaultOptionsAfterExpiration()
- {
- // Arrange
- var optionsAccessor = OptionsAccessor;
-
- var cache = GetCache(optionsAccessor);
-
- CreateFile(FileName);
-
- // Act
- var fileInfo1 = cache.GetFileInfo(FileName);
-
- Sleep(optionsAccessor, cache, 500);
-
- var fileInfo2 = cache.GetFileInfo(FileName);
-
- // Assert
- Assert.NotSame(fileInfo1, fileInfo2);
-
- Assert.Equal(fileInfo1.LastModified, fileInfo2.LastModified);
- Assert.Equal(FileName, fileInfo1.Name);
- Assert.Equal(FileName, fileInfo2.Name);
- }
-
- public static IEnumerable