| File: OptionTagHelper.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.TagHelpers\src\Microsoft.AspNetCore.Mvc.TagHelpers.csproj (Microsoft.AspNetCore.Mvc.TagHelpers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Microsoft.AspNetCore.Mvc.TagHelpers; /// <summary> /// <see cref="ITagHelper"/> implementation targeting <option> elements. /// </summary> /// <remarks> /// This <see cref="ITagHelper"/> works in conjunction with <see cref="SelectTagHelper"/>. It reads elements /// content but does not modify that content. The only modification it makes is to add a <c>selected</c> attribute /// in some cases. /// </remarks> public class OptionTagHelper : TagHelper { /// <summary> /// Creates a new <see cref="OptionTagHelper"/>. /// </summary> /// <param name="generator">The <see cref="IHtmlGenerator"/>.</param> public OptionTagHelper(IHtmlGenerator generator) { Generator = generator; } /// <inheritdoc /> public override int Order => -1000; /// <summary> /// Gets the <see cref="IHtmlGenerator"/> used to generate the <see cref="OptionTagHelper"/>'s output. /// </summary> protected IHtmlGenerator Generator { get; } /// <summary> /// Gets the <see cref="Rendering.ViewContext"/> of the executing view. /// </summary> [HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; } /// <summary> /// Specifies a value for the <option> element. /// </summary> /// <remarks> /// Passed through to the generated HTML in all cases. /// </remarks> public string Value { get; set; } /// <inheritdoc /> /// <remarks> /// Does nothing unless <see cref="TagHelperContext.Items"/> contains a /// <see cref="SelectTagHelper"/> <see cref="Type"/> entry and that entry is a non-<c>null</c> /// <see cref="CurrentValues"/> instance. Also does nothing if the associated <option> is already /// selected. /// </remarks> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(output); // Pass through attributes that are also well-known HTML attributes. if (Value != null) { output.CopyHtmlAttribute(nameof(Value), context); } // Nothing to do if this <option/> is already selected. if (!output.Attributes.ContainsName("selected")) { // Is this <option/> element a child of a <select/> element the SelectTagHelper targeted? context.Items.TryGetValue(typeof(SelectTagHelper), out var formDataEntry); // ... And did the SelectTagHelper determine any selected values? var currentValues = formDataEntry as CurrentValues; if (currentValues?.Values != null && currentValues.Values.Count != 0) { // Select this <option/> element if value attribute or (if no value attribute) body matches a // selected value. Body is encoded as-needed while executing child content. But TagHelperOutput // itself encodes attribute values later, when start tag is generated. bool selected; if (Value != null) { selected = currentValues.Values.Contains(Value); } else { if (currentValues.ValuesAndEncodedValues == null) { // Include encoded versions of all selected values when comparing with body. var allValues = new HashSet<string>(currentValues.Values, StringComparer.OrdinalIgnoreCase); foreach (var selectedValue in currentValues.Values) { allValues.Add(Generator.Encode(selectedValue)); } currentValues.ValuesAndEncodedValues = allValues; } TagHelperContent childContent; if (output.IsContentModified) { // Another tag helper has modified the body. Use what they wrote. childContent = output.Content; } else { childContent = await output.GetChildContentAsync(); } selected = currentValues.ValuesAndEncodedValues.Contains(childContent.GetContent()); } if (selected) { output.Attributes.Add("selected", "selected"); } } } } }