TagHelpers attribute targeting - part 2
This commit is contained in:
parent
bfdeda797d
commit
e2058905ec
|
|
@ -90,22 +90,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_ValueRequired"), p0, p1, p2, p3);
|
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_ValueRequired"), p0, p1, p2, p3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
|
|
||||||
/// </summary>
|
|
||||||
internal static string SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified
|
|
||||||
{
|
|
||||||
get { return GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(object p0, object p1, object p2)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"), p0, p1, p2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.
|
/// The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,6 @@
|
||||||
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
|
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
|
||||||
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
|
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified" xml:space="preserve">
|
|
||||||
<value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value>
|
|
||||||
</data>
|
|
||||||
<data name="TagHelpers_NoProvidedMetadata" xml:space="preserve">
|
<data name="TagHelpers_NoProvidedMetadata" xml:space="preserve">
|
||||||
<value>The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.</value>
|
<value>The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace Microsoft.AspNet.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 an <c>asp-for</c> attribute.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[TargetElement("select", Attributes = ForAttributeName)]
|
||||||
public class SelectTagHelper : TagHelper
|
public class SelectTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
private const string ForAttributeName = "asp-for";
|
private const string ForAttributeName = "asp-for";
|
||||||
|
|
@ -57,63 +58,47 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
if (For == null)
|
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
|
||||||
|
// IHtmlGenerator will enforce name requirements.
|
||||||
|
var metadata = For.Metadata;
|
||||||
|
if (metadata == null)
|
||||||
{
|
{
|
||||||
// Regular HTML <select/> element. Just make sure Items wasn't specified.
|
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
|
||||||
if (Items != null)
|
"<select>",
|
||||||
{
|
ForAttributeName,
|
||||||
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
|
nameof(IModelMetadataProvider),
|
||||||
"<select>",
|
For.Name));
|
||||||
ForAttributeName,
|
|
||||||
ItemsAttributeName);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Base allowMultiple on the instance or declared type of the expression to avoid a
|
||||||
|
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
|
||||||
|
// Metadata.IsCollectionType() is similar but does not take runtime type into account.
|
||||||
|
var realModelType = For.ModelExplorer.ModelType;
|
||||||
|
var allowMultiple = typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType);
|
||||||
|
|
||||||
|
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
|
||||||
|
var items = Items ?? Enumerable.Empty<SelectListItem>();
|
||||||
|
|
||||||
|
ICollection<string> selectedValues;
|
||||||
|
var tagBuilder = Generator.GenerateSelect(
|
||||||
|
ViewContext,
|
||||||
|
For.ModelExplorer,
|
||||||
|
optionLabel: null,
|
||||||
|
expression: For.Name,
|
||||||
|
selectList: items,
|
||||||
|
allowMultiple: allowMultiple,
|
||||||
|
htmlAttributes: null,
|
||||||
|
selectedValues: out selectedValues);
|
||||||
|
|
||||||
|
if (tagBuilder != null)
|
||||||
{
|
{
|
||||||
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
|
output.MergeAttributes(tagBuilder);
|
||||||
// IHtmlGenerator will enforce name requirements.
|
output.PostContent.Append(tagBuilder.InnerHtml);
|
||||||
var metadata = For.Metadata;
|
|
||||||
if (metadata == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
|
|
||||||
"<select>",
|
|
||||||
ForAttributeName,
|
|
||||||
nameof(IModelMetadataProvider),
|
|
||||||
For.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base allowMultiple on the instance or declared type of the expression to avoid a
|
|
||||||
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
|
|
||||||
// Metadata.IsCollectionType() is similar but does not take runtime type into account.
|
|
||||||
var realModelType = For.ModelExplorer.ModelType;
|
|
||||||
var allowMultiple =
|
|
||||||
typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType);
|
|
||||||
|
|
||||||
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
|
|
||||||
var items = Items ?? Enumerable.Empty<SelectListItem>();
|
|
||||||
|
|
||||||
ICollection<string> selectedValues;
|
|
||||||
var tagBuilder = Generator.GenerateSelect(
|
|
||||||
ViewContext,
|
|
||||||
For.ModelExplorer,
|
|
||||||
optionLabel: null,
|
|
||||||
expression: For.Name,
|
|
||||||
selectList: items,
|
|
||||||
allowMultiple: allowMultiple,
|
|
||||||
htmlAttributes: null,
|
|
||||||
selectedValues: out selectedValues);
|
|
||||||
|
|
||||||
if (tagBuilder != null)
|
|
||||||
{
|
|
||||||
output.MergeAttributes(tagBuilder);
|
|
||||||
output.PostContent.Append(tagBuilder.InnerHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
|
|
||||||
// elements. Provide selected values for <option/> tag helpers. They'll run next.
|
|
||||||
ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = selectedValues;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
|
||||||
|
// elements. Provide selected values for <option/> tag helpers. They'll run next.
|
||||||
|
ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = selectedValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="ITagHelper"/> implementation targeting <textarea> elements with an <c>asp-for</c> attribute.
|
/// <see cref="ITagHelper"/> implementation targeting <textarea> elements with an <c>asp-for</c> attribute.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TargetElement("textarea")]
|
[TargetElement("textarea", Attributes = ForAttributeName)]
|
||||||
public class TextAreaTagHelper : TagHelper
|
public class TextAreaTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
private const string ForAttributeName = "asp-for";
|
private const string ForAttributeName = "asp-for";
|
||||||
|
|
@ -33,23 +33,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
{
|
{
|
||||||
if (For != null)
|
var tagBuilder = Generator.GenerateTextArea(
|
||||||
|
ViewContext,
|
||||||
|
For.ModelExplorer,
|
||||||
|
For.Name,
|
||||||
|
rows: 0,
|
||||||
|
columns: 0,
|
||||||
|
htmlAttributes: null);
|
||||||
|
|
||||||
|
if (tagBuilder != null)
|
||||||
{
|
{
|
||||||
var tagBuilder = Generator.GenerateTextArea(
|
// Overwrite current Content to ensure expression result round-trips correctly.
|
||||||
ViewContext,
|
output.Content.SetContent(tagBuilder.InnerHtml);
|
||||||
For.ModelExplorer,
|
|
||||||
For.Name,
|
|
||||||
rows: 0,
|
|
||||||
columns: 0,
|
|
||||||
htmlAttributes: null);
|
|
||||||
|
|
||||||
if (tagBuilder != null)
|
output.MergeAttributes(tagBuilder);
|
||||||
{
|
|
||||||
// Overwrite current Content to ensure expression result round-trips correctly.
|
|
||||||
output.Content.SetContent(tagBuilder.InnerHtml);
|
|
||||||
|
|
||||||
output.MergeAttributes(tagBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -603,37 +603,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
Assert.Same(selectedValues, keyValuePair.Value);
|
Assert.Same(selectedValues, keyValuePair.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_Throws_IfForNotBoundButItemsIs()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var contextAttributes = new Dictionary<string, object>();
|
|
||||||
var originalAttributes = new Dictionary<string, string>();
|
|
||||||
var expectedTagName = "select";
|
|
||||||
var expectedMessage = "Cannot determine body for <select>. 'asp-items' must be null if 'asp-for' is null.";
|
|
||||||
|
|
||||||
var tagHelperContext = new TagHelperContext(
|
|
||||||
contextAttributes,
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test",
|
|
||||||
getChildContentAsync: () =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
var output = new TagHelperOutput(expectedTagName, originalAttributes);
|
|
||||||
var tagHelper = new SelectTagHelper
|
|
||||||
{
|
|
||||||
Items = Enumerable.Empty<SelectListItem>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => tagHelper.ProcessAsync(tagHelperContext, output));
|
|
||||||
Assert.Equal(expectedMessage, exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NameAndId
|
public class NameAndId
|
||||||
{
|
{
|
||||||
public NameAndId(string name, string id)
|
public NameAndId(string name, string id)
|
||||||
|
|
|
||||||
|
|
@ -154,67 +154,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
Assert.Equal(expectedTagName, output.TagName);
|
Assert.Equal(expectedTagName, output.TagName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedAttributes = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "class", "form-control" },
|
|
||||||
};
|
|
||||||
var expectedPreContent = "original pre-content";
|
|
||||||
var expectedContent = "original content";
|
|
||||||
var expectedPostContent = "original post-content";
|
|
||||||
var expectedTagName = "textarea";
|
|
||||||
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
var modelExplorer = metadataProvider
|
|
||||||
.GetModelExplorerForType(typeof(Model), model: null)
|
|
||||||
.GetExplorerForProperty(nameof(Model.Text));
|
|
||||||
|
|
||||||
var modelExpression = new ModelExpression(nameof(Model.Text), modelExplorer);
|
|
||||||
var tagHelper = new TextAreaTagHelper();
|
|
||||||
|
|
||||||
var tagHelperContext = new TagHelperContext(
|
|
||||||
allAttributes: new Dictionary<string, object>(),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test",
|
|
||||||
getChildContentAsync: () =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
var output = new TagHelperOutput(expectedTagName, expectedAttributes)
|
|
||||||
{
|
|
||||||
SelfClosing = true,
|
|
||||||
};
|
|
||||||
output.PreContent.SetContent(expectedPreContent);
|
|
||||||
output.Content.SetContent(expectedContent);
|
|
||||||
output.PostContent.SetContent(expectedPostContent);
|
|
||||||
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
|
|
||||||
{
|
|
||||||
ValidationAttributes =
|
|
||||||
{
|
|
||||||
{ "valid", "from validation attributes" },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Model model = null;
|
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
|
||||||
tagHelper.ViewContext = viewContext;
|
|
||||||
tagHelper.Generator = htmlGenerator;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await tagHelper.ProcessAsync(tagHelperContext, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expectedAttributes, output.Attributes);
|
|
||||||
Assert.Equal(expectedContent, output.Content.GetContent());
|
|
||||||
Assert.True(output.SelfClosing);
|
|
||||||
Assert.Equal(expectedTagName, output.TagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NameAndId
|
public class NameAndId
|
||||||
{
|
{
|
||||||
public NameAndId(string name, string id)
|
public NameAndId(string name, string id)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue