/// <inheritdoc /> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } IHtmlContent content = null; // Create a cancellation token that will be used // to release the task from the memory cache. var tokenSource = new CancellationTokenSource(); if (Enabled) { var cacheKey = new CacheTagKey(this); content = await _distributedCacheService.ProcessContentAsync(output, cacheKey, GetDistributedCacheEntryOptions()); } else { content = await output.GetChildContentAsync(); } // Clear the contents of the "cache" element since we don't want to render it. output.SuppressOutput(); output.Content.SetHtmlContent(content); }
public void GenerateKey_UsesVaryByPropertyToGenerateKey(string varyBy) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryBy = varyBy }; var expected = "CacheTagHelper||testid||VaryBy||" + varyBy; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesCultureAndUICultureName_IfVaryByCulture_IsSet() { // Arrange var expected = "CacheTagHelper||testid||VaryByCulture||fr-FR||es-ES"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByCulture = true }; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_ReturnsKeyBasedOnTagHelperUniqueId() { // Arrange var id = Guid.NewGuid().ToString(); var tagHelperContext = GetTagHelperContext(id); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var expected = "CacheTagHelper||" + id; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByUser_WhenUserIsNotAuthenticated() { // Arrange var expected = "CacheTagHelper||testid||VaryByUser||"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByUser = true }; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByCookieName(string varyByCookie, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByCookie = varyByCookie }; cacheTagHelper.ViewContext.HttpContext.Request.Headers["Cookie"] = "Cookie0=Cookie0Value;Cookie1=Cookie1Value"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByQuery(string varyByQuery, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByQuery = varyByQuery }; cacheTagHelper.ViewContext.HttpContext.Request.QueryString = new QueryString("?sortoption=Adorability&Category=cats&sortOrder="); // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByRoute(string varyByRoute, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByRoute = varyByRoute }; cacheTagHelper.ViewContext.RouteData.Values["id"] = 4; cacheTagHelper.ViewContext.RouteData.Values["category"] = "MyCategory"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_ReturnsKeyBasedOnTagHelperName() { // Arrange var name = "some-name"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new DistributedCacheTagHelper( Mock.Of <IDistributedCacheTagHelperService>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), Name = name }; var expected = "DistributedCacheTagHelper||" + name; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByUserAndAuthenticatedUserName() { // Arrange var expected = "CacheTagHelper||testid||VaryByUser||test_name"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByUser = true }; var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "test_name") }); cacheTagHelper.ViewContext.HttpContext.User = new ClaimsPrincipal(identity); // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void Equals_ReturnsFalseOnDifferentKey() { // Arrange var tagHelperContext1 = GetTagHelperContext("some-id"); var cacheTagHelper1 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var tagHelperContext2 = GetTagHelperContext("some-other-id"); var cacheTagHelper2 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; // Act var cacheTagKey1 = new CacheTagKey(cacheTagHelper1, tagHelperContext1); var cacheTagKey2 = new CacheTagKey(cacheTagHelper2, tagHelperContext2); // Assert Assert.NotEqual(cacheTagKey1, cacheTagKey2); }
public void Equals_ReturnsFalseOnDifferentKey() { // Arrange var tagHelperContext1 = GetTagHelperContext("some-id"); var cacheTagHelper1 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var tagHelperContext2 = GetTagHelperContext("some-other-id"); var cacheTagHelper2 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; // Act var cacheTagKey1 = new CacheTagKey(cacheTagHelper1, tagHelperContext1); var cacheTagKey2 = new CacheTagKey(cacheTagHelper2, tagHelperContext2); // Assert Assert.NotEqual(cacheTagKey1, cacheTagKey2); }
public void GenerateKey_UsesVaryByHeader(string varyByHeader, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByHeader = varyByHeader }; var headers = cacheTagHelper.ViewContext.HttpContext.Request.Headers; headers["Accept-Language"] = "en-us;charset=utf8"; headers["Accept-Encoding"] = "utf8"; headers["X-CustomHeader"] = "Header-Value"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByRoute_UsesInvariantCulture() { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper( new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByRoute = "Category", }; cacheTagHelper.ViewContext.RouteData.Values["id"] = 4; cacheTagHelper.ViewContext.RouteData.Values["category"] = new DateTimeOffset(2018, 10, 31, 7, 37, 38, TimeSpan.FromHours(-7)); var expected = "CacheTagHelper||testid||VaryByRoute(Category||10/31/2018 07:37:38 -07:00)"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void Equals_ReturnsTrueOnSameKey() { // Arrange var id = Guid.NewGuid().ToString(); var tagHelperContext1 = GetTagHelperContext(id); var cacheTagHelper1 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var tagHelperContext2 = GetTagHelperContext(id); var cacheTagHelper2 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; // Act var cacheTagKey1 = new CacheTagKey(cacheTagHelper1, tagHelperContext1); var cacheTagKey2 = new CacheTagKey(cacheTagHelper2, tagHelperContext2); // Assert Assert.Equal(cacheTagKey1, cacheTagKey2); }
public void Equals_ReturnsTrueOnSameKey() { // Arrange var id = Guid.NewGuid().ToString(); var tagHelperContext1 = GetTagHelperContext(id); var cacheTagHelper1 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var tagHelperContext2 = GetTagHelperContext(id); var cacheTagHelper2 = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; // Act var cacheTagKey1 = new CacheTagKey(cacheTagHelper1, tagHelperContext1); var cacheTagKey2 = new CacheTagKey(cacheTagHelper2, tagHelperContext2); // Assert Assert.Equal(cacheTagKey1, cacheTagKey2); }
public void GenerateKey_WithVaryByCulture_ComposesWithOtherOptions() { // Arrange var expected = "CacheTagHelper||testid||VaryBy||custom-value||" + "VaryByHeader(content-type||text/html)||VaryByCulture||zh||zh-Hans"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of <IMemoryCache>()), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByCulture = true, VaryByHeader = "content-type", VaryBy = "custom-value" }; cacheTagHelper.ViewContext.HttpContext.Request.Headers["Content-Type"] = "text/html"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
/// <inheritdoc /> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } IHtmlContent content; if (Enabled) { var cacheKey = new CacheTagKey(this, context); if (MemoryCache.TryGetValue(cacheKey, out Task <IHtmlContent> cachedResult)) { // There is either some value already cached (as a Task) or a worker processing the output. content = await cachedResult; } else { content = await CreateCacheEntry(cacheKey, output); } } else { content = await output.GetChildContentAsync(); } // Clear the contents of the "cache" element since we don't want to render it. output.SuppressOutput(); output.Content.SetHtmlContent(content); }
/// <inheritdoc /> public async Task <IHtmlContent> ProcessContentAsync(TagHelperOutput output, CacheTagKey key, DistributedCacheEntryOptions options) { IHtmlContent content = null; while (content == null) { // Is there any request already processing the value? if (!_workers.TryGetValue(key, out var result)) { // There is a small race condition here between TryGetValue and TryAdd that might cause the // content to be computed more than once. We don't care about this race as the probability of // happening is very small and the impact is not critical. var tcs = new TaskCompletionSource <IHtmlContent>(creationOptions: TaskCreationOptions.RunContinuationsAsynchronously); _workers.TryAdd(key, tcs.Task); try { var serializedKey = Encoding.UTF8.GetBytes(key.GenerateKey()); var storageKey = key.GenerateHashedKey(); var value = await _storage.GetAsync(storageKey); if (value == null) { // The value is not cached, we need to render the tag helper output var processedContent = await output.GetChildContentAsync(); var stringBuilder = new StringBuilder(); using (var writer = new StringWriter(stringBuilder)) { processedContent.WriteTo(writer, _htmlEncoder); } var formattingContext = new DistributedCacheTagHelperFormattingContext { Html = new HtmlString(stringBuilder.ToString()) }; // Then cache the result value = await _formatter.SerializeAsync(formattingContext); var encodeValue = Encode(value, serializedKey); await _storage.SetAsync(storageKey, encodeValue, options); content = formattingContext.Html; } else { // The value was found in the storage, decode and ensure // there is no cache key hash collision byte[] decodedValue = Decode(value, serializedKey); try { if (decodedValue != null) { content = await _formatter.DeserializeAsync(decodedValue); } } catch (Exception e) { _logger.DistributedFormatterDeserializationException(storageKey, e); } finally { // If the deserialization fails the content is rendered if (content == null) { content = await output.GetChildContentAsync(); } } } } catch { content = null; throw; } finally { // Remove the worker task before setting the result. // If the result is null, other threads would potentially // acquire it otherwise. _workers.TryRemove(key, out result); // Notify all other awaiters to render the content tcs.TrySetResult(content); } } else { content = await result; } } return(content); }
public void GenerateKey_ReturnsKeyBasedOnTagHelperName() { // Arrange var name = "some-name"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new DistributedCacheTagHelper( Mock.Of<IDistributedCacheTagHelperService>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), Name = name }; var expected = "DistributedCacheTagHelper||" + name; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GetHashCode_VariesByUniqueId() { // Arrange var tagHelperContext1 = GetTagHelperContext("some-id"); var cacheTagHelper1 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var tagHelperContext2 = GetTagHelperContext("some-other-id"); var cacheTagHelper2 = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var cacheKey1 = new CacheTagKey(cacheTagHelper1, tagHelperContext1); var cacheKey2 = new CacheTagKey(cacheTagHelper2, tagHelperContext2); // Act var hashcode1 = cacheKey1.GetHashCode(); var hashcode2 = cacheKey2.GetHashCode(); // Assert Assert.NotEqual(hashcode1, hashcode2); }
public ReadableCacheTagKey(CacheTagKey key) { CacheTagKey = key; }
public void GenerateKey_UsesVaryByUser_WhenUserIsNotAuthenticated() { // Arrange var expected = "CacheTagHelper||testid||VaryByUser||"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByUser = true }; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_WithMultipleVaryByOptions_CreatesCombinedKey() { // Arrange var expected = "CacheTagHelper||testid||VaryBy||custom-value||" + "VaryByHeader(content-type||text/html)||VaryByUser||someuser"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByUser = true, VaryByHeader = "content-type", VaryBy = "custom-value" }; cacheTagHelper.ViewContext.HttpContext.Request.Headers["Content-Type"] = "text/html"; var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "someuser") }); cacheTagHelper.ViewContext.HttpContext.User = new ClaimsPrincipal(identity); // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByRoute(string varyByRoute, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByRoute = varyByRoute }; cacheTagHelper.ViewContext.RouteData.Values["id"] = 4; cacheTagHelper.ViewContext.RouteData.Values["category"] = "MyCategory"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
/// <inheritdoc /> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } IHtmlContent content = null; if (Enabled) { var cacheKey = new CacheTagKey(this, context); MemoryCacheEntryOptions options; while (content == null) { Task<IHtmlContent> result = null; if (!MemoryCache.TryGetValue(cacheKey, out result)) { var tokenSource = new CancellationTokenSource(); // 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. options = GetMemoryCacheEntryOptions(); options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token)); var tcs = new TaskCompletionSource<IHtmlContent>(); // The returned value is ignored, we only do this so that // the compiler doesn't complain about the returned task // not being awaited var localTcs = MemoryCache.Set(cacheKey, tcs.Task, options); try { // The entry is set instead of assigning a value to the // task so that the expiration options are are not impacted // by the time it took to compute it. using (var entry = MemoryCache.CreateEntry(cacheKey)) { // The result is processed inside an entry // such that the tokens are inherited. result = ProcessContentAsync(output); content = await result; entry.SetOptions(options); entry.Value = result; } } catch { // Remove the worker task from the cache in case it can't complete. tokenSource.Cancel(); throw; } finally { // If an exception occurs, ensure the other awaiters // render the output by themselves. tcs.SetResult(null); } } else { // There is either some value already cached (as a Task) // or a worker processing the output. In the case of a worker, // the result will be null, and the request will try to acquire // the result from memory another time. content = await result; } } } else { content = await output.GetChildContentAsync(); } // Clear the contents of the "cache" element since we don't want to render it. output.SuppressOutput(); output.Content.SetHtmlContent(content); }
public void GenerateKey_UsesVaryByPropertyToGenerateKey(string varyBy) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryBy = varyBy }; var expected = "CacheTagHelper||testid||VaryBy||" + varyBy; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByQuery(string varyByQuery, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByQuery = varyByQuery }; cacheTagHelper.ViewContext.HttpContext.Request.QueryString = new QueryString("?sortoption=Adorability&Category=cats&sortOrder="); // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
/// <inheritdoc /> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } IHtmlContent content = null; if (Enabled) { var cacheKey = new CacheTagKey(this, context); MemoryCacheEntryOptions options; while (content == null) { Task <IHtmlContent> result = null; if (!MemoryCache.TryGetValue(cacheKey, out result)) { var tokenSource = new CancellationTokenSource(); // 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. options = GetMemoryCacheEntryOptions(); options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token)); var tcs = new TaskCompletionSource <IHtmlContent>(); // The returned value is ignored, we only do this so that // the compiler doesn't complain about the returned task // not being awaited var localTcs = MemoryCache.Set(cacheKey, tcs.Task, options); try { // The entry is set instead of assigning a value to the // task so that the expiration options are are not impacted // by the time it took to compute it. using (var entry = MemoryCache.CreateEntry(cacheKey)) { // The result is processed inside an entry // such that the tokens are inherited. result = ProcessContentAsync(output); content = await result; entry.SetOptions(options); entry.Value = result; } } catch { // Remove the worker task from the cache in case it can't complete. tokenSource.Cancel(); throw; } finally { // If an exception occurs, ensure the other awaiters // render the output by themselves. tcs.SetResult(null); } } else { // There is either some value already cached (as a Task) // or a worker processing the output. In the case of a worker, // the result will be null, and the request will try to acquire // the result from memory another time. content = await result; } } } else { content = await output.GetChildContentAsync(); } // Clear the contents of the "cache" element since we don't want to render it. output.SuppressOutput(); output.Content.SetHtmlContent(content); }
public void GenerateKey_UsesVaryByCookieName(string varyByCookie, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByCookie = varyByCookie }; cacheTagHelper.ViewContext.HttpContext.Request.Headers["Cookie"] = "Cookie0=Cookie0Value;Cookie1=Cookie1Value"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_ReturnsKeyBasedOnTagHelperUniqueId() { // Arrange var id = Guid.NewGuid().ToString(); var tagHelperContext = GetTagHelperContext(id); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext() }; var expected = "CacheTagHelper||" + id; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
private async Task <IHtmlContent> CreateCacheEntry(CacheTagKey cacheKey, TagHelperOutput output) { var tokenSource = new CancellationTokenSource(); var options = GetMemoryCacheEntryOptions(); options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token)); options.SetSize(PlaceholderSize); var tcs = new TaskCompletionSource <IHtmlContent>(); // The returned value is ignored, we only do this so that // the compiler doesn't complain about the returned task // not being awaited _ = MemoryCache.Set(cacheKey, tcs.Task, options); IHtmlContent content; try { // The entry is set instead of assigning a value to the // task so that the expiration options are not impacted // by the time it took to compute it. // Use the CreateEntry to ensure a cache scope is created that will copy expiration tokens from // cache entries created from the GetChildContentAsync call to the current entry. var entry = MemoryCache.CreateEntry(cacheKey); // The result is processed inside an entry // such that the tokens are inherited. var result = ProcessContentAsync(output); content = await result; options.SetSize(GetSize(content)); entry.SetOptions(options); entry.Value = result; // An entry gets committed to the cache when disposed gets called. We only want to do this when // the content has been correctly generated (didn't throw an exception). For that reason the entry // can't be put inside a using block. entry.Dispose(); // Set the result on the TCS once we've commited the entry to the cache since commiting to the cache // may throw. tcs.SetResult(content); return(content); } catch (Exception ex) { // Remove the worker task from the cache in case it can't complete. tokenSource.Cancel(); // Fail the TCS so other awaiters see the exception. tcs.TrySetException(ex); throw; } finally { // The tokenSource needs to be disposed as the MemoryCache // will register a callback on the Token. tokenSource.Dispose(); } }
public void GenerateKey_UsesVaryByUserAndAuthenticatedUserName() { // Arrange var expected = "CacheTagHelper||testid||VaryByUser||test_name"; var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByUser = true }; var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "test_name") }); cacheTagHelper.ViewContext.HttpContext.User = new ClaimsPrincipal(identity); // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public void GenerateKey_UsesVaryByHeader(string varyByHeader, string expected) { // Arrange var tagHelperContext = GetTagHelperContext(); var cacheTagHelper = new CacheTagHelper(Mock.Of<IMemoryCache>(), new HtmlTestEncoder()) { ViewContext = GetViewContext(), VaryByHeader = varyByHeader }; var headers = cacheTagHelper.ViewContext.HttpContext.Request.Headers; headers["Accept-Language"] = "en-us;charset=utf8"; headers["Accept-Encoding"] = "utf8"; headers["X-CustomHeader"] = "Header-Value"; // Act var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Assert Assert.Equal(expected, key); }
public async Task ProcessAsync_FlowsEntryLinkThatAllowsAddingTriggersToAddedEntry() { // Arrange var id = "some-id"; var expectedContent = new DefaultTagHelperContent(); expectedContent.SetContent("some-content"); var tokenSource = new CancellationTokenSource(); var cache = new MemoryCache(new MemoryCacheOptions() { TrackLinkedCacheEntries = true }); var cacheEntryOptions = new MemoryCacheEntryOptions() .AddExpirationToken(new CancellationChangeToken(tokenSource.Token)); var tagHelperContext = new TagHelperContext( tagName: "cache", allAttributes: new TagHelperAttributeList(), items: new Dictionary <object, object>(), uniqueId: id); var tagHelperOutput = new TagHelperOutput( "cache", new TagHelperAttributeList { { "attr", "value" } }, getChildContentAsync: (useCachedResult, encoder) => { if (!cache.TryGetValue("key1", out TagHelperContent tagHelperContent)) { tagHelperContent = expectedContent; cache.Set("key1", tagHelperContent, cacheEntryOptions); } return(Task.FromResult(tagHelperContent)); }); tagHelperOutput.PreContent.SetContent("<cache>"); tagHelperOutput.PostContent.SetContent("</cache>"); var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(cache), new HtmlTestEncoder()) { ViewContext = GetViewContext(), }; var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); var key = cacheTagKey.GenerateKey(); // Act - 1 await cacheTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); var result = cache.TryGetValue(cacheTagKey, out Task <IHtmlContent> cachedValue); // Assert - 1 Assert.Equal("HtmlEncode[[some-content]]", tagHelperOutput.Content.GetContent()); Assert.True(result); // Act - 2 tokenSource.Cancel(); result = cache.TryGetValue(cacheTagKey, out cachedValue); // Assert - 2 Assert.False(result); Assert.Null(cachedValue); }