diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs
index 92cab6b0e3..f650216b72 100644
--- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs
+++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs
@@ -15,9 +15,11 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
///
- /// implementation targeting <select> elements with an asp-for attribute.
+ /// implementation targeting <select> elements with asp-for and/or
+ /// asp-items attribute(s).
///
[HtmlTargetElement("select", Attributes = ForAttributeName)]
+ [HtmlTargetElement("select", Attributes = ItemsAttributeName)]
public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
@@ -70,6 +72,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
throw new ArgumentNullException(nameof(context));
}
+ if (For == null)
+ {
+ // Informs contained elements that they're running within a targeted element.
+ context.Items[typeof(SelectTagHelper)] = null;
+ return;
+ }
+
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
if (For.Metadata == null)
@@ -101,9 +110,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
///
/// Does nothing if is null.
- ///
- /// Thrown if is non-null but is null.
- ///
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
@@ -119,6 +125,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty();
+ if (For == null)
+ {
+ var options = Generator.GenerateGroupsAndOptions(optionLabel: null, selectList: items);
+ output.PostContent.AppendHtml(options);
+ return;
+ }
+
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
index 29339f13fa..e055e8ebbd 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs
@@ -1457,7 +1457,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return newSelectList;
}
- private IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable selectList)
+ ///
+ public IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable selectList)
{
var listItemBuilder = new HtmlContentBuilder();
diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
index 551c132ca3..c129e4638a 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs
@@ -307,6 +307,19 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
bool allowMultiple,
object htmlAttributes);
+ ///
+ /// Generates <optgroup> and <option> elements.
+ ///
+ /// Optional text for a default empty <option> element.
+ ///
+ /// A collection of objects used to generate <optgroup> and <option>
+ /// elements.
+ ///
+ ///
+ /// An instance for <optgroup> and <option> elements.
+ ///
+ IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable selectList);
+
TagBuilder GenerateTextArea(
ViewContext viewContext,
ModelExplorer modelExplorer,
diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
index ca79168126..65efd60719 100644
--- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
@@ -356,6 +356,80 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Equal(savedValue, items.Select(item => item.Value));
}
+ [Fact]
+ public async Task ProcessAsync_WithItems_AndNoModelExpression_GeneratesExpectedOutput()
+ {
+ // Arrange
+ var originalAttributes = new TagHelperAttributeList
+ {
+ { "class", "form-control" },
+ };
+ var originalPostContent = "original content";
+
+ var expectedAttributes = new TagHelperAttributeList(originalAttributes);
+ var selectItems = new SelectList(Enumerable.Range(0, 5));
+ var expectedOptions = "" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "" + Environment.NewLine
+ + "" + Environment.NewLine;
+
+ var expectedPreContent = "original pre-content";
+ var expectedContent = "original content";
+ var expectedPostContent = originalPostContent + expectedOptions;
+ var expectedTagName = "select";
+
+ var tagHelperContext = new TagHelperContext(
+ allAttributes: new TagHelperAttributeList(
+ Enumerable.Empty()),
+ items: new Dictionary