Reacting to IExpirationTrigger -> IChangeToken rename

This commit is contained in:
Pranav K 2015-09-21 18:44:31 -07:00
parent b544aabfdb
commit 9eb2c5b810
23 changed files with 98 additions and 97 deletions

View File

@ -3,8 +3,8 @@
using System.Collections.Generic;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Primitives;
using TagHelperSample.Web.Models;
using TagHelperSample.Web.Services;
@ -25,15 +25,15 @@ namespace TagHelperSample.Web.Components
public IViewComponentResult Invoke()
{
// Since this component is invoked from within a CacheTagHelper,
// cache the movie list and provide an expiration trigger, which when triggered causes the
// cache the movie list and provide an expiration token, which when notified causes the
// CacheTagHelper's cached data to be invalidated.
var cacheKey = "featured_movies";
IEnumerable<FeaturedMovies> movies;
if (!_cache.TryGetValue(cacheKey, out movies))
{
IExpirationTrigger trigger;
movies = _moviesService.GetFeaturedMovies(out trigger);
_cache.Set(cacheKey, movies, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger));
IChangeToken expirationToken;
movies = _moviesService.GetFeaturedMovies(out expirationToken);
_cache.Set(cacheKey, movies, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
}
return View(movies);
@ -44,9 +44,9 @@ namespace TagHelperSample.Web.Components
string quote;
if (!_cache.TryGetValue(movieName, out quote))
{
IExpirationTrigger trigger;
quote = _moviesService.GetCriticsQuote(out trigger);
_cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger));
IChangeToken expirationToken;
quote = _moviesService.GetCriticsQuote(out expirationToken);
_cache.Set(movieName, quote, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
}
return Content(quote);

View File

@ -15,9 +15,9 @@ namespace TagHelperSample.Web.Controllers
_moviesService = moviesService;
}
// Sample exhibiting the use of nested cache tag helpers with custom user expiration triggers.
// Sample exhibiting the use of nested cache tag helpers with custom user expiration tokens.
// 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.
// expiration or due to a user specified expiration token would cause the outer cache tag helper to also expire.
public IActionResult Index()
{
ViewData["Title"] = "Movies";

View File

@ -5,8 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Primitives;
using TagHelperSample.Web.Models;
namespace TagHelperSample.Web.Services
@ -18,11 +17,11 @@ namespace TagHelperSample.Web.Services
private CancellationTokenSource _featuredMoviesTokenSource;
private CancellationTokenSource _quotesTokenSource;
public IEnumerable<FeaturedMovies> GetFeaturedMovies(out IExpirationTrigger expirationTrigger)
public IEnumerable<FeaturedMovies> GetFeaturedMovies(out IChangeToken expirationToken)
{
_featuredMoviesTokenSource = new CancellationTokenSource();
expirationTrigger = new CancellationTokenTrigger(_featuredMoviesTokenSource.Token);
expirationToken = new CancellationChangeToken(_featuredMoviesTokenSource.Token);
return GetMovies().OrderBy(m => m.Rank).Take(2);
}
@ -33,7 +32,7 @@ namespace TagHelperSample.Web.Services
_featuredMoviesTokenSource = null;
}
public string GetCriticsQuote(out IExpirationTrigger trigger)
public string GetCriticsQuote(out IChangeToken expirationToken)
{
_quotesTokenSource = new CancellationTokenSource();
@ -45,7 +44,7 @@ namespace TagHelperSample.Web.Services
"Bravo!"
};
trigger = new CancellationTokenTrigger(_quotesTokenSource.Token);
expirationToken = new CancellationChangeToken(_quotesTokenSource.Token);
return quotes[_random.Next(0, quotes.Length)];
}

View File

@ -43,6 +43,10 @@
"System.Runtime": ""
}
},
"dnxcore50": { }
"dnxcore50": {
"dependencies": {
"System.Text.Encoding": "4.0.11-*"
}
}
}
}

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
// negative results and adding a Watch for that file.
var options = new MemoryCacheEntryOptions()
.AddExpirationTrigger(_fileProvider.Watch(pagePath))
.AddExpirationToken(_fileProvider.Watch(pagePath))
.SetSlidingExpiration(SlidingExpirationDuration);
var file = _fileProvider.GetFileInfo(pagePath);

View File

