diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs index 2b0eea317c..5a0416fbf9 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -33,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers private const string ExpiresSlidingAttributeName = "expires-sliding"; private const string CachePriorityAttributeName = "priority"; private const string CacheKeyTokenSeparator = "||"; + private const string EnabledAttributeName = "enabled"; private static readonly char[] AttributeSeparator = new[] { ',' }; /// @@ -108,31 +109,48 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [HtmlAttributeName(CachePriorityAttributeName)] public CachePreservationPriority? Priority { get; set; } + /// + /// Gets or sets the value which determines if the tag helper is enabled or not. + /// + [HtmlAttributeName(EnabledAttributeName)] + public bool Enabled { get; set; } = true; + /// public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { - var key = GenerateKey(context); - TagHelperContent result; - if (!MemoryCache.TryGetValue(key, out result)) + TagHelperContent result = null; + if (Enabled) { - // Create an EntryLink and flow it so that it is accessible via the ambient EntryLinkHelpers.ContentLink - // for user code. - var entryLink = new EntryLink(); - using (entryLink.FlowContext()) + var key = GenerateKey(context); + if (!MemoryCache.TryGetValue(key, out result)) { - result = await context.GetChildContentAsync(); - } + // 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()) + { + result = await context.GetChildContentAsync(); + } - MemoryCache.Set(key, cacheSetContext => - { - UpdateCacheContext(cacheSetContext, entryLink); - return result; - }); + MemoryCache.Set(key, cacheSetContext => + { + UpdateCacheContext(cacheSetContext, entryLink); + return result; + }); + } } // Clear the contents of the "cache" element since we don't want to render it. output.SuppressOutput(); - output.Content.SetContent(result); + if (Enabled) + { + output.Content.SetContent(result); + } + else + { + result = await context.GetChildContentAsync(); + output.Content.SetContent(result); + } } // Internal for unit testing diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs index 9071f5d29d..c30f19a55e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs @@ -228,6 +228,30 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expected2, response2.Trim()); } + [Fact] + public async Task CacheTagHelper_Activated_BasedOnEnabledParameter() + { + // Arrange + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act + // This is cached for 1s. + var response1 = await client.GetStringAsync("/catalog/31"); + var response2 = await client.GetStringAsync("/catalog/32"); + + // This request is made before the cache expires with enabled set to false. + client.DefaultRequestHeaders.Remove("IsCacheEnabled"); + client.DefaultRequestHeaders.Add("IsCacheEnabled", "false"); + var response3 = await client.GetStringAsync("/catalog/156"); + + // Assert + Assert.Equal("Cached content for 31", response1.Trim()); + Assert.Equal("Cached content for 31", response2.Trim()); + Assert.Equal("Cached content for 156", response3.Trim()); + } + [Fact] public async Task CacheTagHelper_UsesVaryByCookie_ToVaryContent() { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index 06168c4153..f7facca604 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -236,6 +236,89 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(expected, key); } + [Fact] + public async Task ProcessAsync_DoesNotCache_IfDisabled() + { + // Arrange + var id = "unique-id"; + var childContent = "original-child-content"; + var cache = new Mock(); + cache.CallBase = true; + cache.Setup(c => c.Set( + /*key*/ It.IsAny(), + /*link*/ It.IsAny(), + /*state*/ It.IsAny(), + /*create*/ It.IsAny>())) + .Returns(new DefaultTagHelperContent().SetContent("ok")) + .Verifiable(); + object cacheResult; + cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out cacheResult)) + .Returns(false); + var tagHelperContext = GetTagHelperContext(id, childContent); + var tagHelperOutput = new TagHelperOutput("cache", new Dictionary()); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache.Object, + Enabled = false + }; + + // Act + await cacheTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); + + // Assert + Assert.Equal(childContent, tagHelperOutput.Content.GetContent()); + cache.Verify(c => c.Set( + /*key*/ It.IsAny(), + /*link*/ It.IsAny(), + /*state*/ It.IsAny(), + /*create*/ It.IsAny>()), + Times.Never); + } + + [Fact] + public async Task ProcessAsync_ReturnsCachedValue_IfEnabled() + { + // Arrange + var id = "unique-id"; + var childContent = "original-child-content"; + var cache = new Mock(); + cache.CallBase = true; + cache.Setup(c => c.Set( + /*key*/ It.IsAny(), + /*link*/ It.IsAny(), + /*state*/ It.IsAny(), + /*create*/ It.IsAny>())) + .Returns(new DefaultTagHelperContent().SetContent("ok")) + .Verifiable(); + object cacheResult; + cache.Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out cacheResult)) + .Returns(false); + var tagHelperContext = GetTagHelperContext(id, childContent); + var tagHelperOutput = new TagHelperOutput("cache", new Dictionary()); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache.Object, + Enabled = true + }; + + // Act + await cacheTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); + + // Assert + Assert.Empty(tagHelperOutput.PreContent.GetContent()); + Assert.Empty(tagHelperOutput.PostContent.GetContent()); + Assert.True(tagHelperOutput.IsContentModified); + Assert.Equal(childContent, tagHelperOutput.Content.GetContent()); + cache.Verify(c => c.Set( + /*key*/ It.IsAny(), + /*link*/ It.IsAny(), + /*state*/ It.IsAny(), + /*create*/ It.IsAny>()), + Times.Once); + } + [Fact] public async Task ProcessAsync_ReturnsCachedValue_IfVaryByParamIsUnchanged() { diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs index 51ba703cf4..85001aafb9 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs @@ -12,7 +12,7 @@ namespace MvcTagHelpersWebSite.Controllers public ProductsService ProductsService { get; set; } [HttpGet("/catalog")] - public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale) + public ViewResult Splash(int categoryId, int correlationId, [FromHeader] string locale) { var category = categoryId == 1 ? "Laptops" : "Phones"; ViewData["Category"] = category; @@ -23,8 +23,15 @@ namespace MvcTagHelpersWebSite.Controllers } [HttpGet("/catalog/{id:int}")] - public ViewResult Details(int id) + public ViewResult Details(int id, [FromHeader] string isCacheEnabled) { + bool cacheEnabledHeader = true; + if (!string.IsNullOrEmpty(isCacheEnabled)) + { + bool.TryParse(isCacheEnabled, out cacheEnabledHeader); + } + + ViewBag.IsCacheEnabled = cacheEnabledHeader; ViewData["ProductId"] = id; return View(); } diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml index 2cd777074f..8ddafaac41 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