public async Task Deserialize_DoesntAlterValue_WhenSerialized()
        {
            // Arrange
            var content   = "<b>some content</b>";
            var formatter = GetFormatter();
            var context   = new DistributedCacheTagHelperFormattingContext
            {
                Html = new HtmlString(content)
            };
            var serialized = await formatter.SerializeAsync(context);

            // Act
            var deserialized = await formatter.DeserializeAsync(serialized);

            // Assert
            Assert.Equal(deserialized.ToString(), content);
        }
    /// <inheritdoc />
    public Task <byte[]> SerializeAsync(DistributedCacheTagHelperFormattingContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Html == null)
        {
            throw new ArgumentException(
                      Resources.FormatPropertyOfTypeCannotBeNull(
                          nameof(DistributedCacheTagHelperFormattingContext.Html),
                          typeof(DistributedCacheTagHelperFormattingContext).FullName));
        }

        var serialized = Encoding.UTF8.GetBytes(context.Html.ToString());

        return(Task.FromResult(serialized));
    }
        public async Task <IHtmlContent> ProcessContentAsync(TagHelperOutput output, CacheContext cacheContext)
        {
            IHtmlContent content = null;

            while (content == null)
            {
                Task <IHtmlContent> result;

                // Is there any request already processing the value?
                if (!_dynamicCacheTagHelperService.Workers.TryGetValue(CacheId, out 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>();

                    _dynamicCacheTagHelperService.Workers.TryAdd(CacheId, tcs.Task);

                    try
                    {
                        var value = await _dynamicCacheService.GetCachedValueAsync(cacheContext);

                        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())
                            };

                            await _dynamicCacheService.SetCachedValueAsync(cacheContext, formattingContext.Html.ToString());

                            content = formattingContext.Html;
                        }
                        else
                        {
                            content = new HtmlString(value);
                        }
                    }
                    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.
                        _dynamicCacheTagHelperService.Workers.TryRemove(CacheId, out result);

                        // Notify all other awaiters to render the content
                        tcs.TrySetResult(content);
                    }
                }
                else
                {
                    content = await result;
                }
            }

            return(content);
        }
Ejemplo n.º 4
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);
    }
        public async Task <IHtmlContent> ProcessContentAsync(TagHelperOutput output, CacheContext cacheContext)
        {
            IHtmlContent content = null;

            while (content == null)
            {
                Task <IHtmlContent> result;

                // Is there any request already processing the value?
                if (!_dynamicCacheTagHelperService.Workers.TryGetValue(CacheId, out 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>();

                    _dynamicCacheTagHelperService.Workers.TryAdd(CacheId, tcs.Task);

                    try
                    {
                        var value = await _dynamicCacheService.GetCachedValueAsync(cacheContext);

                        if (value == null)
                        {
                            // The value is not cached, we need to render the tag helper output
                            var processedContent = await output.GetChildContentAsync();

                            using (var sb = StringBuilderPool.GetInstance())
                            {
                                using (var writer = new StringWriter(sb.Builder))
                                {
                                    // Write the start of a cache debug block.
                                    if (_cacheOptions.DebugMode)
                                    {
                                        // No need to optimize this code as it will be used for debugging purpose.
                                        writer.WriteLine();
                                        writer.WriteLine($"<!-- CACHE BLOCK: {cacheContext.CacheId} ({Guid.NewGuid()})");
                                        writer.WriteLine($"         VARY BY: {String.Join(", ", cacheContext.Contexts)}");
                                        writer.WriteLine($"    DEPENDENCIES: {String.Join(", ", cacheContext.Tags)}");
                                        writer.WriteLine($"      EXPIRES ON: {cacheContext.ExpiresOn}");
                                        writer.WriteLine($"   EXPIRES AFTER: {cacheContext.ExpiresAfter}");
                                        writer.WriteLine($" EXPIRES SLIDING: {cacheContext.ExpiresSliding}");
                                        writer.WriteLine("-->");
                                    }

                                    // Always write the content regardless of debug mode.
                                    processedContent.WriteTo(writer, HtmlEncoder);

                                    // Write the end of a cache debug block.
                                    if (_cacheOptions.DebugMode)
                                    {
                                        writer.WriteLine();
                                        writer.WriteLine($"<!-- END CACHE BLOCK: {cacheContext.CacheId} -->");
                                    }

                                    await writer.FlushAsync();
                                }

                                var html = sb.Builder.ToString();

                                var formattingContext = new DistributedCacheTagHelperFormattingContext
                                {
                                    Html = new HtmlString(html)
                                };

                                await _dynamicCacheService.SetCachedValueAsync(cacheContext, html);

                                content = formattingContext.Html;
                            }
                        }
                        else
                        {
                            content = new HtmlString(value);
                        }
                    }
                    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.
                        _dynamicCacheTagHelperService.Workers.TryRemove(CacheId, out result);

                        // Notify all other awaiters to render the content
                        tcs.TrySetResult(content);
                    }
                }
                else
                {
                    content = await result;
                }
            }

            return(content);
        }