@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
cacheResult = CompilerCacheResult.FileNotFound;
cacheEntryOptions = new MemoryCacheEntryOptions();
cacheEntryOptions.AddExpirationTrigger(_fileProvider.Watch(normalizedPath));
cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(normalizedPath));
}
else
{
@ -128,12 +128,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
private MemoryCacheEntryOptions GetMemoryCacheEntryOptions(string relativePath)
{
var options = new MemoryCacheEntryOptions();
options.AddExpirationTrigger(_fileProvider.Watch(relativePath));
options.AddExpirationToken(_fileProvider.Watch(relativePath));
var viewImportsPaths = ViewHierarchyUtility.GetViewImportsLocations(relativePath);
foreach (var location in viewImportsPaths)
{
options.AddExpirationTrigger(_fileProvider.Watch(location));
options.AddExpirationToken(_fileProvider.Watch(location));
}
return options;

View File

@ -236,10 +236,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
PrecompilationCacheEntry cacheEntry)
{
var options = new MemoryCacheEntryOptions();
options.AddExpirationTrigger(FileProvider.Watch(fileInfo.RelativePath));
options.AddExpirationToken(FileProvider.Watch(fileInfo.RelativePath));
foreach (var path in ViewHierarchyUtility.GetViewImportsLocations(fileInfo.RelativePath))
{
options.AddExpirationTrigger(FileProvider.Watch(path));
options.AddExpirationToken(FileProvider.Watch(path));
}
return options;
}

View File

@ -144,7 +144,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var key = GenerateKey(context);
if (!MemoryCache.TryGetValue(key, out result))
{
// Create an entry link scope and flow it so that any triggers related to the cache entries
// Create an entry link scope and flow it so that any tokens related to the cache entries
// created within this scope get copied to this scope.
using (var link = MemoryCache.CreateLinkingScope())
{

View File

@ -97,10 +97,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
if (!_cache.TryGetValue(path, out value))
{
value = QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo));
_cache.Set(
path,
value,
new MemoryCacheEntryOptions().AddExpirationTrigger(_fileProvider.Watch(resolvedPath)));
var cacheEntryOptions = new MemoryCacheEntryOptions().AddExpirationToken(_fileProvider.Watch(resolvedPath));
_cache.Set(path, value, cacheEntryOptions);
}
return value;

View File

@ -115,8 +115,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
var options = new MemoryCacheEntryOptions();
foreach (var pattern in includePatterns)
{
var trigger = FileProvider.Watch(pattern);
options.AddExpirationTrigger(trigger);
var changeToken = FileProvider.Watch(pattern);
options.AddExpirationToken(changeToken);
}
files = FindFiles(includePatterns, excludePatterns);

View File

@ -72,7 +72,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Host.Directives
Assert.Same(expected1, result1);
// Act 2
fileProvider.GetTrigger(path).IsExpired = true;
fileProvider.GetChangeToken(path).HasChanged = true;
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => expected2);
// Assert 2
@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Host.Directives
// Act 2
fileProvider.DeleteFile(path);
fileProvider.GetTrigger(path).IsExpired = true;
fileProvider.GetChangeToken(path).HasChanged = true;
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => { throw new Exception("Shouldn't be called."); });
// Assert 2
@ -121,7 +121,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Host.Directives
// Act 2
fileProvider.AddFile(path, "test content");
fileProvider.GetTrigger(path).IsExpired = true;
fileProvider.GetChangeToken(path).HasChanged = true;
var result2 = chunkTreeCache.GetOrAdd(path, fileInfo => expected);
// Assert 2

View File

@ -111,9 +111,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
Assert.Same(expected, result1.CompilationResult);
// Act 2
// Delete the file from the file system and set it's expiration trigger.
// Delete the file from the file system and set it's expiration token.
fileProvider.DeleteFile(ViewPath);
fileProvider.GetTrigger(ViewPath).IsExpired = true;
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);
// Assert 2
@ -147,7 +147,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
// Act 3
fileProvider.GetTrigger(ViewPath).IsExpired = true;
fileProvider.GetChangeToken(ViewPath).HasChanged = true;
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
// Assert 3
@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType);
// Act 3
fileProvider.GetTrigger(globalImportPath).IsExpired = true;
fileProvider.GetChangeToken(globalImportPath).HasChanged = true;
var result3 = cache.GetOrAdd(ViewPath, _ => expected2);
// Assert 2
@ -242,7 +242,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Act
fileProvider.Watch(PrecompiledViewsPath);
fileProvider.GetTrigger(PrecompiledViewsPath).IsExpired = true;
fileProvider.GetChangeToken(PrecompiledViewsPath).HasChanged = true;
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert
@ -260,7 +260,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
// Act
fileProvider.Watch(globalImportPath);
fileProvider.GetTrigger(globalImportPath).IsExpired = true;
fileProvider.GetChangeToken(globalImportPath).HasChanged = true;
var result = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);
// Assert

