From 6ef5518f8adc1dc1fbd253fb644e6cebc07ef6a3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 6 Feb 2015 05:31:11 -0800 Subject: [PATCH] Remove IRazorFileProviderCache and rely on file expiration triggers to file change expiry. Fixes #1969 --- .../Compilation/CompilerCache.cs | 155 ++++--- .../Compilation/CompilerCacheResult.cs | 40 ++ .../DefaultRazorFileProviderCache.cs | 87 ---- .../Compilation/ICompilerCache.cs | 6 +- .../Compilation/IRazorFileProviderCache.cs | 15 - .../RazorViewEngineOptions.cs | 32 +- .../VirtualPathRazorPageFactory.cs | 30 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 5 +- .../TestFileProvider.cs | 6 +- .../Compilation/CompilerCacheTest.cs | 275 +++++++++--- .../DefaultRazorFileProviderCacheTest.cs | 414 ------------------ .../RazorViewEngineOptionsTest.cs | 12 +- .../Services/CustomCompilerCache.cs | 5 +- 13 files changed, 376 insertions(+), 706 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheResult.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs delete mode 100644 test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs 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 ImmediateExpirationTimespans - { - get - { - yield return new object[] - { - TimeSpan.FromSeconds(0.0) - }; - - yield return new object[] - { - TimeSpan.FromSeconds(-1.0) - }; - - yield return new object[] - { - TimeSpan.MinValue - }; - } - } - - [Theory] - [MemberData(nameof(ImmediateExpirationTimespans))] - public void GettingFileInfoReturnsNewDataWithCustomImmediateExpiration(TimeSpan expiration) - { - // Arrange - var optionsAccessor = OptionsAccessor; - SetExpiration(optionsAccessor, expiration); - - string FileName = "myfile4.cshtml"; - var cache = GetCache(optionsAccessor); - - CreateFile(FileName); - - // Act - var fileInfo1 = cache.GetFileInfo(FileName); - 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 CustomExpirationTimespans - { - get - { - yield return new object[] - { - TimeSpan.FromSeconds(1.0) - }; - - yield return new object[] - { - TimeSpan.FromSeconds(3.0) - }; - } - } - - [Theory] - [MemberData(nameof(CustomExpirationTimespans))] - public void GettingFileInfoReturnsNewDataWithCustomExpiration(TimeSpan expiration) - { - // Arrange - var optionsAccessor = OptionsAccessor; - SetExpiration(optionsAccessor, expiration); - - string FileName = "myfile5.cshtml"; - 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(FileName, fileInfo1.Name); - } - - [Theory] - [MemberData(nameof(CustomExpirationTimespans))] - public void GettingFileInfoReturnsSameDataWithCustomExpiration(TimeSpan expiration) - { - // Arrange - var optionsAccessor = OptionsAccessor; - SetExpiration(optionsAccessor, expiration); - - string FileName = "myfile6.cshtml"; - var cache = GetCache(optionsAccessor); - - CreateFile(FileName); - - // Act - var fileInfo1 = cache.GetFileInfo(FileName); - - Sleep(optionsAccessor, cache, -500); - - var fileInfo2 = cache.GetFileInfo(FileName); - - // Assert - Assert.Same(fileInfo1, fileInfo2); - - Assert.Equal(FileName, fileInfo1.Name); - } - - [Fact] - public void GettingFileInfoReturnsSameDataWithMaxExpiration() - { - // Arrange - var optionsAccessor = OptionsAccessor; - SetExpiration(optionsAccessor, TimeSpan.MaxValue); - - string FileName = "myfile7.cshtml"; - var cache = GetCache(optionsAccessor); - - CreateFile(FileName); - - // Act - var fileInfo1 = cache.GetFileInfo(FileName); - - Sleep(cache, 2500); - - var fileInfo2 = cache.GetFileInfo(FileName); - - // Assert - Assert.Same(fileInfo1, fileInfo2); - - Assert.Equal(FileName, fileInfo1.Name); - } - - [Fact] - public void GetDirectoryInfo_PassesThroughToUnderlyingFileProvider() - { - // Arrange - var fileProvider = new Mock(); - var expected = Mock.Of(); - fileProvider.Setup(f => f.GetDirectoryContents("/test-path")) - .Returns(expected) - .Verifiable(); - var options = new RazorViewEngineOptions - { - FileProvider = fileProvider.Object - }; - var accessor = new Mock>(); - accessor.SetupGet(a => a.Options) - .Returns(options); - - var cachedFileProvider = new DefaultRazorFileProviderCache(accessor.Object); - - // Act - var result = cachedFileProvider.GetDirectoryContents("/test-path"); - - // Assert - Assert.Same(expected, result); - fileProvider.Verify(); - } - - public class ControllableExpiringFileInfoCache : DefaultRazorFileProviderCache - { - public ControllableExpiringFileInfoCache(IOptions optionsAccessor) - : base(optionsAccessor) - { - } - - private DateTime? _internalUtcNow { get; set; } - - protected override DateTime UtcNow - { - get - { - if (_internalUtcNow == null) - { - _internalUtcNow = base.UtcNow; - } - - return _internalUtcNow.Value.AddTicks(1); - } - } - - public void Sleep(int milliSeconds) - { - if (milliSeconds <= 0) - { - throw new InvalidOperationException(); - } - - _internalUtcNow = UtcNow.AddMilliseconds(milliSeconds); - } - } - public class DummyFileProvider : IFileProvider - { - private Dictionary _fileInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public void AddFile(IFileInfo fileInfo) - { - if (_fileInfos.ContainsKey(fileInfo.Name)) - { - _fileInfos[fileInfo.Name] = fileInfo; - } - else - { - _fileInfos.Add(fileInfo.Name, fileInfo); - } - } - - public IDirectoryContents GetDirectoryContents(string subpath) - { - throw new NotImplementedException(); - } - - public IFileInfo GetFileInfo(string subpath) - { - IFileInfo knownInfo; - if (_fileInfos.TryGetValue(subpath, out knownInfo)) - { - return new TestFileInfo - { - Name = knownInfo.Name, - LastModified = knownInfo.LastModified, - }; - } - else - { - return new NotFoundFileInfo(subpath); - } - } - - public IExpirationTrigger Watch(string filter) - { - throw new NotImplementedException(); - } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs index c692924a59..6781b6d4f0 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.Razor public class RazorViewEngineOptionsTest { [Fact] - public void FileProviderThrows_IfNullIsAsseigned() + public void FileProviderThrows_IfNullIsAssigned() { // Arrange var options = new RazorViewEngineOptions(); @@ -27,18 +27,18 @@ namespace Microsoft.AspNet.Mvc.Razor { // Arrange var services = new ServiceCollection().AddOptions(); - var timeSpan = new TimeSpan(400); + var fileProvider = new TestFileProvider(); // Act - services.ConfigureRazorViewEngineOptions(options => { - options.ExpirationBeforeCheckingFilesOnDisk = timeSpan; + services.ConfigureRazorViewEngineOptions(options => + { + options.FileProvider = fileProvider; }); var serviceProvider = services.BuildServiceProvider(); // Assert var accessor = serviceProvider.GetRequiredService>(); - var expiration = Assert.IsType(accessor.Options.ExpirationBeforeCheckingFilesOnDisk); - Assert.Equal(timeSpan, expiration); + Assert.Same(fileProvider, accessor.Options.FileProvider); } } } \ No newline at end of file diff --git a/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs b/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs index 21f9df3873..af0df88b97 100644 --- a/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs +++ b/test/WebSites/RazorCompilerCacheWebSite/Services/CustomCompilerCache.cs @@ -3,15 +3,16 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Razor; +using Microsoft.Framework.OptionsModel; namespace RazorCompilerCacheWebSite { public class CustomCompilerCache : CompilerCache { public CustomCompilerCache(IAssemblyProvider assemblyProvider, - IRazorFileProviderCache fileProvider, + IOptions optionsAccessor, CompilerCacheInitialiedService cacheInitializedService) - : base(assemblyProvider, fileProvider) + : base(assemblyProvider, optionsAccessor) { cacheInitializedService.Initialized = true; }