React to Caching api review changes

This commit is contained in:
Kiran Challa 2015-05-15 14:32:20 -07:00
parent 8a701726b3
commit 2a321fd622
18 changed files with 209 additions and 271 deletions

View File

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

View File

@ -35,7 +35,7 @@
</div>
<div class="sidebar">
<cache expires-after="TimeSpan.FromMinutes(20)">
@await Component.InvokeAsync("FeaturedMovies")
@Component.Invoke("FeaturedMovies")
</cache>
</div>
<div style="clear: left"></div>

View File

@ -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<IFileInfo, CodeTree> 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<IFileInfo, CodeTree>)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;
}
}
}

View File

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

View File

@ -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<RelativeFileInfo> razorFiles)

View File

@ -106,10 +106,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public TimeSpan? ExpiresSliding { get; set; }
/// <summary>
/// Gets or sets the <see cref="CachePreservationPriority"/> policy for the cache entry.
/// Gets or sets the <see cref="CacheItemPriority"/> policy for the cache entry.
/// </summary>
[HtmlAttributeName(CachePriorityAttributeName)]
public CachePreservationPriority? Priority { get; set; }
public CacheItemPriority? Priority { get; set; }
/// <summary>
/// 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,

View File

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

View File

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

View File

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

View File

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

View File

@ -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<IMemoryCache>();
cache.CallBase = true;
var value = new DefaultTagHelperContent().SetContent("ok");
cache.Setup(c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns(new DefaultTagHelperContent().SetContent("ok"))
/*value*/ value,
/*optons*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(value)
.Verifiable();
object cacheResult;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out cacheResult))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), 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<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()),
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()),
Times.Never);
}
@ -284,15 +284,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var childContent = "original-child-content";
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
var value = new DefaultTagHelperContent().SetContent("ok");
cache.Setup(c => c.CreateLinkingScope()).Returns(new Mock<IEntryLink>().Object);
cache.Setup(c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns(new DefaultTagHelperContent().SetContent("ok"))
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(value)
.Verifiable();
object cacheResult;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out cacheResult))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), 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<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()),
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()),
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<ICacheSetContext>(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<ICacheSetContext>(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<ICacheSetContext>();
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<ICacheSetContext>();
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<ICacheSetContext>();
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<ICacheSetContext>();
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<IExpirationTrigger>(), Mock.Of<IExpirationTrigger>() };
var triggers = new List<IExpirationTrigger>();
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheContext = new Mock<ICacheSetContext>();
cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding))
.Verifiable();
cacheContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()))
.Callback<IExpirationTrigger>(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<object, object>(),
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<TagHelperContent>(expectedContent);
tagHelperContent = expectedContent;
cache.Set("key1", tagHelperContent, cacheEntryOptions);
}
return Task.FromResult(tagHelperContent);
});
var tagHelperOutput = new TagHelperOutput("cache", new TagHelperAttributeList { { "attr", "value" } });
tagHelperOutput.PreContent.SetContent("<cache>");

View File

@ -260,29 +260,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
object result = null;
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
.Returns(result != null);
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()));
cache
.Setup(
cache.Setup(
c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns((
string input,
IEntryLink entryLink,
object state,
Func<ICacheSetContext, object> create) =>
{
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
}
});
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(new object());
return cache.Object;
}
}

View File

@ -141,21 +141,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
object cacheValue = null;
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out cacheValue))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out cacheValue))
.Returns(cacheValue != null);
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(trigger.Object)).Verifiable();
cache.Setup(c => c.Set(
/*key*/ filePath,
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns<string, IEntryLink, object, Func<ICacheSetContext, object>>(
(key, link, state, create) =>
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
})
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.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<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
.Returns(result != null);
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()));
cache
.Setup(
c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns((
string input,
IEntryLink entryLink,
object state,
Func<ICacheSetContext, object> create) =>
{
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
}
});
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(new object());
return cache.Object;
}

View File

@ -252,18 +252,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
{
// Arrange
var fileProvider = MakeFileProvider();
var cache = MakeCache(new List<string> { "/blank.css", "/site.css" });
var expected = new List<string> { "/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<string>())).Returns(trigger.Object);
var cache = MakeCache();
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(trigger.Object)).Verifiable();
Mock.Get(cache).Setup(c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns<string, IEntryLink, object, Func<ICacheSetContext, object>>(
(key, link, state, create) =>
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
})
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.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<IFileProvider>(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<IDirectoryContents>();
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<IMemoryCache>();
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
.Returns(result != null);
return cache.Object;
}

View File

@ -760,29 +760,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
.Returns(result != null);
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()));
cache
.Setup(
c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns((
string input,
IEntryLink entryLink,
object state,
Func<ICacheSetContext, object> create) =>
{
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
}
});
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(result);
return cache.Object;
}
}

View File

@ -842,29 +842,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out result))
.Returns(result != null);
var cacheSetContext = new Mock<ICacheSetContext>();
cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()));
var cacheEntryOptions = new MemoryCacheEntryOptions();
cacheEntryOptions.AddExpirationTrigger(new Mock<IExpirationTrigger>().Object);
cache
.Setup(
c => c.Set(
/*key*/ It.IsAny<string>(),
/*link*/ It.IsAny<IEntryLink>(),
/*state*/ It.IsAny<object>(),
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
.Returns((
string input,
IEntryLink entryLink,
object state,
Func<ICacheSetContext, object> create) =>
{
{
cacheSetContext.Setup(c => c.State).Returns(state);
return create(cacheSetContext.Object);
}
});
/*value*/ It.IsAny<object>(),
/*options*/ cacheEntryOptions))
.Returns(result);
return cache.Object;
}
}

View File

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

View File

@ -1,4 +1,4 @@
@using Microsoft.Framework.Caching.Memory
<cache expires-after="TimeSpan.FromSeconds(1)" priority="CachePreservationPriority.Low">
<cache expires-after="TimeSpan.FromSeconds(1)" priority="CacheItemPriority.Low">
Cached content for @ViewBag.ProductId
</cache>