From 6c21b40894ef93d8f97072991426a42ed324c074 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 16:29:46 -0800 Subject: [PATCH] EntryLinkHelpers.ContentLink should be available to use for user code inside of a cache tag helper's body. Fixes: #1867 --- .../Components/FeaturedMoviesComponent.cs | 44 +++++ .../Controllers/MoviesController.cs | 45 ++++++ .../Models/FeaturedMovies.cs | 16 ++ .../Services/MoviesService.cs | 68 ++++++++ samples/TagHelperSample.Web/Startup.cs | 2 + .../Views/Movies/Index.cshtml | 43 +++++ .../Components/FeaturedMovies/Default.cshtml | 17 ++ .../CacheTagHelper.cs | 15 +- .../MvcTagHelpersTests.cs | 40 +++++ .../CacheTagHelperTest.cs | 153 +++++++++++++++++- .../Components/ProductsViewComponent.cs | 25 +++ .../Catalog_CacheTagHelperController.cs | 19 +++ .../MvcTagHelpersWebSite/ProductsService.cs | 34 ++++ test/WebSites/MvcTagHelpersWebSite/Startup.cs | 1 + .../ListCategories.cshtml | 4 + .../Shared/Components/Products/Default.cshtml | 1 + 16 files changed, 518 insertions(+), 9 deletions(-) create mode 100644 samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs create mode 100644 samples/TagHelperSample.Web/Controllers/MoviesController.cs create mode 100644 samples/TagHelperSample.Web/Models/FeaturedMovies.cs create mode 100644 samples/TagHelperSample.Web/Services/MoviesService.cs create mode 100644 samples/TagHelperSample.Web/Views/Movies/Index.cshtml create mode 100644 samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/ProductsService.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml diff --git a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs new file mode 100644 index 0000000000..3726cfe781 --- /dev/null +++ b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; +using TagHelperSample.Web.Services; + +namespace TagHelperSample.Web.Components +{ + [ViewComponent(Name = "FeaturedMovies")] + public class FeaturedMoviesComponent : ViewComponent + { + private MoviesService _moviesService; + + public FeaturedMoviesComponent(MoviesService moviesService) + { + _moviesService = moviesService; + } + + public IViewComponentResult Invoke() + { + IExpirationTrigger trigger; + var movies = _moviesService.GetFeaturedMovies(out trigger); + + // Add custom triggers + EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { 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 }); + + return Content(quote); + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Controllers/MoviesController.cs b/samples/TagHelperSample.Web/Controllers/MoviesController.cs new file mode 100644 index 0000000000..7c767f9487 --- /dev/null +++ b/samples/TagHelperSample.Web/Controllers/MoviesController.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; +using TagHelperSample.Web.Services; + +namespace TagHelperSample.Web.Controllers +{ + public class MoviesController : Controller + { + private MoviesService _moviesService; + + public MoviesController(MoviesService moviesService) + { + _moviesService = moviesService; + } + + // Sample exhibiting the use of nested cache tag helpers with custom user expiration triggers. + // Trigger expirations cascade, expiration of the inner tag helper's content either due to absolute or sliding + // expiration or due to a user specified expiration trigger would cause the outer cache tag helper to also expire. + public ViewResult Index() + { + ViewData["Title"] = "Movies"; + return View(); + } + + [HttpPost] + public ViewResult UpdateMovieRatings() + { + _moviesService.UpdateMovieRating(); + + ViewData["Title"] = "Movies with updated ratings"; + return View("Index"); + } + + [HttpPost] + public ViewResult UpdateCriticsQuotes() + { + _moviesService.UpdateCriticsQuotes(); + + ViewData["Title"] = "Movies with updated critics quotes"; + return View("Index"); + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Models/FeaturedMovies.cs b/samples/TagHelperSample.Web/Models/FeaturedMovies.cs new file mode 100644 index 0000000000..2ec4ab9954 --- /dev/null +++ b/samples/TagHelperSample.Web/Models/FeaturedMovies.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace TagHelperSample.Web.Models +{ + public class FeaturedMovies + { + public string Name { get; set; } + + public string Description { get; set; } + + public int Rank { get; set; } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Services/MoviesService.cs b/samples/TagHelperSample.Web/Services/MoviesService.cs new file mode 100644 index 0000000000..0f0ad259e4 --- /dev/null +++ b/samples/TagHelperSample.Web/Services/MoviesService.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; +using TagHelperSample.Web.Models; + +namespace TagHelperSample.Web.Services +{ + public class MoviesService + { + private readonly Random _random = new Random(); + + private CancellationTokenSource _featuredMoviesTokenSource; + private CancellationTokenSource _quotesTokenSource; + + public IEnumerable GetFeaturedMovies(out IExpirationTrigger expirationTrigger) + { + _featuredMoviesTokenSource = new CancellationTokenSource(); + + expirationTrigger = new CancellationTokenTrigger(_featuredMoviesTokenSource.Token); + return GetMovies().OrderBy(m => m.Rank).Take(2); + } + + public void UpdateMovieRating() + { + _featuredMoviesTokenSource.Cancel(); + _featuredMoviesTokenSource.Dispose(); + _featuredMoviesTokenSource = null; + } + + public string GetCriticsQuote(out IExpirationTrigger trigger) + { + _quotesTokenSource = new CancellationTokenSource(); + + var quotes = new[] + { + "A must see for iguana lovers everywhere", + "Slightly better than watching paint dry", + "Never felt more relieved seeing the credits roll", + "Bravo!" + }; + + trigger = new CancellationTokenTrigger(_quotesTokenSource.Token); + return quotes[_random.Next(0, quotes.Length)]; + } + + public void UpdateCriticsQuotes() + { + _quotesTokenSource.Cancel(); + _quotesTokenSource.Dispose(); + _quotesTokenSource = null; + } + + private IEnumerable GetMovies() + { + yield return new FeaturedMovies { Name = "A day in the life of a blue whale", Rank = _random.Next(1, 10) }; + yield return new FeaturedMovies { Name = "FlashForward", Rank = _random.Next(1, 10) }; + yield return new FeaturedMovies { Name = "Frontier", Rank = _random.Next(1, 10) }; + yield return new FeaturedMovies { Name = "Attack of the space spiders", Rank = _random.Next(1, 10) }; + yield return new FeaturedMovies { Name = "Rift 3", Rank = _random.Next(1, 10) }; + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Startup.cs b/samples/TagHelperSample.Web/Startup.cs index 0742f29a6f..98c5de0397 100644 --- a/samples/TagHelperSample.Web/Startup.cs +++ b/samples/TagHelperSample.Web/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.Framework.DependencyInjection; +using TagHelperSample.Web.Services; namespace TagHelperSample.Web { @@ -18,6 +19,7 @@ namespace TagHelperSample.Web // Setup services with a test AssemblyProvider so that only the sample's assemblies are loaded. This // prevents loading controllers from other assemblies when the sample is used in functional tests. services.AddTransient>(); + services.AddSingleton(); services.Configure(options => { diff --git a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml new file mode 100644 index 0000000000..780ae36505 --- /dev/null +++ b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml @@ -0,0 +1,43 @@ + + + + @ViewBag.Title + + + +
+

