Tweak CacheTagHelper sample to use a token provider service for

cache expiration.

Fixes #3573
This commit is contained in:
Pranav K 2015-12-09 18:01:38 -08:00
parent 21e815cb36
commit 56c6975a21
9 changed files with 126 additions and 63 deletions

View File

@ -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]

View File

@ -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();
}
}
}

View File

@ -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<Product> products)
{
productsService.UpdateProducts();
productService.UpdateProducts(category, products);
return Ok();
}
[HttpGet("/catalog/GetDealPercentage/{dealPercentage}")]

View File

@ -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<TKey>
{
IChangeToken GetToken(object key);
void SignalToken(object key);
}
}

View File

@ -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<Product> _tokenProviderService;
private readonly Dictionary<string, Product[]> _products = new Dictionary<string, Product[]>
{
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<Product> tokenProviderService)
{
_memoryCache = memoryCache;
_tokenProviderService = tokenProviderService;
}
public void UpdateProducts()
public IEnumerable<string> GetProductNames(string category)
{
_tokenSource.Cancel();
IEnumerable<Product> products;
var key = typeof(ProductsService).FullName;
if (!_memoryCache.TryGetValue(key, out products))
{
var changeToken = _tokenProviderService.GetToken(key);
products = _memoryCache.Set<IEnumerable<Product>>(
key,
_products[category],
new MemoryCacheEntryOptions().AddExpirationToken(changeToken));
}
return products.Select(p => p.ProductName);
}
public void UpdateProducts(string category, IEnumerable<Product> products)
{
_products[category] = products.ToArray();
var key = typeof(ProductsService).FullName;
_tokenProviderService.SignalToken(key);
}
}
}

View File

@ -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<TKey> : ISignalTokenProviderService<TKey>
{
private readonly ConcurrentDictionary<object, ChangeTokenInfo> _changeTokens
= new ConcurrentDictionary<object, ChangeTokenInfo>();
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; }
}
}
}

View File

@ -16,6 +16,7 @@ namespace HtmlGenerationWebSite
// null which is interpreted as true unless element includes an action attribute.
services.AddMvc().InitializeTagHelper<FormTagHelper>((helper, _) => helper.Antiforgery = false);
services.AddSingleton(typeof(ISignalTokenProviderService<>), typeof(SignalTokenProviderService<>));
services.AddSingleton<ProductsService>();
}

View File

@ -1,4 +1,5 @@
<cache vary-by-route="category">
@inject HtmlGenerationWebSite.ProductsService ProductsService
<cache vary-by-route="category">
Category: @ViewBag.Category
<cache>@Component.Invoke("Products", ViewBag.Category)</cache>
<cache>Products: @string.Join(", ", ProductsService.GetProductNames(ViewBag.Category)) (@ViewBag.CorrelationId)</cache>
</cache>

View File

@ -1 +0,0 @@
Products: @ViewBag.Products (@ViewBag.CorrelationId)