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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -132,9 +132,6 @@
|
|||
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
|
||||
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
|
||||
</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">
|
||||
<value>The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <select> elements with an <c>asp-for</c> attribute.
|
||||
/// </summary>
|
||||
[TargetElement("select", Attributes = ForAttributeName)]
|
||||
public class SelectTagHelper : TagHelper
|
||||
{
|
||||
private const string ForAttributeName = "asp-for";
|
||||
|
|
@ -57,63 +58,47 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// </exception>
|
||||
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.
|
||||
if (Items != null)
|
||||
{
|
||||
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
|
||||
"<select>",
|
||||
ForAttributeName,
|
||||
ItemsAttributeName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
|
||||
"<select>",
|
||||
ForAttributeName,
|
||||
nameof(IModelMetadataProvider),
|
||||
For.Name));
|
||||
}
|
||||
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.
|
||||
// IHtmlGenerator will enforce name requirements.
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <textarea> elements with an <c>asp-for</c> attribute.
|
||||
/// </summary>
|
||||
[TargetElement("textarea")]
|
||||
[TargetElement("textarea", Attributes = ForAttributeName)]
|
||||
public class TextAreaTagHelper : TagHelper
|
||||
{
|
||||
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>
|
||||
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(
|
||||
ViewContext,
|
||||
For.ModelExplorer,
|
||||
For.Name,
|
||||
rows: 0,
|
||||
columns: 0,
|
||||
htmlAttributes: null);
|
||||
// Overwrite current Content to ensure expression result round-trips correctly.
|
||||
output.Content.SetContent(tagBuilder.InnerHtml);
|
||||
|
||||
if (tagBuilder != null)
|
||||
{
|
||||
// Overwrite current Content to ensure expression result round-trips correctly.
|
||||
output.Content.SetContent(tagBuilder.InnerHtml);
|
||||
|
||||
output.MergeAttributes(tagBuilder);
|
||||
}
|
||||
output.MergeAttributes(tagBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -603,37 +603,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
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 NameAndId(string name, string id)
|
||||
|
|
|
|||
|
|
@ -154,67 +154,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
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 NameAndId(string name, string id)
|
||||
|
|
|
|||
Loading…
Reference in New Issue