From bfdeda797df054a4ae8a6fa9250e65f07cd63a1e Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Thu, 19 Mar 2015 12:42:21 -0700 Subject: [PATCH] TagHelpers attribute targeting - part 1 --- .../InputTagHelper.cs | 139 ++++++++---------- .../LabelTagHelper.cs | 45 +++--- .../Properties/Resources.Designer.cs | 16 -- .../Resources.resx | 3 - .../ValidationMessageTagHelper.cs | 4 +- .../ValidationSummaryTagHelper.cs | 4 +- ...elpersWebSite.Employee.Create.Invalid.html | 2 +- .../TagHelpersWebSite.Employee.Create.html | 2 +- .../InputTagHelperTest.cs | 88 ----------- .../LabelTagHelperTest.cs | 53 ------- 10 files changed, 91 insertions(+), 265 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs index 49bda15739..08c30ac708 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs @@ -15,6 +15,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// /// implementation targeting <input> elements with an asp-for attribute. /// + [TargetElement("input", Attributes = ForAttributeName)] public class InputTagHelper : TagHelper { private const string ForAttributeName = "asp-for"; @@ -121,93 +122,79 @@ namespace Microsoft.AspNet.Mvc.TagHelpers output.CopyHtmlAttribute(nameof(Value), context); } - 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; + var modelExplorer = For.ModelExplorer; + if (metadata == null) { - // Regular HTML element. Just make sure Format wasn't specified. - if (Format != null) - { - throw new InvalidOperationException(Resources.FormatInputTagHelper_UnableToFormat( - "", - ForAttributeName, - FormatAttributeName)); - } + throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( + "", + ForAttributeName, + nameof(IModelMetadataProvider), + For.Name)); + } + + string inputType; + string inputTypeHint; + if (string.IsNullOrEmpty(InputTypeName)) + { + // Note GetInputType never returns null. + inputType = GetInputType(modelExplorer, out inputTypeHint); } else { - // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient. - // IHtmlGenerator will enforce name requirements. - var metadata = For.Metadata; - var modelExplorer = For.ModelExplorer; - if (metadata == null) - { - throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( - "", - ForAttributeName, - nameof(IModelMetadataProvider), - For.Name)); - } + inputType = InputTypeName.ToLowerInvariant(); + inputTypeHint = null; + } - string inputType; - string inputTypeHint; - if (string.IsNullOrEmpty(InputTypeName)) - { - // Note GetInputType never returns null. - inputType = GetInputType(modelExplorer, out inputTypeHint); - } - else - { - inputType = InputTypeName.ToLowerInvariant(); - inputTypeHint = null; - } + // inputType may be more specific than default the generator chooses below. + if (!output.Attributes.ContainsKey("type")) + { + output.Attributes["type"] = inputType; + } - // inputType may be more specific than default the generator chooses below. - if (!output.Attributes.ContainsKey("type")) - { - output.Attributes["type"] = inputType; - } + TagBuilder tagBuilder; + switch (inputType) + { + case "checkbox": + GenerateCheckBox(modelExplorer, output); + return; - TagBuilder tagBuilder; - switch (inputType) - { - case "checkbox": - GenerateCheckBox(modelExplorer, output); - return; + case "hidden": + tagBuilder = Generator.GenerateHidden( + ViewContext, + modelExplorer, + For.Name, + value: For.Model, + useViewData: false, + htmlAttributes: null); + break; - case "hidden": - tagBuilder = Generator.GenerateHidden( - ViewContext, - modelExplorer, - For.Name, - value: For.Model, - useViewData: false, - htmlAttributes: null); - break; + case "password": + tagBuilder = Generator.GeneratePassword( + ViewContext, + modelExplorer, + For.Name, + value: null, + htmlAttributes: null); + break; - case "password": - tagBuilder = Generator.GeneratePassword( - ViewContext, - modelExplorer, - For.Name, - value: null, - htmlAttributes: null); - break; + case "radio": + tagBuilder = GenerateRadio(modelExplorer); + break; - case "radio": - tagBuilder = GenerateRadio(modelExplorer); - break; + default: + tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType); + break; + } - default: - tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType); - break; - } - - if (tagBuilder != null) - { - // This TagBuilder contains the one element of interest. Since this is not the "checkbox" - // special-case, output is a self-closing element no longer guarunteed. - output.MergeAttributes(tagBuilder); - output.Content.Append(tagBuilder.InnerHtml); - } + if (tagBuilder != null) + { + // This TagBuilder contains the one element of interest. Since this is not the "checkbox" + // special-case, output is a self-closing element no longer guarunteed. + output.MergeAttributes(tagBuilder); + output.Content.Append(tagBuilder.InnerHtml); } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs index e691c018b0..eaedb3f2cd 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/LabelTagHelper.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// /// implementation targeting <label> elements with an asp-for attribute. /// + [TargetElement("label", Attributes = ForAttributeName)] public class LabelTagHelper : TagHelper { private const string ForAttributeName = "asp-for"; @@ -32,34 +33,32 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// Does nothing if is null. public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { - if (For != null) + var tagBuilder = Generator.GenerateLabel( + ViewContext, + For.ModelExplorer, + For.Name, + labelText: null, + htmlAttributes: null); + + if (tagBuilder != null) { - var tagBuilder = Generator.GenerateLabel(ViewContext, - For.ModelExplorer, - For.Name, - labelText: null, - htmlAttributes: null); + output.MergeAttributes(tagBuilder); - if (tagBuilder != null) + // We check for whitespace to detect scenarios such as: + // + if (!output.IsContentModified) { - output.MergeAttributes(tagBuilder); + var childContent = await context.GetChildContentAsync(); - // We check for whitespace to detect scenarios such as: - // - if (!output.IsContentModified) + if (childContent.IsWhiteSpace) { - var childContent = await context.GetChildContentAsync(); - - if (childContent.IsWhiteSpace) - { - // Provide default label text since there was nothing useful in the Razor source. - output.Content.SetContent(tagBuilder.InnerHtml); - } - else - { - output.Content.SetContent(childContent); - } + // Provide default label text since there was nothing useful in the Razor source. + output.Content.SetContent(tagBuilder.InnerHtml); + } + else + { + output.Content.SetContent(childContent); } } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs index c46e3e4940..53da161a39 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs @@ -74,22 +74,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_InvalidExpressionResult"), p0, p1, p2, p3, p4, p5); } - /// - /// Unable to format without a '{1}' expression for {0}. '{2}' must be null if '{1}' is null. - /// - internal static string InputTagHelper_UnableToFormat - { - get { return GetString("InputTagHelper_UnableToFormat"); } - } - - /// - /// Unable to format without a '{1}' expression for {0}. '{2}' must be null if '{1}' is null. - /// - internal static string FormatInputTagHelper_UnableToFormat(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_UnableToFormat"), p0, p1, p2); - } - /// /// '{1}' must not be null for {0} if '{2}' is '{3}'. /// diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx b/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx index ce38a162fc..6a35258fbb 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx @@ -129,9 +129,6 @@ Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'. - - Unable to format without a '{1}' expression for {0}. '{2}' must be null if '{1}' is null. - '{1}' must not be null for {0} if '{2}' is '{3}'. diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs index 2312c8c7ec..f7a9102cf1 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs @@ -8,10 +8,10 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace Microsoft.AspNet.Mvc.TagHelpers { /// - /// implementation targeting <span> elements with an asp-validation-for + /// implementation targeting any HTML element with an asp-validation-for /// attribute. /// - [TargetElement("span")] + [TargetElement("span", Attributes = ValidationForAttributeName)] public class ValidationMessageTagHelper : TagHelper { private const string ValidationForAttributeName = "asp-validation-for"; diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs index 2da0498a95..ab861e509b 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs @@ -8,10 +8,10 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace Microsoft.AspNet.Mvc.TagHelpers { /// - /// implementation targeting <div> elements with an asp-validation-summary + /// implementation targeting any HTML element with an asp-validation-summary /// attribute. /// - [TargetElement("div")] + [TargetElement("div", Attributes = ValidationSummaryAttributeName)] public class ValidationSummaryTagHelper : TagHelper { private const string ValidationSummaryAttributeName = "asp-validation-summary"; diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.Invalid.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.Invalid.html index 7cf694c9e5..44f32743b6 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.Invalid.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.Invalid.html @@ -69,7 +69,7 @@
- +
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.html index f68288557e..120e91115b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Employee.Create.html @@ -67,7 +67,7 @@
- +
diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs index f1351e6f4d..05c644d947 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -617,62 +617,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } - [Fact] - public async Task TagHelper_RestoresTypeAndValue_IfForNotBound() - { - // Arrange - var expectedAttributes = new Dictionary - { - { "class", "form-control" }, - { "type", "datetime" }, - { "value", "2014-10-15T23:24:19.000-7:00" }, - }; - var expectedPreContent = "original pre-content"; - var expectedContent = "original content"; - var expectedPostContent = "original post-content"; - var expectedTagName = "input"; - - var tagHelperContext = new TagHelperContext( - allAttributes: new Dictionary(), - items: new Dictionary(), - uniqueId: "test", - getChildContentAsync: () => - { - var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetContent("Something"); - return Task.FromResult(tagHelperContent); - }); - - var output = new TagHelperOutput(expectedTagName, expectedAttributes) - { - SelfClosing = false, - }; - output.PreContent.SetContent(expectedPreContent); - output.Content.SetContent(expectedContent); - output.PostContent.SetContent(expectedPostContent); - - var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider()) - { - ValidationAttributes = - { - { "valid", "from validation attributes" }, - } - }; - var tagHelper = GetTagHelper(htmlGenerator, model: null, propertyName: nameof(Model.Text)); - tagHelper.For = null; - - // Act - await tagHelper.ProcessAsync(tagHelperContext, output); - - // Assert - Assert.Equal(expectedAttributes, output.Attributes); - Assert.Equal(expectedPreContent, output.PreContent.GetContent()); - Assert.Equal(expectedContent, output.Content.GetContent()); - Assert.Equal(expectedPostContent, output.PostContent.GetContent()); - Assert.False(output.SelfClosing); - Assert.Equal(expectedTagName, output.TagName); - } - private static InputTagHelper GetTagHelper( IHtmlGenerator htmlGenerator, object model, @@ -689,38 +633,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers metadataProvider: metadataProvider); } - [Fact] - public async Task ProcessAsync_Throws_IfForNotBoundButFormatIs() - { - // Arrange - var contextAttributes = new Dictionary(); - var originalAttributes = new Dictionary(); - var expectedTagName = "select"; - var expectedMessage = "Unable to format without a 'asp-for' expression for . 'asp-format' must " + - "be null if 'asp-for' is null."; - - var tagHelperContext = new TagHelperContext( - allAttributes: new Dictionary(), - items: new Dictionary(), - uniqueId: "test", - getChildContentAsync: () => - { - var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetContent("Something"); - return Task.FromResult(tagHelperContent); - }); - var output = new TagHelperOutput(expectedTagName, originalAttributes); - var tagHelper = new InputTagHelper - { - Format = "{0}", - }; - - // Act & Assert - var exception = await Assert.ThrowsAsync( - () => tagHelper.ProcessAsync(tagHelperContext, output)); - Assert.Equal(expectedMessage, exception.Message); - } - private static InputTagHelper GetTagHelper( IHtmlGenerator htmlGenerator, object container, diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs index 032e6d8a86..1733b80fd1 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs @@ -228,59 +228,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } - [Fact] - public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound2() - { - // Arrange - var expectedAttributes = new Dictionary - { - { "class", "form-control" }, - }; - var expectedPreContent = "original pre-content"; - var expectedContent = "original content"; - var expectedPostContent = "original post-content"; - var expectedTagName = "label"; - - 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 LabelTagHelper(); - - var tagHelperContext = new TagHelperContext( - allAttributes: new Dictionary(), - items: new Dictionary(), - uniqueId: "test", - getChildContentAsync: () => - { - var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetContent("Something"); - return Task.FromResult(tagHelperContent); - }); - var output = new TagHelperOutput(expectedTagName, expectedAttributes); - output.PreContent.SetContent(expectedPreContent); - output.Content.SetContent(expectedContent); - output.PostContent.SetContent(expectedPostContent); - - var htmlGenerator = new TestableHtmlGenerator(metadataProvider); - 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(expectedPreContent, output.PreContent.GetContent()); - Assert.Equal(expectedContent, output.Content.GetContent()); - Assert.Equal(expectedPostContent, output.PostContent.GetContent()); - Assert.Equal(expectedTagName, output.TagName); - } - public class TagHelperOutputContent { public TagHelperOutputContent(string originalChildContent,