View File

@ -18,9 +18,9 @@ using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Primitives;
using Moq;
using Xunit;
@ -536,7 +536,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
// Arrange
var expiresSliding = TimeSpan.FromSeconds(30);
var expected = new[] { Mock.Of<IExpirationTrigger>(), Mock.Of<IExpirationTrigger>() };
var expected = new[] { Mock.Of<IChangeToken>(), Mock.Of<IChangeToken>() };
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheTagHelper = new CacheTagHelper(cache)
{
@ -544,13 +544,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
};
var entryLink = new EntryLink();
entryLink.AddExpirationTriggers(expected);
entryLink.AddExpirationTokens(expected);
// Act
var cacheEntryOptions = cacheTagHelper.GetMemoryCacheEntryOptions(entryLink);
// Assert
Assert.Equal(expected, cacheEntryOptions.Triggers.ToArray());
Assert.Equal(expected, cacheEntryOptions.ExpirationTokens.ToArray());
}
[Fact]
@ -722,7 +722,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var tokenSource = new CancellationTokenSource();
var cache = new MemoryCache(new MemoryCacheOptions());
var cacheEntryOptions = new MemoryCacheEntryOptions()
.AddExpirationTrigger(new CancellationTokenTrigger(tokenSource.Token));
.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));
var tagHelperContext = new TagHelperContext(
allAttributes: new TagHelperAttributeList(),
items: new Dictionary<object, object>(),

View File

@ -17,8 +17,8 @@ using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Primitives;
using Microsoft.Framework.WebEncoders.Testing;
using Moq;
using Xunit;
@ -304,7 +304,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
mockFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>()))
.Returns(mockFile.Object);
mockFileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileTrigger());
.Returns(new TestFileChangeToken());
var hostingEnvironment = new Mock<IHostingEnvironment>();
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);

View File

@ -7,8 +7,8 @@ using System.Text;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Primitives;
using Moq;
using Xunit;
@ -54,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
mockFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>()))
.Returns(mockFile.Object);
mockFileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileTrigger());
.Returns(new TestFileChangeToken());
var hostingEnvironment = new Mock<IHostingEnvironment>();
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);
@ -137,10 +137,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public void SetsValueInCache(string filePath, string watchPath, string requestPathBase)
{
// Arrange
var trigger = new Mock<IExpirationTrigger>();
var changeToken = new Mock<IChangeToken>();
var hostingEnvironment = GetMockHostingEnvironment(filePath, requestPathBase != null);
Mock.Get(hostingEnvironment.WebRootFileProvider)
.Setup(f => f.Watch(watchPath)).Returns(trigger.Object);
.Setup(f => f.Watch(watchPath)).Returns(changeToken.Object);
object cacheValue = null;
var cache = new Mock<IMemoryCache>();
@ -197,7 +197,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
}
mockFileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileTrigger());
.Returns(new TestFileChangeToken());
var hostingEnvironment = new Mock<IHostingEnvironment>();
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);

View File

@ -6,10 +6,10 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.FileSystemGlobbing;
using Microsoft.Framework.FileSystemGlobbing.Abstractions;
using Microsoft.Framework.Primitives;
using Moq;
using Xunit;
@ -272,9 +272,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers.Internal
public void CachesMatchResults()
{
// Arrange
var trigger = new Mock<IExpirationTrigger>();
var changeToken = new Mock<IChangeToken>();
var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css"));
Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny<string>())).Returns(trigger.Object);
Mock.Get(fileProvider).Setup(f => f.Watch(It.IsAny<string>())).Returns(changeToken.Object);
var cache = MakeCache();
Mock.Get(cache).Setup(c => c.Set(
/*key*/ It.IsAny<string>(),

View File

@ -19,9 +19,9 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Logging;
using Microsoft.Framework.Primitives;
using Microsoft.Framework.WebEncoders.Testing;
using Moq;
using Xunit;
@ -871,7 +871,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
mockFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>()))
.Returns(mockFile.Object);
mockFileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileTrigger());
.Returns(new TestFileChangeToken());
var hostingEnvironment = new Mock<IHostingEnvironment>();
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);

View File