Watch the greatest movies right here!

+ Submit your movie rankings: + +
+ Movies + ratings go here + +
+ +
+ Movies + ratings go here + +
+
+ +
+ + diff --git a/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml new file mode 100644 index 0000000000..8348bffa01 --- /dev/null +++ b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml @@ -0,0 +1,17 @@ +@model IEnumerable + +
+ @foreach (var movie in Model) + { +
(@movie.Rank) @movie.Name
+
+
+ @movie.Description +
+ Critics say: + + @Component.Invoke("FeaturedMovies", movie.Name) + +
+ } +
\ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs index 6d8e8d288c..c177c9fa99 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -115,10 +115,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers string result; 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.ContentLink + // for user code. + var entryLink = new EntryLink(); + using (entryLink.FlowContext()) + { + result = await context.GetChildContentAsync(); + } + MemoryCache.Set(key, cacheSetContext => { - UpdateCacheContext(cacheSetContext); + UpdateCacheContext(cacheSetContext, entryLink); return result; }); } @@ -171,7 +178,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } // Internal for unit testing - internal void UpdateCacheContext(ICacheSetContext cacheSetContext) + internal void UpdateCacheContext(ICacheSetContext cacheSetContext, EntryLink entryLink) { if (ExpiresOn != null) { @@ -192,6 +199,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { cacheSetContext.SetPriority(Priority.Value); } + + cacheSetContext.AddEntryLink(entryLink); } private static void AddStringCollectionKey(StringBuilder builder, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs index fc975bae58..14032856f6 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs @@ -286,5 +286,45 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expected2, response3.Trim()); Assert.Equal(expected2, response4.Trim()); } + + [Fact] + public async Task CacheTagHelper_BubblesExpirationOfNestedTagHelpers() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync("/categories/Books?correlationId=1"); + + // Assert - 1 + var expected1 = +@"Category: Books +Products: Book1, Book2 (1)"; + Assert.Equal(expected1, response1.Trim()); + + // Act - 2 + var response2 = await client.GetStringAsync("/categories/Electronics?correlationId=2"); + + // Assert - 2 + var expected2 = +@"Category: Electronics +Products: Book1, Book2 (1)"; + Assert.Equal(expected2, response2.Trim()); + + // Act - 3 + // Trigger an expiration + var response3 = await client.PostAsync("/categories/update-products", new StringContent(string.Empty)); + response3.EnsureSuccessStatusCode(); + + var response4 = await client.GetStringAsync("/categories/Electronics?correlationId=3"); + + // Assert - 3 + var expected3 = +@"Category: Electronics +Products: Laptops (3)"; + Assert.Equal(expected3, response4.Trim()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index 923eaf9446..8b91ecac88 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -7,14 +7,16 @@ using System.IO; using System.Security.Claims; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Routing; using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.Cache.Memory.Infrastructure; +using Microsoft.Framework.Expiration.Interfaces; using Moq; using Xunit; @@ -345,7 +347,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); + var cacheContext = new Mock(MockBehavior.Strict); cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) .Verifiable(); var cacheTagHelper = new CacheTagHelper @@ -355,7 +357,64 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_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 + }; + + var entryLink = new EntryLink(); + entryLink.SetAbsoluteExpiration(expiresOn); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_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, + ExpiresOn = expiresOn1 + }; + + var entryLink = new EntryLink(); + entryLink.SetAbsoluteExpiration(expiresOn2); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); // Assert cacheContext.Verify(); @@ -377,7 +436,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); @@ -399,7 +458,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); @@ -421,12 +480,43 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); } + [Fact] + public void UpdateCacheContext_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, + ExpiresSliding = expiresSliding + }; + + var entryLink = new EntryLink(); + entryLink.AddExpirationTriggers(expected); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + + // Assert + cacheContext.Verify(); + Assert.Equal(expected, triggers); + } + [Fact] public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry() { @@ -604,6 +694,57 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(childContent2, tagHelperOutput2.Content); } + [Fact] + public async Task ProcessAsync_FlowsEntryLinkThatAllowsAddingTriggersToAddedEntry() + { + // Arrange + var id = "some-id"; + var expectedContent = "some-content"; + var tokenSource = new CancellationTokenSource(); + var cache = new MemoryCache(new MemoryCacheOptions()); + var tagHelperContext = new TagHelperContext(new Dictionary(), + id, + () => + { + var entryLink = EntryLinkHelpers.ContextLink; + Assert.NotNull(entryLink); + entryLink.AddExpirationTriggers(new[] + { + new CancellationTokenTrigger(tokenSource.Token) + }); + return Task.FromResult(expectedContent); + }); + var tagHelperOutput = new TagHelperOutput("cache", new Dictionary()) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + }; + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Act - 1 + await cacheTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); + string cachedValue; + var result = cache.TryGetValue(key, out cachedValue); + + // Assert - 1 + Assert.Equal(expectedContent, tagHelperOutput.Content); + Assert.True(result); + Assert.Equal(expectedContent, cachedValue); + + // Act - 2 + tokenSource.Cancel(); + result = cache.TryGetValue(key, out cachedValue); + + // Assert - 2 + Assert.False(result); + Assert.Null(cachedValue); + } + private static ViewContext GetViewContext() { var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); diff --git a/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs new file mode 100644 index 0000000000..10d7e8e32d --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; + +namespace MvcTagHelpersWebSite.Components +{ + public class ProductsViewComponent : ViewComponent + { + [Activate] + public ProductsService ProductsService { get; set; } + + public IViewComponentResult Invoke(string category) + { + IExpirationTrigger trigger; + var products = ProductsService.GetProducts(category, out trigger); + EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger }); + + ViewData["Products"] = products; + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs index 1f83b6e0f4..3fb0549183 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs @@ -8,6 +8,9 @@ namespace MvcTagHelpersWebSite.Controllers { public class Catalog_CacheTagHelperController : Controller { + [Activate] + public ProductsService ProductsService { get; set; } + [HttpGet("/catalog")] public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale) { @@ -61,5 +64,21 @@ namespace MvcTagHelpersWebSite.Controllers ViewData["CorrelationId"] = correlationId; return View(); } + + [HttpGet("/categories/{category}")] + public ViewResult ListCategories(string category, int correlationId) + { + ViewData["Category"] = category; + ViewData["CorrelationId"] = correlationId; + + return View(); + } + + [HttpPost("/categories/update-products")] + public IActionResult UpdateCategories() + { + ProductsService.UpdateProducts(); + return new EmptyResult(); + } } } diff --git a/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs b/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs new file mode 100644 index 0000000000..0de16735f3 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; + +namespace MvcTagHelpersWebSite +{ + public class ProductsService + { + private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); + + public string GetProducts(string category, out IExpirationTrigger trigger) + { + var token = _tokenSource.IsCancellationRequested ? CancellationToken.None : + _tokenSource.Token; + trigger = new CancellationTokenTrigger(token); + if (category == "Books") + { + return "Book1, Book2"; + } + else + { + return "Laptops"; + } + } + + public void UpdateProducts() + { + _tokenSource.Cancel(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Startup.cs b/test/WebSites/MvcTagHelpersWebSite/Startup.cs index 067828ecdf..516d70e552 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Startup.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Startup.cs @@ -16,6 +16,7 @@ namespace MvcTagHelpersWebSite app.UseServices(services => { services.AddMvc(configuration); + services.AddSingleton(); }); app.UseMvc(routes => diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml new file mode 100644 index 0000000000..ff12c2e201 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml @@ -0,0 +1,4 @@ + +Category: @ViewBag.Category +@Component.Invoke("Products", ViewBag.Category) + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml new file mode 100644 index 0000000000..bb28a8aad0 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml @@ -0,0 +1 @@ +Products: @ViewBag.Products (@ViewBag.CorrelationId) \ No newline at end of file