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.Builder;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using TagHelperSample.Web.Services;
|
||||||
|
|
||||||
namespace TagHelperSample.Web
|
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
|
// 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.
|
// prevents loading controllers from other assemblies when the sample is used in functional tests.
|
||||||
services.AddTransient<IAssemblyProvider, TestAssemblyProvider<Startup>>();
|
services.AddTransient<IAssemblyProvider, TestAssemblyProvider<Startup>>();
|
||||||
|
services.AddSingleton<MoviesService>();
|
||||||
|
|
||||||
services.Configure<MvcOptions>(options =>
|
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;
|
string result;
|
||||||
if (!MemoryCache.TryGetValue(key, out 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 =>
|
MemoryCache.Set(key, cacheSetContext =>
|
||||||
{
|
{
|
||||||
UpdateCacheContext(cacheSetContext);
|
UpdateCacheContext(cacheSetContext, entryLink);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +178,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for unit testing
|
// Internal for unit testing
|
||||||
internal void UpdateCacheContext(ICacheSetContext cacheSetContext)
|
internal void UpdateCacheContext(ICacheSetContext cacheSetContext, EntryLink entryLink)
|
||||||
{
|
{
|
||||||
if (ExpiresOn != null)
|
if (ExpiresOn != null)
|
||||||
{
|
{
|
||||||
|
|
@ -192,6 +199,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
cacheSetContext.SetPriority(Priority.Value);
|
cacheSetContext.SetPriority(Priority.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheSetContext.AddEntryLink(entryLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddStringCollectionKey(StringBuilder builder,
|
private static void AddStringCollectionKey(StringBuilder builder,
|
||||||
|
|
|
||||||
|
|
@ -289,5 +289,45 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expected2, response3.Trim());
|
Assert.Equal(expected2, response3.Trim());
|
||||||
Assert.Equal(expected2, response4.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.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http.Core;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Http.Core;
|
|
||||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.Cache.Memory;
|
using Microsoft.Framework.Cache.Memory;
|
||||||
using Microsoft.Framework.Cache.Memory.Infrastructure;
|
using Microsoft.Framework.Cache.Memory.Infrastructure;
|
||||||
|
using Microsoft.Framework.Expiration.Interfaces;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -345,7 +347,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
// Arrange
|
// Arrange
|
||||||
var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4);
|
var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4);
|
||||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var cacheContext = new Mock<ICacheSetContext>();
|
var cacheContext = new Mock<ICacheSetContext>(MockBehavior.Strict);
|
||||||
cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn))
|
cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
var cacheTagHelper = new CacheTagHelper
|
var cacheTagHelper = new CacheTagHelper
|
||||||
|
|
@ -355,7 +357,64 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// 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
|
// Assert
|
||||||
cacheContext.Verify();
|
cacheContext.Verify();
|
||||||
|
|
@ -377,7 +436,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
cacheTagHelper.UpdateCacheContext(cacheContext.Object);
|
cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
cacheContext.Verify();
|
cacheContext.Verify();
|
||||||
|
|
@ -399,7 +458,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
cacheTagHelper.UpdateCacheContext(cacheContext.Object);
|
cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
cacheContext.Verify();
|
cacheContext.Verify();
|
||||||
|
|
@ -421,12 +480,43 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
cacheTagHelper.UpdateCacheContext(cacheContext.Object);
|
cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
cacheContext.Verify();
|
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]
|
[Fact]
|
||||||
public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry()
|
public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry()
|
||||||
{
|
{
|
||||||
|
|
@ -604,6 +694,57 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
Assert.Equal(childContent2, tagHelperOutput2.Content);
|
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()
|
private static ViewContext GetViewContext()
|
||||||
{
|
{
|
||||||
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
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
|
public class Catalog_CacheTagHelperController : Controller
|
||||||
{
|
{
|
||||||
|
[Activate]
|
||||||
|
public ProductsService ProductsService { get; set; }
|
||||||
|
|
||||||
[HttpGet("/catalog")]
|
[HttpGet("/catalog")]
|
||||||
public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale)
|
public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale)
|
||||||
{
|
{
|
||||||
|
|
@ -61,5 +64,21 @@ namespace MvcTagHelpersWebSite.Controllers
|
||||||
ViewData["CorrelationId"] = correlationId;
|
ViewData["CorrelationId"] = correlationId;
|
||||||
return View();
|
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 =>
|
app.UseServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMvc(configuration);
|
services.AddMvc(configuration);
|
||||||
|
services.AddSingleton<ProductsService>();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseMvc(routes =>
|
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