| File: Runtime\TagHelpers\TagHelperExecutionContext.cs | Web Access |
| Project: src\aspnetcore\src\Razor\Razor.Runtime\src\Microsoft.AspNetCore.Razor.Runtime.csproj (Microsoft.AspNetCore.Razor.Runtime) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers; /// <summary> /// Class used to store information about a <see cref="ITagHelper"/>'s execution lifetime. /// </summary> public class TagHelperExecutionContext { private readonly List<ITagHelper> _tagHelpers; private readonly Action<HtmlEncoder> _startTagHelperWritingScope; private readonly Func<TagHelperContent> _endTagHelperWritingScope; private TagHelperContent _childContent; private Func<Task> _executeChildContentAsync; private Dictionary<HtmlEncoder, TagHelperContent> _perEncoderChildContent; private readonly TagHelperAttributeList _allAttributes; /// <summary> /// Internal for testing purposes only. /// </summary> internal TagHelperExecutionContext(string tagName, TagMode tagMode) : this(tagName, tagMode, items: new Dictionary<object, object>(), uniqueId: string.Empty, executeChildContentAsync: () => Task.CompletedTask, startTagHelperWritingScope: _ => { }, endTagHelperWritingScope: () => new DefaultTagHelperContent()) { } /// <summary> /// Instantiates a new <see cref="TagHelperExecutionContext"/>. /// </summary> /// <param name="tagName">The HTML tag name in the Razor source.</param> /// <param name="tagMode">HTML syntax of the element in the Razor source.</param> /// <param name="items">The collection of items used to communicate with other /// <see cref="ITagHelper"/>s</param> /// <param name="uniqueId">An identifier unique to the HTML element this context is for.</param> /// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param> /// <param name="startTagHelperWritingScope"> /// A delegate used to start a writing scope in a Razor page and optionally override the page's /// <see cref="HtmlEncoder"/> within that scope. /// </param> /// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param> public TagHelperExecutionContext( string tagName, TagMode tagMode, IDictionary<object, object> items, string uniqueId, Func<Task> executeChildContentAsync, Action<HtmlEncoder> startTagHelperWritingScope, Func<TagHelperContent> endTagHelperWritingScope) { ArgumentNullException.ThrowIfNull(startTagHelperWritingScope); ArgumentNullException.ThrowIfNull(endTagHelperWritingScope); _tagHelpers = new List<ITagHelper>(); _allAttributes = new TagHelperAttributeList(); Context = new TagHelperContext(tagName, _allAttributes, items, uniqueId); Output = new TagHelperOutput(tagName, new TagHelperAttributeList(), GetChildContentAsync) { TagMode = tagMode }; Reinitialize(tagName, tagMode, items, uniqueId, executeChildContentAsync); _startTagHelperWritingScope = startTagHelperWritingScope; _endTagHelperWritingScope = endTagHelperWritingScope; } /// <summary> /// Indicates if <see cref="GetChildContentAsync"/> has been called. /// </summary> public bool ChildContentRetrieved => _childContent != null; /// <summary> /// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s. /// </summary> public IDictionary<object, object> Items { get; private set; } /// <summary> /// <see cref="ITagHelper"/>s that should be run. /// </summary> public IList<ITagHelper> TagHelpers => _tagHelpers; internal List<ITagHelper> TagHelperList => _tagHelpers; // Internal set for testing. /// <summary> /// The <see cref="ITagHelper"/>'s output. /// </summary> public TagHelperOutput Output { get; internal set; } /// <summary> /// The <see cref="ITagHelper"/>'s context. /// </summary> public TagHelperContext Context { get; } /// <summary> /// Tracks the given <paramref name="tagHelper"/>. /// </summary> /// <param name="tagHelper">The tag helper to track.</param> public void Add(ITagHelper tagHelper) { ArgumentNullException.ThrowIfNull(tagHelper); _tagHelpers.Add(tagHelper); } /// <summary> /// Tracks the HTML attribute. /// </summary> /// <param name="name">The HTML attribute name.</param> /// <param name="value">The HTML attribute value.</param> /// <param name="valueStyle">The value style of the attribute.</param> public void AddHtmlAttribute(string name, object value, HtmlAttributeValueStyle valueStyle) { ArgumentNullException.ThrowIfNull(name); var attribute = new TagHelperAttribute(name, value, valueStyle); AddHtmlAttribute(attribute); } /// <summary> /// Tracks the HTML attribute. /// </summary> /// <param name="attribute">The <see cref="TagHelperAttribute"/> to track.</param> public void AddHtmlAttribute(TagHelperAttribute attribute) { ArgumentNullException.ThrowIfNull(attribute); Output.Attributes.Add(attribute); _allAttributes.Add(attribute); } /// <summary> /// Tracks the <see cref="ITagHelper"/> bound attribute. /// </summary> /// <param name="name">The bound attribute name.</param> /// <param name="value">The attribute value.</param> /// <param name="valueStyle">The value style of the attribute.</param> public void AddTagHelperAttribute(string name, object value, HtmlAttributeValueStyle valueStyle) { ArgumentNullException.ThrowIfNull(name); var attribute = new TagHelperAttribute(name, value, valueStyle); _allAttributes.Add(attribute); } /// <summary> /// Tracks the <see cref="ITagHelper"/> bound attribute. /// </summary> /// <param name="attribute">The bound attribute.</param> public void AddTagHelperAttribute(TagHelperAttribute attribute) { ArgumentNullException.ThrowIfNull(attribute); _allAttributes.Add(attribute); } /// <summary> /// Clears the <see cref="TagHelperExecutionContext"/> and updates its state with the provided values. /// </summary> /// <param name="tagName">The tag name to use.</param> /// <param name="tagMode">The <see cref="TagMode"/> to use.</param> /// <param name="items">The <see cref="IDictionary{Object, Object}"/> to use.</param> /// <param name="uniqueId">The unique id to use.</param> /// <param name="executeChildContentAsync">The <see cref="Func{Task}"/> to use.</param> public void Reinitialize( string tagName, TagMode tagMode, IDictionary<object, object> items, string uniqueId, Func<Task> executeChildContentAsync) { ArgumentNullException.ThrowIfNull(tagName); ArgumentNullException.ThrowIfNull(items); ArgumentNullException.ThrowIfNull(uniqueId); ArgumentNullException.ThrowIfNull(executeChildContentAsync); Items = items; _executeChildContentAsync = executeChildContentAsync; _tagHelpers.Clear(); _perEncoderChildContent?.Clear(); _childContent = null; Context.Reinitialize(tagName, Items, uniqueId); Output.Reinitialize(tagName, tagMode); } /// <summary> /// Executes children asynchronously with the page's <see cref="HtmlEncoder" /> in scope and /// sets <see cref="Output"/>'s <see cref="TagHelperOutput.Content"/> to the rendered results. /// </summary> /// <returns>A <see cref="Task"/> that on completion sets <see cref="Output"/>'s /// <see cref="TagHelperOutput.Content"/> to the children's rendered content.</returns> public async Task SetOutputContentAsync() { var childContent = _childContent; if (childContent == null) { _startTagHelperWritingScope(null); try { await _executeChildContentAsync(); } finally { childContent = _endTagHelperWritingScope(); } } Debug.Assert(!Output.IsContentModified); Output.Content.SetHtmlContent(childContent); } // Internal for testing. internal async Task<TagHelperContent> GetChildContentAsync(bool useCachedResult, HtmlEncoder encoder) { // Get cached content for this encoder. TagHelperContent childContent; if (encoder == null) { childContent = _childContent; } else { if (_perEncoderChildContent == null) { childContent = null; _perEncoderChildContent = new Dictionary<HtmlEncoder, TagHelperContent>(); } else { _perEncoderChildContent.TryGetValue(encoder, out childContent); } } if (!useCachedResult || childContent == null) { _startTagHelperWritingScope(encoder); try { await _executeChildContentAsync(); } finally { childContent = _endTagHelperWritingScope(); } if (encoder == null) { _childContent = childContent; } else { _perEncoderChildContent[encoder] = childContent; } } return new DefaultTagHelperContent().SetHtmlContent(childContent); } }