[Fixes #4047] SelectTagHelpers: asp-items shouldn't require/depend on asp-for.
This commit is contained in:
parent
fd3ee49987
commit
4123d83d26
|
|
@ -15,9 +15,11 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="ITagHelper"/> implementation targeting <select> elements with an <c>asp-for</c> attribute.
|
/// <see cref="ITagHelper"/> implementation targeting <select> elements with <c>asp-for</c> and/or
|
||||||
|
/// <c>asp-items</c> attribute(s).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HtmlTargetElement("select", Attributes = ForAttributeName)]
|
[HtmlTargetElement("select", Attributes = ForAttributeName)]
|
||||||
|
[HtmlTargetElement("select", Attributes = ItemsAttributeName)]
|
||||||
public class SelectTagHelper : TagHelper
|
public class SelectTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
private const string ForAttributeName = "asp-for";
|
private const string ForAttributeName = "asp-for";
|
||||||
|
|
@ -70,6 +72,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (For == null)
|
||||||
|
{
|
||||||
|
// Informs contained elements that they're running within a targeted <select/> element.
|
||||||
|
context.Items[typeof(SelectTagHelper)] = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
|
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
|
||||||
// IHtmlGenerator will enforce name requirements.
|
// IHtmlGenerator will enforce name requirements.
|
||||||
if (For.Metadata == null)
|
if (For.Metadata == null)
|
||||||
|
|
@ -101,9 +110,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
||||||
/// <exception cref="InvalidOperationException">
|
|
||||||
/// Thrown if <see cref="Items"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
|
|
||||||
/// </exception>
|
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
|
|
@ -119,6 +125,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
|
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
|
||||||
var items = Items ?? Enumerable.Empty<SelectListItem>();
|
var items = Items ?? Enumerable.Empty<SelectListItem>();
|
||||||
|
|
||||||
|
if (For == null)
|
||||||
|
{
|
||||||
|
var options = Generator.GenerateGroupsAndOptions(optionLabel: null, selectList: items);
|
||||||
|
output.PostContent.AppendHtml(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tagBuilder = Generator.GenerateSelect(
|
var tagBuilder = Generator.GenerateSelect(
|
||||||
ViewContext,
|
ViewContext,
|
||||||
For.ModelExplorer,
|
For.ModelExplorer,
|
||||||
|
|
|
||||||
|
|
@ -1457,7 +1457,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
return newSelectList;
|
return newSelectList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
|
/// <inheritdoc />
|
||||||
|
public IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
|
||||||
{
|
{
|
||||||
var listItemBuilder = new HtmlContentBuilder();
|
var listItemBuilder = new HtmlContentBuilder();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,19 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
bool allowMultiple,
|
bool allowMultiple,
|
||||||
object htmlAttributes);
|
object htmlAttributes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates <optgroup> and <option> elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionLabel">Optional text for a default empty <option> element.</param>
|
||||||
|
/// <param name="selectList">
|
||||||
|
/// A collection of <see cref="SelectListItem"/> objects used to generate <optgroup> and <option>
|
||||||
|
/// elements.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IHtmlContent"/> instance for <optgroup> and <option> elements.
|
||||||
|
/// </returns>
|
||||||
|
IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList);
|
||||||
|
|
||||||
TagBuilder GenerateTextArea(
|
TagBuilder GenerateTextArea(
|
||||||
ViewContext viewContext,
|
ViewContext viewContext,
|
||||||
ModelExplorer modelExplorer,
|
ModelExplorer modelExplorer,
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,80 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Assert.Equal(savedValue, items.Select(item => item.Value));
|
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 = "<option>HtmlEncode[[0]]</option>" + Environment.NewLine
|
||||||
|
+ "<option>HtmlEncode[[1]]</option>" + Environment.NewLine
|
||||||
|
+ "<option>HtmlEncode[[2]]</option>" + Environment.NewLine
|
||||||
|
+ "<option>HtmlEncode[[3]]</option>" + Environment.NewLine
|
||||||
|
+ "<option>HtmlEncode[[4]]</option>" + 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<TagHelperAttribute>()),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
expectedTagName,
|
||||||
|
originalAttributes,
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
var tagHelperContent = new DefaultTagHelperContent();
|
||||||
|
tagHelperContent.AppendHtml("Something");
|
||||||
|
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
TagMode = TagMode.SelfClosing,
|
||||||
|
};
|
||||||
|
output.PreContent.AppendHtml(expectedPreContent);
|
||||||
|
output.Content.AppendHtml(expectedContent);
|
||||||
|
output.PostContent.AppendHtml(originalPostContent);
|
||||||
|
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||||
|
var viewContext = TestableHtmlGenerator.GetViewContext(
|
||||||
|
model: null,
|
||||||
|
htmlGenerator: htmlGenerator,
|
||||||
|
metadataProvider: metadataProvider);
|
||||||
|
|
||||||
|
var tagHelper = new SelectTagHelper(htmlGenerator)
|
||||||
|
{
|
||||||
|
Items = selectItems,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
tagHelper.Init(tagHelperContext);
|
||||||
|
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(TagMode.SelfClosing, output.TagMode);
|
||||||
|
Assert.Equal(expectedAttributes, output.Attributes);
|
||||||
|
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
|
||||||
|
Assert.Equal(expectedContent, output.Content.GetContent());
|
||||||
|
Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
|
||||||
|
Assert.Equal(expectedTagName, output.TagName);
|
||||||
|
|
||||||
|
var kvp = Assert.Single(tagHelperContext.Items);
|
||||||
|
Assert.Equal(typeof(SelectTagHelper), kvp.Key);
|
||||||
|
Assert.Null(kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(GeneratesExpectedDataSet))]
|
[MemberData(nameof(GeneratesExpectedDataSet))]
|
||||||
public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesNotChangeSelectList(
|
public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesNotChangeSelectList(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue