/// <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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
        }
Пример #14
0
        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);
        }
Пример #15
0
        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);
        }
Пример #16
0
        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);
        }
Пример #17
0
        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);
        }
Пример #18
0
        /// <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);
        }
Пример #19
0
    /// <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);
    }
Пример #20
0
        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);
        }
Пример #21
0
        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);
        }
Пример #22
0
 public ReadableCacheTagKey(CacheTagKey key)
 {
     CacheTagKey = key;
 }
Пример #23
0
        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);
        }
Пример #24
0
        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);
        }
Пример #25
0
        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);
        }
Пример #26
0
        /// <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);
        }
Пример #27
0
        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);
        }
Пример #28
0
        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);
        }
Пример #29
0
        /// <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);
        }
Пример #30
0
        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);
        }
Пример #31
0
        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);
        }
Пример #32
0
        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();
            }
        }
Пример #33
0
        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);
        }
Пример #34
0
        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);
        }
Пример #35
0
    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);
    }