Reacting to changes in Caching

This commit is contained in:
Sebastien Ros 2016-03-25 10:56:34 -07:00
parent 66ff9939c3
commit 476d2eb81d
4 changed files with 59 additions and 110 deletions

View File

@ -82,22 +82,28 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var tcs = new TaskCompletionSource<IHtmlContent>();
MemoryCache.Set(key, tcs.Task, options);
// The returned value is ignored, we only do this so that
// the compiler doesn't complain about the returned task
// not being awaited
var localTcs = MemoryCache.Set(key, tcs.Task, options);
try
{
using (var link = MemoryCache.CreateLinkingScope())
{
result = ProcessContentAsync(output);
content = await result;
options.AddEntryLink(link);
}
// The entry is set instead of assigning a value to the
// task so that the expiration options are are not impacted
// by the time it took to compute it.
MemoryCache.Set(key, result, options);
using (var entry = MemoryCache.CreateEntry(key))
{
// The result is processed inside an entry
// such that the tokens are inherited.
result = ProcessContentAsync(output);
content = await result;
entry.SetOptions(options);
entry.Value = result;
}
}
catch
{

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
@ -250,12 +251,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var id = "unique-id";
var childContent = "original-child-content";
var cache = new Mock<IMemoryCache>();
var value = new DefaultTagHelperContent().SetContent("ok");
cache.Setup(c => c.Set(
/*key*/ It.IsAny<string>(),
/*value*/ value,
/*optons*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(value);
var value = new Mock<ICacheEntry>();
value.Setup(c => c.Value).Returns(new DefaultTagHelperContent().SetContent("ok"));
cache.Setup(c => c.CreateEntry(
/*key*/ It.IsAny<string>()))
.Returns((object key) => value.Object)
.Verifiable();
object cacheResult;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out cacheResult))
.Returns(false);
@ -274,10 +275,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Equal(childContent, tagHelperOutput.Content.GetContent());
cache.Verify(c => c.Set(
/*key*/ It.IsAny<string>(),
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()),
cache.Verify(c => c.CreateEntry(
/*key*/ It.IsAny<string>()),
Times.Never);
}
@ -288,13 +287,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var id = "unique-id";
var childContent = "original-child-content";
var cache = new Mock<IMemoryCache>();
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>(),
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns(value);
var value = new Mock<ICacheEntry>();
value.Setup(c => c.Value).Returns(new DefaultTagHelperContent().SetContent("ok"));
value.Setup(c => c.ExpirationTokens).Returns(new List<IChangeToken>());
cache.Setup(c => c.CreateEntry(
/*key*/ It.IsAny<string>()))
.Returns((object key) => value.Object);
object cacheResult;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out cacheResult))
.Returns(false);
@ -318,10 +316,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Equal(childContent, tagHelperOutput.Content.GetContent());
// There are two calls to set (for the TCS and the processed value)
cache.Verify(c => c.Set(
/*key*/ It.IsAny<string>(),
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()),
cache.Verify(c => c.CreateEntry(
/*key*/ It.IsAny<string>()),
Times.Exactly(2));
}
@ -443,51 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Equal(expiresOn, cacheEntryOptions.AbsoluteExpiration);
}
[Fact]
public void UpdateCacheEntryOptions_UsesAbsoluteExpirationSpecifiedOnEntryLink()
{
// Arrange
var expiresOn = DateTimeOffset.UtcNow.AddMinutes(7);
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheTagHelper = new CacheTagHelper(cache, new HtmlTestEncoder())
{
};
var entryLink = new EntryLink();
entryLink.SetAbsoluteExpiration(expiresOn);
// Act
var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions();
cacheEntryOptions.AddEntryLink(entryLink);
// Assert
Assert.Equal(expiresOn, cacheEntryOptions.AbsoluteExpiration);
}
[Fact]
public void UpdateCacheEntryOptions_PrefersAbsoluteExpirationSpecifiedOnEntryLinkOverExpiresOn()
{
// Arrange
var expiresOn1 = DateTimeOffset.UtcNow.AddDays(12);
var expiresOn2 = DateTimeOffset.UtcNow.AddMinutes(4);
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheTagHelper = new CacheTagHelper(cache, new HtmlTestEncoder())
{
ExpiresOn = expiresOn1
};
var entryLink = new EntryLink();
entryLink.SetAbsoluteExpiration(expiresOn2);
// Act
var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions();
cacheEntryOptions.AddEntryLink(entryLink);
// Assert
Assert.Equal(expiresOn2, cacheEntryOptions.AbsoluteExpiration);
}
[Fact]
public void UpdateCacheEntryOptions_SetsAbsoluteExpiration_IfExpiresAfterIsSet()
{
@ -541,30 +493,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
Assert.Equal(priority, cacheEntryOptions.Priority);
}
[Fact]
public void UpdateCacheEntryOptions_CopiesTriggersFromEntryLink()
{
// Arrange
var expiresSliding = TimeSpan.FromSeconds(30);
var expected = new[] { Mock.Of<IChangeToken>(), Mock.Of<IChangeToken>() };
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheTagHelper = new CacheTagHelper(cache, new HtmlTestEncoder())
{
ExpiresSliding = expiresSliding
};
var entryLink = new EntryLink();
entryLink.AddExpirationTokens(expected);
// Act
var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions();
cacheEntryOptions.AddEntryLink(entryLink);
// Assert
Assert.Equal(expected, cacheEntryOptions.ExpirationTokens.ToArray());
}
[Fact]
public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry()
{
@ -961,6 +890,17 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
});
}
private static TagHelperOutput GetTagHelperOutput(
Func<bool, HtmlEncoder, Task<TagHelperContent>> processAsync)
{
var attributes = new TagHelperAttributeList { { "attr", "value" } };
return new TagHelperOutput(
"cache",
attributes,
getChildContentAsync: processAsync);
}
private static string GetHashedBytes(string input)
{
using (var sha = SHA256.Create())

View File

@ -1,6 +1,7 @@
// 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 System.IO;
using System.Text;
using Microsoft.AspNetCore.Http;
@ -278,15 +279,16 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
.Setup(f => f.Watch(watchPath)).Returns(changeToken.Object);
object cacheValue = null;
var value = new Mock<ICacheEntry>();
value.Setup(c => c.Value).Returns(cacheValue);
value.Setup(c => c.ExpirationTokens).Returns(new List<IChangeToken>());
var cache = new Mock<IMemoryCache>();
cache.CallBase = true;
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), out cacheValue))
.Returns(cacheValue != null);
cache.Setup(c => c.Set(
/*key*/ filePath,
/*value*/ It.IsAny<object>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns((object key, object value, MemoryCacheEntryOptions options) => value)
cache.Setup(c => c.CreateEntry(
/*key*/ filePath))
.Returns((object key) => value.Object)
.Verifiable();
var fileVersionProvider = new FileVersionProvider(
fileProvider,

View File

@ -275,12 +275,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal
var changeToken = new Mock<IChangeToken>();
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny<string>())).Returns(changeToken.Object);
var value = new Mock<ICacheEntry>();
value.Setup(c => c.Value).Returns(null);
value.Setup(c => c.ExpirationTokens).Returns(new List<IChangeToken>());
var cache = MakeCache();
Mock.Get(cache).Setup(c => c.Set(
/*key*/ It.IsAny<object>(),
/*value*/ It.IsAny<List<string>>(),
/*options*/ It.IsAny<MemoryCacheEntryOptions>()))
.Returns((object key, object value, MemoryCacheEntryOptions options) => value)
Mock.Get(cache).Setup(c => c.CreateEntry(
/*key*/ It.IsAny<object>()))
.Returns((object key) => value.Object)
.Verifiable();
var requestPathBase = PathString.Empty;
var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);