Added fragment to FormActionTagHelper and FormActionTagHelper

Addresses #4917
This commit is contained in:
Jaspreet Bagga 2016-10-18 09:46:50 -07:00 committed by GitHub
parent 4e1ec39a1f
commit e7c992ff06
7 changed files with 203 additions and 56 deletions

View File

@ -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; }
/// <summary>
/// Gets or sets the URL fragment.
/// </summary>
[HtmlAttributeName(FragmentAttributeName)]
public string Fragment { get; set; }
/// <summary>
/// Name of the route.
/// </summary>
@ -134,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// <remarks>Does nothing if user provides an <c>formaction</c> attribute.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <c>formaction</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>,
/// or <see cref="Route"/> are non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes.
/// <see cref="Fragment"/> or <see cref="Route"/> are non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes.
/// Also thrown if <see cref="Route"/> and one or both of <see cref="Action"/> and <see cref="Controller"/>
/// are non-<c>null</c>
/// </exception>
@ -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);
}
}

View File

@ -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; }
/// <summary>
/// Gets or sets the URL fragment.
/// </summary>
[HtmlAttributeName(FragmentAttributeName)]
public string Fragment { get; set; }
/// <summary>
/// Name of the route.
/// </summary>
@ -121,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// <c>false</c>.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <c>action</c> attribute is provided and <see cref="Action"/> or <see cref="Controller"/> are
/// Thrown if <c>action</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/> or <see cref="Fragment"/> are
/// non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes.
/// </exception>
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);
}

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// 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.
/// </summary>
internal static string FormTagHelper_CannotOverrideAction
{
@ -51,11 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
@ -147,11 +147,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
@ -179,11 +179,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// Cannot override the '{6}' attribute for &lt;{0}&gt;. &lt;{0}&gt; 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 &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{7}' must not have attributes starting with '{6}' or an '{1}', '{2}', '{3}', '{4}', or '{5}' attribute.
/// </summary>
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);
}
/// <summary>
@ -195,11 +195,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// Cannot determine a '{4}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{1}' must not have an '{2}' or '{3}' attribute.
/// Cannot determine a '{4}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute.
/// </summary>
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)

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -124,7 +124,7 @@
<value>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.</value>
</data>
<data name="FormTagHelper_CannotOverrideAction" xml:space="preserve">
<value>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.</value>
<value>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.</value>
</data>
<data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve">
<value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value>
@ -142,15 +142,15 @@
<value>The attribute '{0}' does not exist in the {1}.</value>
</data>
<data name="FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified" xml:space="preserve">
<value>Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
<value>Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute.</value>
</data>
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
<value>The '{0}' property of '{1}' must not be null.</value>
</data>
<data name="FormActionTagHelper_CannotOverrideFormAction" xml:space="preserve">
<value>Cannot override the '{6}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.</value>
<value>Cannot override the '{7}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{7}' must not have attributes starting with '{6}' or an '{1}', '{2}', '{3}', '{4}', or '{5}' attribute.</value>
</data>
<data name="FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified" xml:space="preserve">
<value>Cannot determine a '{4}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
<value>Cannot determine a '{4}' attribute for &lt;{0}&gt;. &lt;{0}&gt; elements with a specified '{1}' must not have an '{2}', '{3}', or '{5}' attribute.</value>
</data>
</root>

View File

@ -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;
}
}
}

View File

@ -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<KeyValuePair<string, object>>(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<UrlRouteContext>()))
.Callback<UrlRouteContext>(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<KeyValuePair<string, object>>(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<TagHelperContent>(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(

View File

@ -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<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList
{
{ "id", "myform" },
},
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var urlHelper = new Mock<IUrlHelper>();
urlHelper
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>())).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 <form>. A <form> 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<TagHelperContent>(null));
var expectedErrorMessage = "Cannot determine an 'action' attribute for <form>. A <form> 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(