@ -19,9 +19,9 @@ using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Logging;
using Microsoft.Framework.Primitives;
using Microsoft.Framework.WebEncoders.Testing;
using Moq;
using Xunit;
@ -964,7 +964,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
mockFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>()))
.Returns(mockFile.Object);
mockFileProvider.Setup(fp => fp.Watch(It.IsAny<string>()))
.Returns(new TestFileTrigger());
.Returns(new TestFileChangeToken());
var hostingEnvironment = new Mock<IHostingEnvironment>();
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);
@ -986,7 +986,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(result != null);
var cacheEntryOptions = new MemoryCacheEntryOptions();
cacheEntryOptions.AddExpirationTrigger(new Mock<IExpirationTrigger>().Object);
cacheEntryOptions.AddExpirationToken(Mock.Of<IChangeToken>());
cache
.Setup(
c => c.Set(

View File

@ -0,0 +1,19 @@
// 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;
namespace Microsoft.Framework.Primitives
{
internal class TestFileChangeToken : IChangeToken
{
public bool ActiveChangeCallbacks => false;
public bool HasChanged { get; set; }
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
throw new NotImplementedException();
}
}
}

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNet.FileProviders;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Primitives;
namespace Microsoft.AspNet.Mvc.Razor
{
@ -13,8 +13,8 @@ namespace Microsoft.AspNet.Mvc.Razor
{
private readonly Dictionary<string, IFileInfo> _lookup =
new Dictionary<string, IFileInfo>(StringComparer.Ordinal);
private readonly Dictionary<string, TestFileTrigger> _fileTriggers =
new Dictionary<string, TestFileTrigger>(StringComparer.Ordinal);
private readonly Dictionary<string, TestFileChangeToken> _fileTriggers =
new Dictionary<string, TestFileChangeToken>(StringComparer.Ordinal);
public virtual IDirectoryContents GetDirectoryContents(string subpath)
{
@ -58,19 +58,19 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
public virtual IExpirationTrigger Watch(string filter)
public virtual IChangeToken Watch(string filter)
{
TestFileTrigger trigger;
if (!_fileTriggers.TryGetValue(filter, out trigger) || trigger.IsExpired)
TestFileChangeToken changeToken;
if (!_fileTriggers.TryGetValue(filter, out changeToken) || changeToken.HasChanged)
{
trigger = new TestFileTrigger();
_fileTriggers[filter] = trigger;
changeToken = new TestFileChangeToken();
_fileTriggers[filter] = changeToken;
}
return trigger;
return changeToken;
}
public TestFileTrigger GetTrigger(string filter)
public TestFileChangeToken GetChangeToken(string filter)
{
return _fileTriggers[filter];
}

View File

@ -1,19 +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 System;
namespace Microsoft.Framework.Caching
{
internal class TestFileTrigger : IExpirationTrigger
{
public bool ActiveExpirationCallbacks { get; } = false;
public bool IsExpired { get; set; }
public IDisposable RegisterExpirationCallback(Action<object> callback, object state)
{
throw new NotImplementedException();
}
}
}

View File

@ -2,8 +2,8 @@
// 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.Caching;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.Primitives;
namespace HtmlGenerationWebSite.Components
{
@ -24,9 +24,9 @@ namespace HtmlGenerationWebSite.Components
string products;
if (!Cache.TryGetValue(category, out products))
{
IExpirationTrigger trigger;
products = ProductsService.GetProducts(category, out trigger);
Cache.Set(category, products, new MemoryCacheEntryOptions().AddExpirationTrigger(trigger));
IChangeToken changeToken;
products = ProductsService.GetProducts(category, out changeToken);
Cache.Set(category, products, new MemoryCacheEntryOptions().AddExpirationToken(changeToken));
}
ViewData["Products"] = products;

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.Framework.Caching;
using Microsoft.Framework.Primitives;
namespace HtmlGenerationWebSite
{
@ -10,11 +10,11 @@ namespace HtmlGenerationWebSite
{
private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
public string GetProducts(string category, out IExpirationTrigger trigger)
public string GetProducts(string category, out IChangeToken changeToken)
{
var token = _tokenSource.IsCancellationRequested ? CancellationToken.None :
_tokenSource.Token;
trigger = new CancellationTokenTrigger(token);
var token = _tokenSource.IsCancellationRequested ?
CancellationToken.None : _tokenSource.Token;
changeToken = new CancellationChangeToken(token);
if (category == "Books")
{
return "Book1, Book2";