/// <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); }
/// <inheritdoc /> public async Task <IHtmlContent> ProcessContentAsync(TagHelperOutput output, string key, DistributedCacheEntryOptions options) { IHtmlContent content = null; while (content == null) { Task <IHtmlContent> result = null; // Is there any request already processing the value? if (!_workers.TryGetValue(key, out result)) { var tcs = new TaskCompletionSource <IHtmlContent>(); _workers.TryAdd(key, tcs.Task); try { var value = await _storage.GetAsync(key); if (value == null) { 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()) }; value = await _formatter.SerializeAsync(formattingContext); await _storage.SetAsync(key, value, options); content = formattingContext.Html; } else { content = await _formatter.DeserializeAsync(value); // If the deserialization fails, it can return null, for instance when the // value is not in the expected format. if (content == null) { content = await output.GetChildContentAsync(); } } tcs.TrySetResult(content); } catch { tcs.TrySetResult(null); throw; } finally { // Remove the worker task from the in-memory cache Task <IHtmlContent> worker; _workers.TryRemove(key, out worker); } } else { content = await result; } } return(content); }