public async Task DisplayedAsync(ShapeDisplayContext context) { var cacheContext = context.ShapeMetadata.Cache(); // If the shape is not configured to be cached, continue as usual if (cacheContext == null) { if (context.ChildContent == null) { context.ChildContent = HtmlString.Empty; } return; } // If we have got this far, then this shape is configured to be cached. // We need to determine whether or not the ChildContent of this shape was retrieved from the cache by the DisplayingAsync method above, as opposed to generated by the View Engine. // ChildContent will be generated by the View Engine if it was not available in the cache when we rendered the shape. // In this instance, we need insert the ChildContent into the cache so that subsequent attempt to render this shape can take advantage of the cached content. // If the ChildContent was retrieved form the cache, then the Cache Context will be present in the _cached collection (see the DisplayingAsync method in this class). // So, if the cache context is not present in the _cached collection, we need to insert the ChildContent value into the cache: if (!_cached.ContainsKey(cacheContext.CacheId) && context.ChildContent != null) { // The content is pre-encoded in the cache so we don't have to do it every time it's rendered using (var sb = StringBuilderPool.GetInstance()) { using (var sw = new StringWriter(sb.Builder)) { context.ChildContent.WriteTo(sw, HtmlEncoder.Default); await _dynamicCacheService.SetCachedValueAsync(cacheContext, sw.ToString()); await sw.FlushAsync(); } } } }
public async Task DisplayedAsync(ShapeDisplayContext context) { var cacheContext = context.Shape.Metadata.Cache(); // If the shape is not configured to be cached, continue as usual if (cacheContext == null) { if (context.ChildContent == null) { context.ChildContent = HtmlString.Empty; } return; } // If we have got this far, then this shape is configured to be cached. // We need to determine whether or not the ChildContent of this shape was retrieved from the cache by the DisplayingAsync method above, as opposed to generated by the View Engine. // ChildContent will be generated by the View Engine if it was not available in the cache when we rendered the shape. // In this instance, we need insert the ChildContent into the cache so that subsequent attempt to render this shape can take advantage of the cached content. // If the ChildContent was retrieved form the cache, then the Cache Context will be present in the _cached collection (see the DisplayingAsync method in this class). // So, if the cache context is not present in the _cached collection, we need to insert the ChildContent value into the cache: if (!_cached.ContainsKey(cacheContext.CacheId) && context.ChildContent != null) { // The content is pre-encoded in the cache so we don't have to do it every time it's rendered using var sw = new ZStringWriter(); // 'ChildContent' may be a 'ViewBufferTextWriterContent' on which we can't // call 'WriteTo()' twice, so here we update it with a new 'HtmlString()'. context.ChildContent.WriteTo(sw, _htmlEncoder); var contentHtmlString = new HtmlString(sw.ToString()); context.ChildContent = contentHtmlString; await _dynamicCacheService.SetCachedValueAsync(cacheContext, contentHtmlString.Value); } }
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); }
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); }