From 2a321fd622cee473bf43c85d451b64da2fbf278d Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 15 May 2015 14:32:20 -0700 Subject: [PATCH] React to Caching api review changes --- .../Components/FeaturedMoviesComponent.cs | 36 +++-- .../Views/Movies/Index.cshtml | 2 +- .../Directives/DefaultCodeTreeCache.cs | 31 +++-- .../Compilation/CompilerCache.cs | 30 +++-- .../Precompilation/RazorPreCompiler.cs | 34 ++--- .../CacheTagHelper.cs | 33 +++-- .../Internal/FileVersionProvider.cs | 15 ++- .../Internal/GlobbingUrlBuilder.cs | 14 +- .../RazorPreCompileModule.cs | 4 +- .../Directives/DefaultCodeTreeCacheTest.cs | 3 +- .../CacheTagHelperTest.cs | 126 +++++++----------- .../ImageTagHelperTest.cs | 25 +--- .../Internal/FileVersionProviderTest.cs | 38 ++---- .../Internal/GlobbingUrlBuilderTest.cs | 30 ++--- .../LinkTagHelperTest.cs | 21 +-- .../ScriptTagHelperTest.cs | 23 +--- .../Components/ProductsViewComponent.cs | 13 +- .../Catalog_CacheTagHelper/Details.cshtml | 2 +- 18 files changed, 209 insertions(+), 271 deletions(-) diff --git a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs index 8f7c85efe8..f7a3156b93 100644 --- a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs +++ b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs @@ -1,9 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using Microsoft.AspNet.Mvc; using Microsoft.Framework.Caching; using Microsoft.Framework.Caching.Memory; +using TagHelperSample.Web.Models; using TagHelperSample.Web.Services; namespace TagHelperSample.Web.Components @@ -11,31 +13,41 @@ namespace TagHelperSample.Web.Components [ViewComponent(Name = "FeaturedMovies")] public class FeaturedMoviesComponent : ViewComponent { - private MoviesService _moviesService; + private readonly IMemoryCache _cache; + private readonly MoviesService _moviesService; - public FeaturedMoviesComponent(MoviesService moviesService) + public FeaturedMoviesComponent(MoviesService moviesService, IMemoryCache cache) { _moviesService = moviesService; + _cache = cache; } public IViewComponentResult Invoke() { - IExpirationTrigger trigger; - var movies = _moviesService.GetFeaturedMovies(out trigger); - - // Add custom triggers - EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger }); + // Since this component is invoked from within a CacheTagHelper, + // cache the movie list and provide an expiration trigger, which when triggered causes the + // CacheTagHelper's cached data to be invalidated. + var cacheKey = "featured_movies"; + IEnumerable movies; + if (!_cache.TryGetValue(cacheKey, out movies)) + { + IExpirationTrigger trigger; + movies = _moviesService.GetFeaturedMovies(out trigger); + _cache.Set(cacheKey, movies, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger)); + } return View(movies); } public IViewComponentResult Invoke(string movieName) { - IExpirationTrigger trigger; - var quote = _moviesService.GetCriticsQuote(out trigger); - - // This is invoked as part of a nested cache tag helper. - EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger }); + string quote; + if (!_cache.TryGetValue(movieName, out quote)) + { + IExpirationTrigger trigger; + quote = _moviesService.GetCriticsQuote(out trigger); + _cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger)); + } return Content(quote); } diff --git a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml index 780ae36505..b0ff77c174 100644 --- a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml +++ b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml @@ -35,7 +35,7 @@
diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultCodeTreeCache.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultCodeTreeCache.cs index b43a0a86a4..568de7b45d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultCodeTreeCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/DefaultCodeTreeCache.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives { private static readonly MemoryCacheOptions MemoryCacheOptions = new MemoryCacheOptions { - ListenForMemoryPressure = false + CompactOnMemoryPressure = false }; private static readonly TimeSpan SlidingExpirationDuration = TimeSpan.FromMinutes(1); private readonly IFileProvider _fileProvider; @@ -43,22 +43,25 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives public CodeTree GetOrAdd([NotNull] string pagePath, [NotNull] Func getCodeTree) { - return _codeTreeCache.GetOrSet(pagePath, getCodeTree, OnCacheMiss); - } + CodeTree codeTree; + if (!_codeTreeCache.TryGetValue(pagePath, out codeTree)) + { + // GetOrAdd is invoked for each _GlobalImport that might potentially exist in the path. + // We can avoid performing file system lookups for files that do not exist by caching + // negative results and adding a Watch for that file. - private CodeTree OnCacheMiss(ICacheSetContext cacheSetContext) - { - var pagePath = cacheSetContext.Key; - var getCodeTree = (Func)cacheSetContext.State; + var options = new MemoryCacheEntryOptions() + .AddExpirationTrigger(_fileProvider.Watch(pagePath)) + .SetSlidingExpiration(SlidingExpirationDuration); - // GetOrAdd is invoked for each _ViewImports that might potentially exist in the path. - // We can avoid performing file system lookups for files that do not exist by caching - // negative results and adding a Watch for that file. - cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(pagePath)); - cacheSetContext.SetSlidingExpiration(SlidingExpirationDuration); + var file = _fileProvider.GetFileInfo(pagePath); + codeTree = file.Exists ? getCodeTree(file) : null; - var file = _fileProvider.GetFileInfo(pagePath); - return file.Exists ? getCodeTree(file) : null; + + _codeTreeCache.Set(pagePath, codeTree, options); + } + + return codeTree; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs index 7be2386633..8bf42bee63 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs @@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation IFileProvider fileProvider) { _fileProvider = fileProvider; - _cache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false }); + _cache = new MemoryCache(new MemoryCacheOptions { CompactOnMemoryPressure = false }); var cacheEntries = new List(); foreach (var viewCollection in razorFileInfoCollections) @@ -58,7 +58,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation // 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.Set(NormalizePath(fileInfo.RelativePath), cacheEntry, PopulateCacheSetContext); + var normalizedPath = NormalizePath(fileInfo.RelativePath); + _cache.Set( + normalizedPath, + cacheEntry, + GetMemoryCacheEntryOptions(fileInfo.RelativePath)); cacheEntries.Add(cacheEntry); } @@ -184,9 +188,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation var compilationResult = compile(file).EnsureSuccessful(); // Concurrent addition to MemoryCache with the same key result in safe race. - var cacheEntry = _cache.Set(normalizedPath, - new CompilerCacheEntry(file, compilationResult.CompiledType), - PopulateCacheSetContext); + var compilerCacheEntry = new CompilerCacheEntry(file, compilationResult.CompiledType); + var cacheEntry = _cache.Set( + normalizedPath, + compilerCacheEntry, + GetMemoryCacheEntryOptions(compilerCacheEntry.RelativePath)); + return new GetOrAddResult { CompilationResult = compilationResult, @@ -194,18 +201,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation }; } - private CompilerCacheEntry PopulateCacheSetContext(ICacheSetContext cacheSetContext) + private MemoryCacheEntryOptions GetMemoryCacheEntryOptions(string relativePath) { - var entry = (CompilerCacheEntry)cacheSetContext.State; - cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(entry.RelativePath)); + var options = new MemoryCacheEntryOptions(); + options.AddExpirationTrigger(_fileProvider.Watch(relativePath)); - var viewImportsPaths = ViewHierarchyUtility.GetViewImportsLocations(cacheSetContext.Key); + var viewImportsPaths = ViewHierarchyUtility.GetViewImportsLocations(relativePath); foreach (var location in viewImportsPaths) { - cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(location)); + options.AddExpirationTrigger(_fileProvider.Watch(location)); } - - return entry; + return options; } private bool AssociatedGlobalFilesChanged(CompilerCacheEntry entry, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs index 217c884212..29a788f2ef 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Precompilation/RazorPreCompiler.cs @@ -91,9 +91,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation Parallel.For(0, filesToProcess.Count, parallelOptions, index => { var file = filesToProcess[index]; - var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath, - file, - OnCacheMiss); + + PrecompilationCacheEntry cacheEntry; + if(!PreCompilationCache.TryGetValue(file.RelativePath, out cacheEntry)) + { + cacheEntry = GetCacheEntry(file); + PreCompilationCache.Set( + file.RelativePath, + cacheEntry, + GetMemoryCacheEntryOptions(file, cacheEntry)); + } + if (cacheEntry != null) { if (cacheEntry.Success) @@ -190,21 +198,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation }; } - private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext) + private MemoryCacheEntryOptions GetMemoryCacheEntryOptions( + RelativeFileInfo fileInfo, + PrecompilationCacheEntry cacheEntry) { - var fileInfo = (RelativeFileInfo)cacheSetContext.State; - var entry = GetCacheEntry(fileInfo); - - if (entry != null) + var options = new MemoryCacheEntryOptions(); + options.AddExpirationTrigger(FileProvider.Watch(fileInfo.RelativePath)); + foreach (var path in ViewHierarchyUtility.GetViewImportsLocations(fileInfo.RelativePath)) { - cacheSetContext.AddExpirationTrigger(FileProvider.Watch(fileInfo.RelativePath)); - foreach (var path in ViewHierarchyUtility.GetViewImportsLocations(fileInfo.RelativePath)) - { - cacheSetContext.AddExpirationTrigger(FileProvider.Watch(path)); - } + options.AddExpirationTrigger(FileProvider.Watch(path)); } - - return entry; + return options; } private void GetFileInfosRecursive(string root, List razorFiles) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs index 9179991cab..7d4f72a415 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -106,10 +106,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public TimeSpan? ExpiresSliding { get; set; } /// - /// Gets or sets the policy for the cache entry. + /// Gets or sets the policy for the cache entry. /// [HtmlAttributeName(CachePriorityAttributeName)] - public CachePreservationPriority? Priority { get; set; } + public CacheItemPriority? Priority { get; set; } /// /// Gets or sets the value which determines if the tag helper is enabled or not. @@ -126,19 +126,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var key = GenerateKey(context); if (!MemoryCache.TryGetValue(key, out result)) { - // Create an EntryLink and flow it so that it is accessible via the ambient - // EntryLinkHelpers.ContextLink for user code. - var entryLink = new EntryLink(); - using (entryLink.FlowContext()) + // Create an entry link scope and flow it so that any triggers related to the cache entries + // created within this scope get copied to this scope. + using (var link = MemoryCache.CreateLinkingScope()) { result = await context.GetChildContentAsync(); - } - MemoryCache.Set(key, cacheSetContext => - { - UpdateCacheContext(cacheSetContext, entryLink); - return result; - }); + MemoryCache.Set(key, result, GetMemoryCacheEntryOptions(link)); + } } } @@ -197,29 +192,31 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } // Internal for unit testing - internal void UpdateCacheContext(ICacheSetContext cacheSetContext, EntryLink entryLink) + internal MemoryCacheEntryOptions GetMemoryCacheEntryOptions(IEntryLink entryLink) { + var options = new MemoryCacheEntryOptions(); if (ExpiresOn != null) { - cacheSetContext.SetAbsoluteExpiration(ExpiresOn.Value); + options.SetAbsoluteExpiration(ExpiresOn.Value); } if (ExpiresAfter != null) { - cacheSetContext.SetAbsoluteExpiration(ExpiresAfter.Value); + options.SetAbsoluteExpiration(ExpiresAfter.Value); } if (ExpiresSliding != null) { - cacheSetContext.SetSlidingExpiration(ExpiresSliding.Value); + options.SetSlidingExpiration(ExpiresSliding.Value); } if (Priority != null) { - cacheSetContext.SetPriority(Priority.Value); + options.SetPriority(Priority.Value); } - cacheSetContext.AddEntryLink(entryLink); + options.AddEntryLink(entryLink); + return options; } private static void AddStringCollectionKey(StringBuilder builder, diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/FileVersionProvider.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/FileVersionProvider.cs index 645e62fdf3..cff00bc974 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/FileVersionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/FileVersionProvider.cs @@ -65,12 +65,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal } } - return _cache.GetOrSet(path, cacheGetOrSetContext => + string value; + if(!_cache.TryGetValue(path, out value)) { - var trigger = _fileProvider.Watch(resolvedPath); - cacheGetOrSetContext.AddExpirationTrigger(trigger); - return QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo)); - }); + value = QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo)); + _cache.Set( + path, + value, + new MemoryCacheEntryOptions().AddExpirationTrigger(_fileProvider.Watch(resolvedPath))); + } + + return value; } private string GetHashForFile(IFileInfo fileInfo) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs index 98afa5c8f4..7e84e5f2ad 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs @@ -101,16 +101,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal if (Cache != null) { var cacheKey = $"{nameof(GlobbingUrlBuilder)}-inc:{include}-exc:{exclude}"; - return Cache.GetOrSet(cacheKey, cacheSetContext => + IEnumerable files; + if (!Cache.TryGetValue(cacheKey, out files)) { + var options = new MemoryCacheEntryOptions(); foreach (var pattern in includePatterns) { var trigger = FileProvider.Watch(pattern); - cacheSetContext.AddExpirationTrigger(trigger); + options.AddExpirationTrigger(trigger); } - return FindFiles(includePatterns, excludePatterns); - }); + files = FindFiles(includePatterns, excludePatterns); + + Cache.Set(cacheKey, files, options); + } + return files; } return FindFiles(includePatterns, excludePatterns); @@ -175,7 +180,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal // Only extension differs so just compare the extension var xExt = xExtIndex >= 0 ? x.Substring(xExtIndex) : string.Empty; var yExt = yExtIndex >= 0 ? y.Substring(yExtIndex) : string.Empty; - return string.Compare(xExt, yExt, StringComparison.Ordinal); } diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs index 46b1f84c2e..7305ef875b 100644 --- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs +++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs @@ -27,10 +27,10 @@ namespace Microsoft.AspNet.Mvc { _appServices = services; - // When ListenForMemoryPressure is true, the MemoryCache evicts items at every gen2 collection. + // When CompactOnMemoryPressure is true, the MemoryCache evicts items at every gen2 collection. // In DTH, gen2 happens frequently enough to make it undesirable for caching precompilation results. We'll // disable listening for memory pressure for the MemoryCache instance used by precompilation. - _memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false }); + _memoryCache = new MemoryCache(new MemoryCacheOptions { CompactOnMemoryPressure = false }); } /// diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/DefaultCodeTreeCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/DefaultCodeTreeCacheTest.cs index df6134ae80..676b320f8c 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/DefaultCodeTreeCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/DefaultCodeTreeCacheTest.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using Microsoft.AspNet.Mvc.Razor.Directives; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.Framework.Caching.Memory; -using Microsoft.Framework.Caching.Memory.Infrastructure; +using Microsoft.Framework.Internal; using Moq; using Xunit; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index 58b2f1a0d7..b8c80eee71 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Text; @@ -16,7 +17,7 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Routing; using Microsoft.Framework.Caching; using Microsoft.Framework.Caching.Memory; -using Microsoft.Framework.Caching.Memory.Infrastructure; +using Microsoft.Framework.Internal; using Moq; using Xunit; @@ -244,15 +245,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var childContent = "original-child-content"; var cache = new Mock(); cache.CallBase = true; + var value = new DefaultTagHelperContent().SetContent("ok"); cache.Setup(c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(new DefaultTagHelperContent().SetContent("ok")) + /*value*/ value, + /*optons*/ It.IsAny())) + .Returns(value) .Verifiable(); object cacheResult; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out cacheResult)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out cacheResult)) .Returns(false); var tagHelperContext = GetTagHelperContext(id, childContent); var tagHelperOutput = new TagHelperOutput("cache", new TagHelperAttributeList()); @@ -270,9 +271,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(childContent, tagHelperOutput.Content.GetContent()); cache.Verify(c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>()), + /*value*/ It.IsAny(), + /*options*/ It.IsAny()), Times.Never); } @@ -284,15 +284,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var childContent = "original-child-content"; var cache = new Mock(); cache.CallBase = true; + var value = new DefaultTagHelperContent().SetContent("ok"); + cache.Setup(c => c.CreateLinkingScope()).Returns(new Mock().Object); cache.Setup(c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(new DefaultTagHelperContent().SetContent("ok")) + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(value) .Verifiable(); object cacheResult; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out cacheResult)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out cacheResult)) .Returns(false); var tagHelperContext = GetTagHelperContext(id, childContent); var tagHelperOutput = new TagHelperOutput("cache", new TagHelperAttributeList()); @@ -313,9 +314,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(childContent, tagHelperOutput.Content.GetContent()); cache.Verify(c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>()), + /*value*/ It.IsAny(), + /*options*/ It.IsAny()), Times.Once); } @@ -421,14 +421,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } [Fact] - public void UpdateCacheContext_SetsAbsoluteExpiration_IfExpiresOnIsSet() + public void UpdateCacheEntryOptions_SetsAbsoluteExpiration_IfExpiresOnIsSet() { // Arrange var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(MockBehavior.Strict); - cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -436,21 +433,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(new EntryLink()); // Assert - cacheContext.Verify(); + Assert.Equal(expiresOn, cacheEntryOptions.AbsoluteExpiration); } [Fact] - public void UpdateCacheContext_UsesAbsoluteExpirationSpecifiedOnEntryLink() + public void UpdateCacheEntryOptions_UsesAbsoluteExpirationSpecifiedOnEntryLink() { // Arrange var expiresOn = DateTimeOffset.UtcNow.AddMinutes(7); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(MockBehavior.Strict); - cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache @@ -460,29 +454,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers entryLink.SetAbsoluteExpiration(expiresOn); // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(entryLink); // Assert - cacheContext.Verify(); + Assert.Equal(expiresOn, cacheEntryOptions.AbsoluteExpiration); } [Fact] - public void UpdateCacheContext_PrefersAbsoluteExpirationSpecifiedOnEntryLinkOverExpiresOn() + public void UpdateCacheEntryOptions_PrefersAbsoluteExpirationSpecifiedOnEntryLinkOverExpiresOn() { // Arrange var expiresOn1 = DateTimeOffset.UtcNow.AddDays(12); var expiresOn2 = DateTimeOffset.UtcNow.AddMinutes(4); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); - var sequence = new MockSequence(); - cacheContext.InSequence(sequence) - .Setup(c => c.SetAbsoluteExpiration(expiresOn1)) - .Verifiable(); - - cacheContext.InSequence(sequence) - .Setup(c => c.SetAbsoluteExpiration(expiresOn2)) - .Verifiable(); - var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -493,21 +477,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers entryLink.SetAbsoluteExpiration(expiresOn2); // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(entryLink); // Assert - cacheContext.Verify(); + Assert.Equal(expiresOn2, cacheEntryOptions.AbsoluteExpiration); } [Fact] - public void UpdateCacheContext_SetsAbsoluteExpiration_IfExpiresAfterIsSet() + public void UpdateCacheEntryOptions_SetsAbsoluteExpiration_IfExpiresAfterIsSet() { // Arrange var expiresAfter = TimeSpan.FromSeconds(42); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); - cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresAfter)) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -515,21 +496,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(new EntryLink()); // Assert - cacheContext.Verify(); + Assert.Equal(expiresAfter, cacheEntryOptions.AbsoluteExpirationRelativeToNow); } [Fact] - public void UpdateCacheContext_SetsSlidingExpiration_IfExpiresSlidingIsSet() + public void UpdateCacheEntryOptions_SetsSlidingExpiration_IfExpiresSlidingIsSet() { // Arrange var expiresSliding = TimeSpan.FromSeconds(37); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); - cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding)) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -537,21 +515,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(new EntryLink()); // Assert - cacheContext.Verify(); + Assert.Equal(expiresSliding, cacheEntryOptions.SlidingExpiration); } [Fact] - public void UpdateCacheContext_SetsCachePreservationPriority() + public void UpdateCacheEntryOptions_SetsCachePreservationPriority() { // Arrange - var priority = CachePreservationPriority.High; + var priority = CacheItemPriority.High; var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); - cacheContext.Setup(c => c.SetPriority(priority)) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -559,26 +534,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(new EntryLink()); // Assert - cacheContext.Verify(); + Assert.Equal(priority, cacheEntryOptions.Priority); } [Fact] - public void UpdateCacheContext_CopiesTriggersFromEntryLink() + public void UpdateCacheEntryOptions_CopiesTriggersFromEntryLink() { // Arrange var expiresSliding = TimeSpan.FromSeconds(30); var expected = new[] { Mock.Of(), Mock.Of() }; - var triggers = new List(); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); - cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding)) - .Verifiable(); - cacheContext.Setup(c => c.AddExpirationTrigger(It.IsAny())) - .Callback(triggers.Add) - .Verifiable(); var cacheTagHelper = new CacheTagHelper { MemoryCache = cache, @@ -589,11 +557,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers entryLink.AddExpirationTriggers(expected); // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(entryLink); // Assert - cacheContext.Verify(); - Assert.Equal(expected, triggers); + Assert.Equal(expected, cacheEntryOptions.Triggers.ToArray()); } [Fact] @@ -770,19 +737,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers expectedContent.SetContent("some-content"); var tokenSource = new CancellationTokenSource(); var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheEntryOptions = new MemoryCacheEntryOptions() + .AddExpirationTrigger(new CancellationTokenTrigger(tokenSource.Token)); var tagHelperContext = new TagHelperContext( allAttributes: new TagHelperAttributeList(), items: new Dictionary(), uniqueId: id, getChildContentAsync: () => { - var entryLink = EntryLinkHelpers.ContextLink; - Assert.NotNull(entryLink); - entryLink.AddExpirationTriggers(new[] + TagHelperContent tagHelperContent; + if(!cache.TryGetValue("key1", out tagHelperContent)) { - new CancellationTokenTrigger(tokenSource.Token) - }); - return Task.FromResult(expectedContent); + tagHelperContent = expectedContent; + cache.Set("key1", tagHelperContent, cacheEntryOptions); + } + + return Task.FromResult(tagHelperContent); }); var tagHelperOutput = new TagHelperOutput("cache", new TagHelperAttributeList { { "attr", "value" } }); tagHelperOutput.PreContent.SetContent(""); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ImageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ImageTagHelperTest.cs index 5b36b8c128..dba73ef93f 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ImageTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ImageTagHelperTest.cs @@ -260,29 +260,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers object result = null; var cache = new Mock(); cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out result)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out result)) .Returns(result != null); - - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny())); - cache - .Setup( + cache.Setup( c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(( - string input, - IEntryLink entryLink, - object state, - Func create) => - { - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - } - }); + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(new object()); return cache.Object; } } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs index af31f0982c..b75a679a50 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs @@ -141,21 +141,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal object cacheValue = null; var cache = new Mock(); cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out cacheValue)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out cacheValue)) .Returns(cacheValue != null); - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(trigger.Object)).Verifiable(); cache.Setup(c => c.Set( /*key*/ filePath, - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns>( - (key, link, state, create) => - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - }) + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(new object()) .Verifiable(); var fileVersionProvider = new FileVersionProvider( hostingEnvironment.WebRootFileProvider, @@ -167,7 +159,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal // Assert Assert.Equal(filePath + "?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); - cacheSetContext.VerifyAll(); cache.VerifyAll(); } @@ -211,29 +202,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { var cache = new Mock(); cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out result)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out result)) .Returns(result != null); - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny())); cache .Setup( c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(( - string input, - IEntryLink entryLink, - object state, - Func create) => - { - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - } - }); + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(new object()); return cache.Object; } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs index 83ad012a39..76abc652f5 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs @@ -252,18 +252,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { // Arrange var fileProvider = MakeFileProvider(); - var cache = MakeCache(new List { "/blank.css", "/site.css" }); + var expected = new List { "/blank.css", "/site.css" }; + var cache = MakeCache(result: expected); var requestPathBase = PathString.Empty; var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase); // Act - var urlList = globbingUrlBuilder.BuildUrlList( + var actual = globbingUrlBuilder.BuildUrlList( staticUrl: null, includePattern: "**/*.css", excludePattern: null); // Assert - Assert.Collection(urlList, + Assert.Collection(actual, url => Assert.Equal("/blank.css", url), url => Assert.Equal("/site.css", url)); } @@ -276,19 +277,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css")); Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny())).Returns(trigger.Object); var cache = MakeCache(); - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(trigger.Object)).Verifiable(); Mock.Get(cache).Setup(c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns>( - (key, link, state, create) => - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - }) + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(new object()) .Verifiable(); var requestPathBase = PathString.Empty; var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase); @@ -303,7 +296,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal Assert.Collection(urlList, url => Assert.Equal("/blank.css", url), url => Assert.Equal("/site.css", url)); - cacheSetContext.VerifyAll(); Mock.Get(cache).VerifyAll(); } @@ -394,11 +386,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal { throw new ArgumentException(nameof(rootNode)); } - + var fileProvider = new Mock(MockBehavior.Strict); fileProvider.Setup(fp => fp.GetDirectoryContents(string.Empty)) .Returns(MakeDirectoryContents(rootNode, fileProvider)); - + return fileProvider.Object; } @@ -418,7 +410,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal .Returns(MakeDirectoryContents(node, fileProviderMock)); } } - + var directoryContents = new Mock(); directoryContents.Setup(dc => dc.GetEnumerator()).Returns(children.GetEnumerator()); @@ -450,7 +442,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal private static IMemoryCache MakeCache(object result = null) { var cache = new Mock(); - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out result)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out result)) .Returns(result != null); return cache.Object; } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index 5de1674ed5..29875f2303 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -760,29 +760,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { var cache = new Mock(); cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out result)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out result)) .Returns(result != null); - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny())); cache .Setup( c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(( - string input, - IEntryLink entryLink, - object state, - Func create) => - { - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - } - }); + /*value*/ It.IsAny(), + /*options*/ It.IsAny())) + .Returns(result); return cache.Object; } } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index bd5349a821..4803670fae 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -842,29 +842,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { var cache = new Mock(); cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out result)) + cache.Setup(c => c.TryGetValue(It.IsAny(), out result)) .Returns(result != null); - var cacheSetContext = new Mock(); - cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny())); + var cacheEntryOptions = new MemoryCacheEntryOptions(); + cacheEntryOptions.AddExpirationTrigger(new Mock().Object); cache .Setup( c => c.Set( /*key*/ It.IsAny(), - /*link*/ It.IsAny(), - /*state*/ It.IsAny(), - /*create*/ It.IsAny>())) - .Returns(( - string input, - IEntryLink entryLink, - object state, - Func create) => - { - { - cacheSetContext.Setup(c => c.State).Returns(state); - return create(cacheSetContext.Object); - } - }); + /*value*/ It.IsAny(), + /*options*/ cacheEntryOptions)) + .Returns(result); return cache.Object; } } diff --git a/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs index 0d57eb8207..c5d44bdc77 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs @@ -12,11 +12,18 @@ namespace MvcTagHelpersWebSite.Components [Activate] public ProductsService ProductsService { get; set; } + [Activate] + public IMemoryCache Cache { get; set; } + public IViewComponentResult Invoke(string category) { - IExpirationTrigger trigger; - var products = ProductsService.GetProducts(category, out trigger); - EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger }); + string products; + if (!Cache.TryGetValue(category, out products)) + { + IExpirationTrigger trigger; + products = ProductsService.GetProducts(category, out trigger); + Cache.Set(category, products, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger)); + } ViewData["Products"] = products; return View(); diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml index 2cd777074f..0a5efbcb8c 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml @@ -1,4 +1,4 @@ @using Microsoft.Framework.Caching.Memory - + Cached content for @ViewBag.ProductId