Merge branch 'release' into dev
This commit is contained in:
commit
4d77f670f6
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<FeaturedMovies> 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<FeaturedMovies> 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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IAssemblyProvider, TestAssemblyProvider<Startup>>();
|
||||
services.AddSingleton<MoviesService>();
|
||||
|
||||
services.Configure<MvcOptions>(options =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>@ViewBag.Title</title>
|
||||
<style>
|
||||
body {
|
||||
width: 1040px;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 800px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<h3>Watch the greatest movies right here!</h3>
|
||||
Submit your movie rankings:
|
||||
|
||||
<form asp-anti-forgery="false" asp-action="UpdateMovieRatings">
|
||||
Movies + ratings go here
|
||||
<button type="submit">Update ratings</button>
|
||||
</form>
|
||||
|
||||
<form asp-anti-forgery="false" asp-action="UpdateCriticsQuotes">
|
||||
Movies + ratings go here
|
||||
<button type="submit">Update quotes</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<cache expires-after="TimeSpan.FromMinutes(20)">
|
||||
@await Component.InvokeAsync("FeaturedMovies")
|
||||
</cache>
|
||||
</div>
|
||||
<div style="clear: left"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
@model IEnumerable<TagHelperSample.Web.Models.FeaturedMovies>
|
||||
|
||||
<dl>
|
||||
@foreach (var movie in Model)
|
||||
{
|
||||
<dt>(@movie.Rank) @movie.Name</dt>
|
||||
<dd>
|
||||
<div>
|
||||
@movie.Description
|
||||
</div>
|
||||
<em>Critics say:</em>
|
||||
<cache vary-by="@movie.Name">
|
||||
@Component.Invoke("FeaturedMovies", movie.Name)
|
||||
</cache>
|
||||
</dd>
|
||||
}
|
||||
</dl>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -289,5 +289,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ICacheSetContext>();
|
||||
var cacheContext = new Mock<ICacheSetContext>(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<ICacheSetContext>(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<ICacheSetContext>();
|
||||
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<IExpirationTrigger>(), Mock.Of<IExpirationTrigger>() };
|
||||
var triggers = new List<IExpirationTrigger>();
|
||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||
var cacheContext = new Mock<ICacheSetContext>();
|
||||
cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding))
|
||||
.Verifiable();
|
||||
cacheContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()))
|
||||
.Callback<IExpirationTrigger>(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<string, object>(),
|
||||
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<string, string>())
|
||||
{
|
||||
PreContent = "<cache>",
|
||||
PostContent = "</cache>"
|
||||
};
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ namespace MvcTagHelpersWebSite
|
|||
app.UseServices(services =>
|
||||
{
|
||||
services.AddMvc(configuration);
|
||||
services.AddSingleton<ProductsService>();
|
||||
});
|
||||
|
||||
app.UseMvc(routes =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<cache vary-by-route="category">
|
||||
Category: @ViewBag.Category
|
||||
<cache>@Component.Invoke("Products", ViewBag.Category)</cache>
|
||||
</cache>
|
||||
|
|
@ -0,0 +1 @@
|
|||
Products: @ViewBag.Products (@ViewBag.CorrelationId)
|
||||
Loading…
Reference in New Issue