diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs index 8d629b4bce..66c114c159 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs @@ -82,22 +82,28 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var tcs = new TaskCompletionSource(); - 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 { diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index fe8eaa7a87..b54e53933a 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -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(); - var value = new DefaultTagHelperContent().SetContent("ok"); - cache.Setup(c => c.Set( - /*key*/ It.IsAny(), - /*value*/ value, - /*optons*/ It.IsAny())) - .Returns(value); + var value = new Mock(); + value.Setup(c => c.Value).Returns(new DefaultTagHelperContent().SetContent("ok")); + cache.Setup(c => c.CreateEntry( + /*key*/ It.IsAny())) + .Returns((object key) => value.Object) + .Verifiable(); object cacheResult; cache.Setup(c => c.TryGetValue(It.IsAny(), 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(), - /*value*/ It.IsAny(), - /*options*/ It.IsAny()), + cache.Verify(c => c.CreateEntry( + /*key*/ It.IsAny()), Times.Never); } @@ -288,13 +287,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var id = "unique-id"; var childContent = "original-child-content"; var cache = new Mock(); - var value = new DefaultTagHelperContent().SetContent("ok"); - cache.Setup(c => c.CreateLinkingScope()).Returns(new Mock().Object); - cache.Setup(c => c.Set( - /*key*/ It.IsAny(), - /*value*/ It.IsAny(), - /*options*/ It.IsAny())) - .Returns(value); + var value = new Mock(); + value.Setup(c => c.Value).Returns(new DefaultTagHelperContent().SetContent("ok")); + value.Setup(c => c.ExpirationTokens).Returns(new List()); + cache.Setup(c => c.CreateEntry( + /*key*/ It.IsAny())) + .Returns((object key) => value.Object); object cacheResult; cache.Setup(c => c.TryGetValue(It.IsAny(), 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(), - /*value*/ It.IsAny(), - /*options*/ It.IsAny()), + cache.Verify(c => c.CreateEntry( + /*key*/ It.IsAny()), 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(), Mock.Of() }; - 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> 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()) diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs index 930b0b0623..533a38faf3 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs @@ -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(); + value.Setup(c => c.Value).Returns(cacheValue); + value.Setup(c => c.ExpirationTokens).Returns(new List()); var cache = new Mock(); cache.CallBase = true; cache.Setup(c => c.TryGetValue(It.IsAny(), out cacheValue)) .Returns(cacheValue != null); - cache.Setup(c => c.Set( - /*key*/ filePath, - /*value*/ It.IsAny(), - /*options*/ It.IsAny())) - .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, diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs index 5bec5b2cc8..8493f1daa5 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs @@ -275,12 +275,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal var changeToken = new Mock(); var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css")); Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny())).Returns(changeToken.Object); + var value = new Mock(); + value.Setup(c => c.Value).Returns(null); + value.Setup(c => c.ExpirationTokens).Returns(new List()); var cache = MakeCache(); - Mock.Get(cache).Setup(c => c.Set( - /*key*/ It.IsAny(), - /*value*/ It.IsAny>(), - /*options*/ It.IsAny())) - .Returns((object key, object value, MemoryCacheEntryOptions options) => value) + Mock.Get(cache).Setup(c => c.CreateEntry( + /*key*/ It.IsAny())) + .Returns((object key) => value.Object) .Verifiable(); var requestPathBase = PathString.Empty; var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase);