From e7c992ff06683e6666b7ee03ff374809195a9c7c Mon Sep 17 00:00:00 2001 From: Jaspreet Bagga Date: Tue, 18 Oct 2016 09:46:50 -0700 Subject: [PATCH] Added fragment to FormActionTagHelper and FormActionTagHelper Addresses #4917 --- .../FormActionTagHelper.cs | 23 ++++-- .../FormTagHelper.cs | 17 ++++- .../Properties/Resources.Designer.cs | 26 +++---- .../Resources.resx | 62 ++++++++-------- .../DefaultHtmlGeneratorExtensions.cs | 51 ++++++++++++++ .../FormActionTagHelperTest.cs | 10 +-- .../FormTagHelperTest.cs | 70 ++++++++++++++++++- 7 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs index 4c4ba9c8d0..1b7cc1e401 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs @@ -18,18 +18,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("button", Attributes = ActionAttributeName)] [HtmlTargetElement("button", Attributes = ControllerAttributeName)] [HtmlTargetElement("button", Attributes = AreaAttributeName)] + [HtmlTargetElement("button", Attributes = FragmentAttributeName)] [HtmlTargetElement("button", Attributes = RouteAttributeName)] [HtmlTargetElement("button", Attributes = RouteValuesDictionaryName)] [HtmlTargetElement("button", Attributes = RouteValuesPrefix + "*")] [HtmlTargetElement("input", Attributes = ImageActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = ImageControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = ImageAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = ImageRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = ImageRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = ImageRouteValuesSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("input", Attributes = SubmitRouteValuesSelector, TagStructure = TagStructure.WithoutEndTag)] @@ -38,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string ActionAttributeName = "asp-action"; private const string AreaAttributeName = "asp-area"; private const string ControllerAttributeName = "asp-controller"; + private const string FragmentAttributeName = "asp-fragment"; private const string RouteAttributeName = "asp-route"; private const string RouteValuesDictionaryName = "asp-all-route-data"; private const string RouteValuesPrefix = "asp-route-"; @@ -46,6 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string ImageTypeSelector = "[type=image], "; private const string ImageActionAttributeSelector = ImageTypeSelector + ActionAttributeName; private const string ImageAreaAttributeSelector = ImageTypeSelector + AreaAttributeName; + private const string ImageFragmentAttributeSelector = ImageTypeSelector + FragmentAttributeName; private const string ImageControllerAttributeSelector = ImageTypeSelector + ControllerAttributeName; private const string ImageRouteAttributeSelector = ImageTypeSelector + RouteAttributeName; private const string ImageRouteValuesDictionarySelector = ImageTypeSelector + RouteValuesDictionaryName; @@ -54,6 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string SubmitTypeSelector = "[type=submit], "; private const string SubmitActionAttributeSelector = SubmitTypeSelector + ActionAttributeName; private const string SubmitAreaAttributeSelector = SubmitTypeSelector + AreaAttributeName; + private const string SubmitFragmentAttributeSelector = SubmitTypeSelector + FragmentAttributeName; private const string SubmitControllerAttributeSelector = SubmitTypeSelector + ControllerAttributeName; private const string SubmitRouteAttributeSelector = SubmitTypeSelector + RouteAttributeName; private const string SubmitRouteValuesDictionarySelector = SubmitTypeSelector + RouteValuesDictionaryName; @@ -100,6 +106,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(AreaAttributeName)] public string Area { get; set; } + /// + /// Gets or sets the URL fragment. + /// + [HtmlAttributeName(FragmentAttributeName)] + public string Fragment { get; set; } + /// /// Name of the route. /// @@ -134,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// Does nothing if user provides an formaction attribute. /// /// Thrown if formaction attribute is provided and , , - /// or are non-null or if the user provided asp-route-* attributes. + /// or are non-null or if the user provided asp-route-* attributes. /// Also thrown if and one or both of and /// are non-null /// @@ -156,6 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers if (Action != null || Controller != null || Area != null || + Fragment != null || Route != null || (_routeValues != null && _routeValues.Count > 0)) { @@ -167,6 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers ActionAttributeName, ControllerAttributeName, AreaAttributeName, + FragmentAttributeName, RouteAttributeName, RouteValuesPrefix, FormAction)); @@ -194,7 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers if (Route == null) { var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); - var url = urlHelper.Action(Action, Controller, routeValues); + var url = urlHelper.Action(Action, Controller, routeValues, protocol: null, host: null, fragment: Fragment); output.Attributes.SetAttribute(FormAction, url); } else if (Action != null || Controller != null) @@ -206,12 +220,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers RouteAttributeName, ActionAttributeName, ControllerAttributeName, - FormAction)); + FormAction, + FragmentAttributeName)); } else { var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); - var url = urlHelper.RouteUrl(Route, routeValues); + var url = urlHelper.RouteUrl(Route, routeValues, protocol: null, host: null, fragment: Fragment); output.Attributes.SetAttribute(FormAction, url); } } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs index 2b9d480c25..3b00906fcb 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("form", Attributes = ActionAttributeName)] [HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)] [HtmlTargetElement("form", Attributes = AreaAttributeName)] + [HtmlTargetElement("form", Attributes = FragmentAttributeName)] [HtmlTargetElement("form", Attributes = ControllerAttributeName)] [HtmlTargetElement("form", Attributes = RouteAttributeName)] [HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)] @@ -25,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string ActionAttributeName = "asp-action"; private const string AntiforgeryAttributeName = "asp-antiforgery"; private const string AreaAttributeName = "asp-area"; + private const string FragmentAttributeName = "asp-fragment"; private const string ControllerAttributeName = "asp-controller"; private const string RouteAttributeName = "asp-route"; private const string RouteValuesDictionaryName = "asp-all-route-data"; @@ -78,6 +80,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(AntiforgeryAttributeName)] public bool? Antiforgery { get; set; } + /// + /// Gets or sets the URL fragment. + /// + [HtmlAttributeName(FragmentAttributeName)] + public string Fragment { get; set; } + /// /// Name of the route. /// @@ -121,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// false. /// /// - /// Thrown if action attribute is provided and or are + /// Thrown if action attribute is provided and , or are /// non-null or if the user provided asp-route-* attributes. /// public override void Process(TagHelperContext context, TagHelperOutput output) @@ -148,6 +156,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers if (Action != null || Controller != null || Area != null || + Fragment != null || Route != null || (_routeValues != null && _routeValues.Count > 0)) { @@ -158,6 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers HtmlActionAttributeName, ActionAttributeName, ControllerAttributeName, + FragmentAttributeName, AreaAttributeName, RouteAttributeName, RouteValuesPrefix)); @@ -198,6 +208,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers ViewContext, Action, Controller, + Fragment, routeValues, method: null, htmlAttributes: null); @@ -211,7 +222,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers RouteAttributeName, ActionAttributeName, ControllerAttributeName, - HtmlActionAttributeName)); + HtmlActionAttributeName, + FragmentAttributeName)); } else { @@ -219,6 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers ViewContext, Route, routeValues, + Fragment, method: null, htmlAttributes: null); } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs index bd92f4d69e..d2d8502c6f 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute. + /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute. /// internal static string FormTagHelper_CannotOverrideAction { @@ -51,11 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute. + /// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute. /// - internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6) + internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7) { - return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5, p6); + return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5, p6, p7); } /// @@ -147,11 +147,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. + /// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute. /// - internal static string FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4) + internal static string FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4, object p5) { - return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"), p0, p1, p2, p3, p4); + return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"), p0, p1, p2, p3, p4, p5); } /// @@ -179,11 +179,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Cannot override the '{6}' attribute for <{0}>. <{0}> elements with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute. + /// Cannot override the '{6}' attribute for <{0}>. <{0}> elements with a specified '{7}' must not have attributes starting with '{6}' or an '{1}', '{2}', '{3}', '{4}', or '{5}' attribute. /// - internal static string FormatFormActionTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6) + internal static string FormatFormActionTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7) { - return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6); + return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6, p7); } /// @@ -195,11 +195,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}' or '{3}' attribute. + /// Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute. /// - internal static string FormatFormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4) + internal static string FormatFormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4, object p5) { - return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4); + return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4, p5); } private static string GetString(string name, params string[] formatterNames) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx index 86fe55cc08..ade3082337 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx @@ -1,17 +1,17 @@  - @@ -124,7 +124,7 @@ Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute. - Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute. + Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute. Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'. @@ -142,15 +142,15 @@ The attribute '{0}' does not exist in the {1}. - Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. + Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute. The '{0}' property of '{1}' must not be null. - Cannot override the '{6}' attribute for <{0}>. <{0}> elements with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute. + Cannot override the '{7}' attribute for <{0}>. <{0}> elements with a specified '{7}' must not have attributes starting with '{6}' or an '{1}', '{2}', '{3}', '{4}', or '{5}' attribute. - Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}' or '{3}' attribute. + Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute. \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs new file mode 100644 index 0000000000..fd58824cd0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + public static class DefaultHtmlGeneratorExtensions + { + public static TagBuilder GenerateForm( + this IHtmlGenerator generator, + ViewContext viewContext, + string actionName, + string controllerName, + string fragment, + object routeValues, + string method, + object htmlAttributes) + { + var tagBuilder = generator.GenerateForm(viewContext, actionName, controllerName, routeValues, method, htmlAttributes); + + // Append the fragment to action + if (fragment != null) + { + tagBuilder.Attributes["action"] += "#" + fragment; + } + + return tagBuilder; + } + + public static TagBuilder GenerateRouteForm( + this IHtmlGenerator generator, + ViewContext viewContext, + string routeName, + object routeValues, + string fragment, + string method, + object htmlAttributes) + { + var tagBuilder = generator.GenerateRouteForm(viewContext, routeName, routeValues, method, htmlAttributes); + + // Append the fragment to action + if (fragment != null) + { + tagBuilder.Attributes["action"] += "#" + fragment; + } + + return tagBuilder; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs index ab574e8b18..bfe589702e 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs @@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { Assert.Equal("delete", param.Action, StringComparer.Ordinal); Assert.Equal("books", param.Controller, StringComparer.Ordinal); - Assert.Null(param.Fragment); + Assert.Equal("test", param.Fragment, StringComparer.Ordinal); Assert.Null(param.Host); Assert.Null(param.Protocol); Assert.Equal>(expectedRouteValues, param.Values as RouteValueDictionary); @@ -238,6 +238,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { Action = "delete", Controller = "books", + Fragment = "test", RouteValues = routeValues, ViewContext = viewContext, }; @@ -277,9 +278,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers .Setup(mock => mock.RouteUrl(It.IsAny())) .Callback(param => { - Assert.Null(param.Fragment); Assert.Null(param.Host); Assert.Null(param.Protocol); + Assert.Equal("test", param.Fragment, StringComparer.Ordinal); Assert.Equal("Default", param.RouteName, StringComparer.Ordinal); Assert.Equal>(expectedRouteValues, param.Values as RouteValueDictionary); }) @@ -294,6 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var tagHelper = new FormActionTagHelper(urlHelperFactory.Object) { Route = "Default", + Fragment = "test", RouteValues = routeValues, ViewContext = viewContext, }; @@ -467,7 +469,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var expectedErrorMessage = $"Cannot override the 'formaction' attribute for <{tagName}>. <{tagName}> " + "elements with a specified 'formaction' must not have attributes starting with 'asp-route-' or an " + - "'asp-action', 'asp-controller', 'asp-area', or 'asp-route' attribute."; + "'asp-action', 'asp-controller', 'asp-area', 'asp-fragment', or 'asp-route' attribute."; var context = new TagHelperContext( allAttributes: new TagHelperAttributeList( @@ -502,7 +504,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers attributes: new TagHelperAttributeList(), getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(null)); var expectedErrorMessage = $"Cannot determine a 'formaction' attribute for <{tagName}>. <{tagName}> " + - "elements with a specified 'asp-route' must not have an 'asp-action' or 'asp-controller' attribute."; + "elements with a specified 'asp-route' must not have an 'asp-action', 'asp-controller', or 'asp-fragment' attribute."; var context = new TagHelperContext( allAttributes: new TagHelperAttributeList( diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 6ef2efc11a..5879eca4c0 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -284,6 +284,72 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Empty(output.PostContent.GetContent()); } + [Fact] + public async Task ProcessAsync_AspFragmentAddsFragmentToAction() + { + // Arrange + var expectedTagName = "form"; + var metadataProvider = new TestModelMetadataProvider(); + var tagHelperContext = new TagHelperContext( + allAttributes: new TagHelperAttributeList + { + { "id", "myform" }, + { "asp-route-name", "value" }, + { "asp-action", "index" }, + { "asp-controller", "home" }, + { "asp-fragment", "test" }, + { "method", "post" }, + { "asp-antiforgery", true } + }, + items: new Dictionary(), + uniqueId: "test"); + + var output = new TagHelperOutput( + expectedTagName, + attributes: new TagHelperAttributeList + { + { "id", "myform" }, + }, + getChildContentAsync: (useCachedResult, encoder) => + { + var tagHelperContent = new DefaultTagHelperContent(); + return Task.FromResult(tagHelperContent); + }); + + var urlHelper = new Mock(); + urlHelper + .Setup(mock => mock.Action(It.IsAny())).Returns("home/index"); + + var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object); + var viewContext = TestableHtmlGenerator.GetViewContext( + model: null, + htmlGenerator: htmlGenerator, + metadataProvider: metadataProvider); + + var formTagHelper = new FormTagHelper(htmlGenerator) + { + Action = "index", + Antiforgery = true, + Controller = "home", + Fragment = "test", + ViewContext = viewContext, + RouteValues = + { + { "name", "value" }, + }, + }; + + // Act + await formTagHelper.ProcessAsync(tagHelperContext, output); + + // Assert + + Assert.Equal("form", output.TagName); + Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode); + var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("action")); + Assert.Equal("home/index#test", attribute.Value); + } + [Fact] public async Task ProcessAsync_AspAreaAddsAreaToRouteValues() { @@ -585,7 +651,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var expectedErrorMessage = "Cannot override the 'action' attribute for
. A with a specified " + "'action' must not have attributes starting with 'asp-route-' or an " + - "'asp-action' or 'asp-controller' or 'asp-area' or 'asp-route' attribute."; + "'asp-action', 'asp-controller', 'asp-fragment', 'asp-area', or 'asp-route' attribute."; var context = new TagHelperContext( allAttributes: new TagHelperAttributeList( @@ -616,7 +682,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers attributes: new TagHelperAttributeList(), getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(null)); var expectedErrorMessage = "Cannot determine an 'action' attribute for . A with a specified " + - "'asp-route' must not have an 'asp-action' or 'asp-controller' attribute."; + "'asp-route' must not have an 'asp-action', 'asp-controller', or 'asp-fragment' attribute."; var context = new TagHelperContext( allAttributes: new TagHelperAttributeList(