diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs index fa7997c0cf..1e53fed94e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Testing; using Xunit; @@ -445,17 +446,19 @@ Products: Book1, Book2 (1)"; Assert.Equal(expected2, response2.Trim(), ignoreLineEndingDifferences: true); // Act - 3 - // Trigger an expiration - var response3 = await Client.PostAsync("/categories/update-products", new StringContent(string.Empty)); - response3.EnsureSuccessStatusCode(); + // Trigger an expiration of the nested content. + var content = @"[{ productName: ""Music Systems"" },{ productName: ""Televisions"" }]"; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/categories/Electronics"); + requestMessage.Content = new StringContent(content, Encoding.UTF8, "application/json"); + (await Client.SendAsync(requestMessage)).EnsureSuccessStatusCode(); - var response4 = await Client.GetStringAsync("/categories/Electronics?correlationId=3"); + var response3 = await Client.GetStringAsync("/categories/Electronics?correlationId=3"); // Assert - 3 var expected3 = @"Category: Electronics -Products: Laptops (3)"; - Assert.Equal(expected3, response4.Trim(), ignoreLineEndingDifferences: true); +Products: Music Systems, Televisions (3)"; + Assert.Equal(expected3, response3.Trim(), ignoreLineEndingDifferences: true); } [Fact] diff --git a/test/WebSites/HtmlGenerationWebSite/Components/ProductsViewComponent.cs b/test/WebSites/HtmlGenerationWebSite/Components/ProductsViewComponent.cs deleted file mode 100644 index a27109def0..0000000000 --- a/test/WebSites/HtmlGenerationWebSite/Components/ProductsViewComponent.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 Microsoft.AspNet.Mvc; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Primitives; - -namespace HtmlGenerationWebSite.Components -{ - public class ProductsViewComponent : ViewComponent - { - public ProductsViewComponent(ProductsService productsService, IMemoryCache cache) - { - ProductsService = productsService; - Cache = cache; - } - - private ProductsService ProductsService { get; } - - public IMemoryCache Cache { get; } - - public IViewComponentResult Invoke(string category) - { - string products; - if (!Cache.TryGetValue(category, out products)) - { - IChangeToken changeToken; - products = ProductsService.GetProducts(category, out changeToken); - Cache.Set(category, products, new MemoryCacheEntryOptions().AddExpirationToken(changeToken)); - } - - ViewData["Products"] = products; - return View(); - } - } -} \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs b/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs index cabce61c4d..2edeccc7a9 100644 --- a/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs +++ b/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs @@ -1,7 +1,9 @@ // 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.Security.Claims; +using HtmlGenerationWebSite.Models; using Microsoft.AspNet.Mvc; namespace HtmlGenerationWebSite.Controllers @@ -71,10 +73,14 @@ namespace HtmlGenerationWebSite.Controllers return View(); } - [HttpPost("/categories/update-products")] - public void UpdateCategories([FromServices] ProductsService productsService) + [HttpPost("/categories/{category}")] + public IActionResult UpdateProducts( + [FromServices] ProductsService productService, + string category, + [FromBody] List products) { - productsService.UpdateProducts(); + productService.UpdateProducts(category, products); + return Ok(); } [HttpGet("/catalog/GetDealPercentage/{dealPercentage}")] diff --git a/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs b/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs new file mode 100644 index 0000000000..370bc89ca0 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs @@ -0,0 +1,14 @@ +// 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 Microsoft.Extensions.Primitives; + +namespace HtmlGenerationWebSite +{ + public interface ISignalTokenProviderService + { + IChangeToken GetToken(object key); + + void SignalToken(object key); + } +} \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/ProductsService.cs b/test/WebSites/HtmlGenerationWebSite/ProductsService.cs index c21c331938..c2ce3d4586 100644 --- a/test/WebSites/HtmlGenerationWebSite/ProductsService.cs +++ b/test/WebSites/HtmlGenerationWebSite/ProductsService.cs @@ -1,33 +1,59 @@ // 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.Threading; -using Microsoft.Extensions.Primitives; +using System.Collections.Generic; +using System.Linq; +using HtmlGenerationWebSite.Models; +using Microsoft.Extensions.Caching.Memory; namespace HtmlGenerationWebSite { public class ProductsService { - private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); - - public string GetProducts(string category, out IChangeToken changeToken) + private readonly IMemoryCache _memoryCache; + private readonly ISignalTokenProviderService _tokenProviderService; + private readonly Dictionary _products = new Dictionary { - var token = _tokenSource.IsCancellationRequested ? - CancellationToken.None : _tokenSource.Token; - changeToken = new CancellationChangeToken(token); - if (category == "Books") + ["Books"] = new[] { - return "Book1, Book2"; - } - else + new Product { ProductName = "Book1" }, + new Product { ProductName = "Book2" } + }, + ["Electronics"] = new[] { - return "Laptops"; + new Product { ProductName = "Laptops" } } + }; + + public ProductsService( + IMemoryCache memoryCache, + ISignalTokenProviderService tokenProviderService) + { + _memoryCache = memoryCache; + _tokenProviderService = tokenProviderService; } - public void UpdateProducts() + public IEnumerable GetProductNames(string category) { - _tokenSource.Cancel(); + IEnumerable products; + var key = typeof(ProductsService).FullName; + if (!_memoryCache.TryGetValue(key, out products)) + { + var changeToken = _tokenProviderService.GetToken(key); + products = _memoryCache.Set>( + key, + _products[category], + new MemoryCacheEntryOptions().AddExpirationToken(changeToken)); + } + + return products.Select(p => p.ProductName); + } + + public void UpdateProducts(string category, IEnumerable products) + { + _products[category] = products.ToArray(); + var key = typeof(ProductsService).FullName; + _tokenProviderService.SignalToken(key); } } } \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs b/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs new file mode 100644 index 0000000000..d45e291182 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs @@ -0,0 +1,49 @@ +// 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.Concurrent; +using System.Threading; +using Microsoft.Extensions.Primitives; + +namespace HtmlGenerationWebSite +{ + public class SignalTokenProviderService : ISignalTokenProviderService + { + private readonly ConcurrentDictionary _changeTokens + = new ConcurrentDictionary(); + + public IChangeToken GetToken(object key) + { + return _changeTokens.GetOrAdd( + key, + _ => + { + var cancellationTokenSource = new CancellationTokenSource(); + var changeToken = new CancellationChangeToken(cancellationTokenSource.Token); + return new ChangeTokenInfo(changeToken, cancellationTokenSource); + }).ChangeToken; + } + + public void SignalToken(object key) + { + ChangeTokenInfo changeTokenInfo; + if (_changeTokens.TryRemove(key, out changeTokenInfo)) + { + changeTokenInfo.TokenSource.Cancel(); + } + } + + private class ChangeTokenInfo + { + public ChangeTokenInfo(IChangeToken changeToken, CancellationTokenSource tokenSource) + { + ChangeToken = changeToken; + TokenSource = tokenSource; + } + + public IChangeToken ChangeToken { get; } + + public CancellationTokenSource TokenSource { get; } + } + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/Startup.cs b/test/WebSites/HtmlGenerationWebSite/Startup.cs index e3d1ae34ea..79089e3bf6 100644 --- a/test/WebSites/HtmlGenerationWebSite/Startup.cs +++ b/test/WebSites/HtmlGenerationWebSite/Startup.cs @@ -16,6 +16,7 @@ namespace HtmlGenerationWebSite // null which is interpreted as true unless element includes an action attribute. services.AddMvc().InitializeTagHelper((helper, _) => helper.Antiforgery = false); + services.AddSingleton(typeof(ISignalTokenProviderService<>), typeof(SignalTokenProviderService<>)); services.AddSingleton(); } diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml index ff12c2e201..8d58ee5641 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml @@ -1,4 +1,5 @@ - +@inject HtmlGenerationWebSite.ProductsService ProductsService + Category: @ViewBag.Category -@Component.Invoke("Products", ViewBag.Category) +Products: @string.Join(", ", ProductsService.GetProductNames(ViewBag.Category)) (@ViewBag.CorrelationId) \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Products/Default.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Products/Default.cshtml deleted file mode 100644 index bb28a8aad0..0000000000 --- a/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Products/Default.cshtml +++ /dev/null @@ -1 +0,0 @@ -Products: @ViewBag.Products (@ViewBag.CorrelationId) \ No newline at end of file