Build on be73cd7 to include `<input type="image"/>`, reduce duplication, and add tests
- consolidate `ButtonTagHelper` and `SubmitTagHelper` into `FormActionTagHelper` - consolidate `ButtonTagHelperTest` and `SubmitTagHelperTest` into `FormActionTagHelperTest` nits: - do not allocate dictionaries in the `<a>` or `<form>` tag helpers unless needed - clean up some hard-to-maintain whitespace
This commit is contained in:
parent
be73cd77bf
commit
2659904061
|
|
@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
Protocol != null ||
|
Protocol != null ||
|
||||||
Host != null ||
|
Host != null ||
|
||||||
Fragment != null ||
|
Fragment != null ||
|
||||||
RouteValues.Count != 0)
|
(_routeValues != null && _routeValues.Count > 0))
|
||||||
{
|
{
|
||||||
// User specified an href and one of the bound attributes; can't determine the href attribute.
|
// User specified an href and one of the bound attributes; can't determine the href attribute.
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
@ -194,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
routeValues = new RouteValueDictionary();
|
routeValues = new RouteValueDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unconditionally replace any value from asp-route-area.
|
// Unconditionally replace any value from asp-route-area.
|
||||||
routeValues["area"] = Area;
|
routeValues["area"] = Area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ using Microsoft.AspNetCore.Routing;
|
||||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="ITagHelper"/> implementation targeting <button> elements.
|
/// <see cref="ITagHelper"/> implementation targeting <button> elements and <input> elements with
|
||||||
|
/// their <c>type</c> attribute set to <c>image</c> or <c>submit</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HtmlTargetElement("button", Attributes = ActionAttributeName)]
|
[HtmlTargetElement("button", Attributes = ActionAttributeName)]
|
||||||
[HtmlTargetElement("button", Attributes = ControllerAttributeName)]
|
[HtmlTargetElement("button", Attributes = ControllerAttributeName)]
|
||||||
|
|
@ -20,22 +21,51 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
[HtmlTargetElement("button", Attributes = RouteAttributeName)]
|
[HtmlTargetElement("button", Attributes = RouteAttributeName)]
|
||||||
[HtmlTargetElement("button", Attributes = RouteValuesDictionaryName)]
|
[HtmlTargetElement("button", Attributes = RouteValuesDictionaryName)]
|
||||||
[HtmlTargetElement("button", Attributes = RouteValuesPrefix + "*")]
|
[HtmlTargetElement("button", Attributes = RouteValuesPrefix + "*")]
|
||||||
public class ButtonTagHelper : TagHelper
|
[HtmlTargetElement("input", Attributes = ImageActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
|
||||||
|
[HtmlTargetElement("input", Attributes = ImageControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
|
||||||
|
[HtmlTargetElement("input", Attributes = ImageAreaAttributeSelector, 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 = SubmitRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
|
||||||
|
[HtmlTargetElement("input", Attributes = SubmitRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)]
|
||||||
|
[HtmlTargetElement("input", Attributes = SubmitRouteValuesSelector, TagStructure = TagStructure.WithoutEndTag)]
|
||||||
|
public class FormActionTagHelper : TagHelper
|
||||||
{
|
{
|
||||||
private const string ActionAttributeName = "asp-action";
|
private const string ActionAttributeName = "asp-action";
|
||||||
private const string ControllerAttributeName = "asp-controller";
|
|
||||||
private const string AreaAttributeName = "asp-area";
|
private const string AreaAttributeName = "asp-area";
|
||||||
|
private const string ControllerAttributeName = "asp-controller";
|
||||||
private const string RouteAttributeName = "asp-route";
|
private const string RouteAttributeName = "asp-route";
|
||||||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
||||||
private const string RouteValuesPrefix = "asp-route-";
|
private const string RouteValuesPrefix = "asp-route-";
|
||||||
private const string FormAction = "formaction";
|
private const string FormAction = "formaction";
|
||||||
|
|
||||||
|
private const string ImageTypeSelector = "[type=image], ";
|
||||||
|
private const string ImageActionAttributeSelector = ImageTypeSelector + ActionAttributeName;
|
||||||
|
private const string ImageAreaAttributeSelector = ImageTypeSelector + AreaAttributeName;
|
||||||
|
private const string ImageControllerAttributeSelector = ImageTypeSelector + ControllerAttributeName;
|
||||||
|
private const string ImageRouteAttributeSelector = ImageTypeSelector + RouteAttributeName;
|
||||||
|
private const string ImageRouteValuesDictionarySelector = ImageTypeSelector + RouteValuesDictionaryName;
|
||||||
|
private const string ImageRouteValuesSelector = ImageTypeSelector + RouteValuesPrefix + "*";
|
||||||
|
|
||||||
|
private const string SubmitTypeSelector = "[type=submit], ";
|
||||||
|
private const string SubmitActionAttributeSelector = SubmitTypeSelector + ActionAttributeName;
|
||||||
|
private const string SubmitAreaAttributeSelector = SubmitTypeSelector + AreaAttributeName;
|
||||||
|
private const string SubmitControllerAttributeSelector = SubmitTypeSelector + ControllerAttributeName;
|
||||||
|
private const string SubmitRouteAttributeSelector = SubmitTypeSelector + RouteAttributeName;
|
||||||
|
private const string SubmitRouteValuesDictionarySelector = SubmitTypeSelector + RouteValuesDictionaryName;
|
||||||
|
private const string SubmitRouteValuesSelector = SubmitTypeSelector + RouteValuesPrefix + "*";
|
||||||
|
|
||||||
private IDictionary<string, string> _routeValues;
|
private IDictionary<string, string> _routeValues;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ButtonTagHelper"/>.
|
/// Creates a new <see cref="FormActionTagHelper"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
|
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
|
||||||
public ButtonTagHelper(IUrlHelperFactory urlHelperFactory)
|
public FormActionTagHelper(IUrlHelperFactory urlHelperFactory)
|
||||||
{
|
{
|
||||||
UrlHelperFactory = urlHelperFactory;
|
UrlHelperFactory = urlHelperFactory;
|
||||||
}
|
}
|
||||||
|
|
@ -120,15 +150,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
throw new ArgumentNullException(nameof(output));
|
throw new ArgumentNullException(nameof(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "formaction" is already set, it means the user is attempting to use a normal button.
|
// If "formaction" is already set, it means the user is attempting to use a normal button or input element.
|
||||||
if (output.Attributes.ContainsName(FormAction))
|
if (output.Attributes.ContainsName(FormAction))
|
||||||
{
|
{
|
||||||
if (Action != null || Controller != null || Area != null || Route != null || RouteValues.Count != 0)
|
if (Action != null ||
|
||||||
|
Controller != null ||
|
||||||
|
Area != null ||
|
||||||
|
Route != null ||
|
||||||
|
(_routeValues != null && _routeValues.Count > 0))
|
||||||
{
|
{
|
||||||
// User specified a formaction and one of the bound attributes; can't determine the formaction attribute.
|
// User specified a formaction and one of the bound attributes; can't override that formaction
|
||||||
|
// attribute.
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
Resources.FormatButtonTagHelper_CannotOverrideFormAction(
|
Resources.FormatFormActionTagHelper_CannotOverrideFormAction(
|
||||||
"<button>",
|
output.TagName,
|
||||||
ActionAttributeName,
|
ActionAttributeName,
|
||||||
ControllerAttributeName,
|
ControllerAttributeName,
|
||||||
AreaAttributeName,
|
AreaAttributeName,
|
||||||
|
|
@ -152,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
routeValues = new RouteValueDictionary();
|
routeValues = new RouteValueDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unconditionally replace any value from asp-route-area.
|
// Unconditionally replace any value from asp-route-area.
|
||||||
routeValues["area"] = Area;
|
routeValues["area"] = Area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,8 +201,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
{
|
{
|
||||||
// Route and Action or Controller were specified. Can't determine the formaction attribute.
|
// Route and Action or Controller were specified. Can't determine the formaction attribute.
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
Resources.FormatButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(
|
Resources.FormatFormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(
|
||||||
"<button>",
|
output.TagName,
|
||||||
RouteAttributeName,
|
RouteAttributeName,
|
||||||
ActionAttributeName,
|
ActionAttributeName,
|
||||||
ControllerAttributeName,
|
ControllerAttributeName,
|
||||||
|
|
@ -145,7 +145,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
// If "action" is already set, it means the user is attempting to use a normal <form>.
|
// If "action" is already set, it means the user is attempting to use a normal <form>.
|
||||||
if (output.Attributes.ContainsName(HtmlActionAttributeName))
|
if (output.Attributes.ContainsName(HtmlActionAttributeName))
|
||||||
{
|
{
|
||||||
if (Action != null || Controller != null || Area != null || Route != null || RouteValues.Count != 0)
|
if (Action != null ||
|
||||||
|
Controller != null ||
|
||||||
|
Area != null ||
|
||||||
|
Route != null ||
|
||||||
|
(_routeValues != null && _routeValues.Count > 0))
|
||||||
{
|
{
|
||||||
// User also specified bound attributes we cannot use.
|
// User also specified bound attributes we cannot use.
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
|
|
|
||||||
|
|
@ -42,70 +42,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
|
return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string ButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified
|
|
||||||
{
|
|
||||||
get { return GetString("ButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine an '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot override the '{6}' attribute for {0}. A {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string ButtonTagHelper_CannotOverrideFormAction
|
|
||||||
{
|
|
||||||
get { return GetString("ButtonTagHelper_CannotOverrideFormAction"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot override the '{6}' attribute for {0}. A {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatButtonTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ButtonTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string SubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified
|
|
||||||
{
|
|
||||||
get { return GetString("SubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatSubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("SubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot override the '{6}' attribute for {0}. An {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string SubmitTagHelper_CannotOverrideFormAction
|
|
||||||
{
|
|
||||||
get { return GetString("SubmitTagHelper_CannotOverrideFormAction"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cannot override the '{6}' attribute for {0}. An {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatSubmitTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("SubmitTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <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 '{6}' or an '{2}' or '{3}' or '{4}' or '{5}' attribute.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -234,6 +170,38 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1);
|
return string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormActionTagHelper_CannotOverrideFormAction
|
||||||
|
{
|
||||||
|
get { return GetString("FormActionTagHelper_CannotOverrideFormAction"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatFormActionTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified
|
||||||
|
{
|
||||||
|
get { return GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}' or '{3}' attribute.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatFormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
@ -147,16 +147,10 @@
|
||||||
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
<data name="PropertyOfTypeCannotBeNull" xml:space="preserve">
|
||||||
<value>The '{0}' property of '{1}' must not be null.</value>
|
<value>The '{0}' property of '{1}' must not be null.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ButtonTagHelper_CannotOverrideFormAction" xml:space="preserve">
|
<data name="FormActionTagHelper_CannotOverrideFormAction" xml:space="preserve">
|
||||||
<value>Cannot override the '{6}' attribute for {0}. A {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.</value>
|
<value>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.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ButtonTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified" xml:space="preserve">
|
<data name="FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified" xml:space="preserve">
|
||||||
<value>Cannot determine a '{4}' attribute for {0}. A {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
|
<value>Cannot determine a '{4}' attribute for <{0}>. <{0}> elements with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
|
||||||
</data>
|
|
||||||
<data name="SubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified" xml:space="preserve">
|
|
||||||
<value>Cannot determine a '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
|
|
||||||
</data>
|
|
||||||
<data name="SubmitTagHelper_CannotOverrideFormAction" xml:space="preserve">
|
|
||||||
<value>Cannot override the '{6}' attribute for {0}. An {0} with a specified '{6}' must not have attributes starting with '{5}' or an '{1}', '{2}', '{3}', or '{4}' attribute.</value>
|
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
// 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 System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="ITagHelper"/> implementation targeting <input> elements with <c>type</c> attribute set to <c>submit</c>.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlTargetElement("input", Attributes = ActionAttributeSelector)]
|
|
||||||
[HtmlTargetElement("input", Attributes = ControllerAttributeSelector)]
|
|
||||||
[HtmlTargetElement("input", Attributes = AreaAttributeSelector)]
|
|
||||||
[HtmlTargetElement("input", Attributes = RouteAttributeSelector)]
|
|
||||||
[HtmlTargetElement("input", Attributes = RouteValuesDictionarySelector)]
|
|
||||||
[HtmlTargetElement("input", Attributes = RouteValuesSelector)]
|
|
||||||
public class SubmitTagHelper : TagHelper
|
|
||||||
{
|
|
||||||
private const string ActionAttributeSelector = TypeSelector + ActionAttributeName;
|
|
||||||
private const string ControllerAttributeSelector = TypeSelector + ControllerAttributeName;
|
|
||||||
private const string AreaAttributeSelector = TypeSelector + AreaAttributeName;
|
|
||||||
private const string RouteAttributeSelector = TypeSelector + RouteAttributeName;
|
|
||||||
private const string RouteValuesDictionarySelector = TypeSelector + RouteValuesDictionaryName;
|
|
||||||
private const string RouteValuesSelector = TypeSelector + RouteValuesPrefix + "*";
|
|
||||||
private const string TypeSelector = "[type=submit], ";
|
|
||||||
|
|
||||||
private const string ActionAttributeName = "asp-action";
|
|
||||||
private const string ControllerAttributeName = "asp-controller";
|
|
||||||
private const string AreaAttributeName = "asp-area";
|
|
||||||
private const string RouteAttributeName = "asp-route";
|
|
||||||
private const string RouteValuesDictionaryName = "asp-all-route-data";
|
|
||||||
private const string RouteValuesPrefix = "asp-route-";
|
|
||||||
private const string FormAction = "formaction";
|
|
||||||
private IDictionary<string, string> _routeValues;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="SubmitTagHelper"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
|
|
||||||
public SubmitTagHelper(IUrlHelperFactory urlHelperFactory)
|
|
||||||
{
|
|
||||||
UrlHelperFactory = urlHelperFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int Order => -1000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="Rendering.ViewContext"/> for the current request.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlAttributeNotBound]
|
|
||||||
[ViewContext]
|
|
||||||
public ViewContext ViewContext { get; set; }
|
|
||||||
|
|
||||||
protected IUrlHelperFactory UrlHelperFactory { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the action method.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlAttributeName(ActionAttributeName)]
|
|
||||||
public string Action { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the controller.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlAttributeName(ControllerAttributeName)]
|
|
||||||
public string Controller { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the area.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlAttributeName(AreaAttributeName)]
|
|
||||||
public string Area { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the route.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
|
|
||||||
/// </remarks>
|
|
||||||
[HtmlAttributeName(RouteAttributeName)]
|
|
||||||
public string Route { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Additional parameters for the route.
|
|
||||||
/// </summary>
|
|
||||||
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)]
|
|
||||||
public IDictionary<string, string> RouteValues
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_routeValues == null)
|
|
||||||
{
|
|
||||||
_routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _routeValues;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_routeValues = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <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.
|
|
||||||
/// Also thrown if <see cref="Route"/> and one or both of <see cref="Action"/> and <see cref="Controller"/>
|
|
||||||
/// are non-<c>null</c>
|
|
||||||
/// </exception>
|
|
||||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(output));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If "formaction" is already set, it means the user is attempting to use a normal submit input.
|
|
||||||
if (output.Attributes.ContainsName(FormAction))
|
|
||||||
{
|
|
||||||
if (Action != null || Controller != null || Area != null || Route != null || RouteValues.Count != 0)
|
|
||||||
{
|
|
||||||
// User specified a formaction and one of the bound attributes; can't determine the formaction attribute.
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatSubmitTagHelper_CannotOverrideFormAction(
|
|
||||||
"<input>",
|
|
||||||
ActionAttributeName,
|
|
||||||
ControllerAttributeName,
|
|
||||||
AreaAttributeName,
|
|
||||||
RouteAttributeName,
|
|
||||||
RouteValuesPrefix,
|
|
||||||
FormAction));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RouteValueDictionary routeValues = null;
|
|
||||||
if (_routeValues != null && _routeValues.Count > 0)
|
|
||||||
{
|
|
||||||
routeValues = new RouteValueDictionary(_routeValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Area != null)
|
|
||||||
{
|
|
||||||
if (routeValues == null)
|
|
||||||
{
|
|
||||||
routeValues = new RouteValueDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unconditionally replace any value from asp-route-area.
|
|
||||||
routeValues["area"] = Area;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Route == null)
|
|
||||||
{
|
|
||||||
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
|
|
||||||
var url = urlHelper.Action(Action, Controller, routeValues);
|
|
||||||
output.Attributes.SetAttribute(FormAction, url);
|
|
||||||
}
|
|
||||||
else if (Action != null || Controller != null)
|
|
||||||
{
|
|
||||||
// Route and Action or Controller were specified. Can't determine the formaction attribute.
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
Resources.FormatSubmitTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(
|
|
||||||
"<input>",
|
|
||||||
RouteAttributeName,
|
|
||||||
ActionAttributeName,
|
|
||||||
ControllerAttributeName,
|
|
||||||
FormAction));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
|
|
||||||
var url = urlHelper.RouteUrl(Route, routeValues);
|
|
||||||
output.Attributes.SetAttribute(FormAction, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -57,9 +57,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>())).Returns("home/index");
|
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>())).Returns("home/index");
|
||||||
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
var viewContext = TestableHtmlGenerator.GetViewContext(
|
||||||
htmlGenerator: htmlGenerator,
|
model: null,
|
||||||
metadataProvider: metadataProvider);
|
htmlGenerator: htmlGenerator,
|
||||||
|
metadataProvider: metadataProvider);
|
||||||
var anchorTagHelper = new AnchorTagHelper(htmlGenerator)
|
var anchorTagHelper = new AnchorTagHelper(htmlGenerator)
|
||||||
{
|
{
|
||||||
Action = "index",
|
Action = "index",
|
||||||
|
|
|
||||||
|
|
@ -1,381 +0,0 @@
|
||||||
// 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 System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
{
|
|
||||||
public class ButtonTagHelperTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_GeneratesExpectedOutput()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedTagName = "not-button";
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
|
|
||||||
var tagHelperContext = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "id", "mybutton" },
|
|
||||||
{ "asp-route-name", "value" },
|
|
||||||
{ "asp-action", "index" },
|
|
||||||
{ "asp-controller", "home" },
|
|
||||||
},
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
expectedTagName,
|
|
||||||
attributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "id", "mybutton" },
|
|
||||||
},
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something Else");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent("Something");
|
|
||||||
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>())).Returns("home/index").Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
|
||||||
htmlGenerator: htmlGenerator,
|
|
||||||
metadataProvider: metadataProvider);
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "index",
|
|
||||||
Controller = "home",
|
|
||||||
RouteValues =
|
|
||||||
{
|
|
||||||
{ "name", "value" },
|
|
||||||
},
|
|
||||||
ViewContext = viewContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await buttonTagHelper.ProcessAsync(tagHelperContext, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal(2, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("id"));
|
|
||||||
Assert.Equal("mybutton", attribute.Value);
|
|
||||||
attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("home/index", attribute.Value);
|
|
||||||
Assert.Equal("Something", output.Content.GetContent());
|
|
||||||
Assert.Equal(expectedTagName, output.TagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.RouteUrl(It.IsAny<UrlRouteContext>())).Returns("home/index").Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
|
||||||
htmlGenerator: htmlGenerator,
|
|
||||||
metadataProvider: metadataProvider);
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Route = "Default",
|
|
||||||
ViewContext = viewContext,
|
|
||||||
RouteValues =
|
|
||||||
{
|
|
||||||
{ "name", "value" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await buttonTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("button", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("home/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_AddsAreaToRouteValuesAndCallsIntoActionLinkWithExpectedParameters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", "Admin" } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = "Admin",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await buttonTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("button", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_AspAreaOverridesAspRouteArea()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", "Admin" } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = "Admin",
|
|
||||||
RouteValues = new Dictionary<string, string> { { "area", "Home" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await buttonTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("button", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_EmptyStringOnAspAreaIsPassedThroughToRouteValues()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", string.Empty } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = string.Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await buttonTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("button", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Action")]
|
|
||||||
[InlineData("Controller")]
|
|
||||||
[InlineData("Route")]
|
|
||||||
[InlineData("asp-route-")]
|
|
||||||
public async Task ProcessAsync_ThrowsIfFormActionConflictsWithBoundAttributes(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
|
||||||
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory);
|
|
||||||
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "formaction", "my-action" }
|
|
||||||
},
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
|
|
||||||
if (propertyName == "asp-route-")
|
|
||||||
{
|
|
||||||
buttonTagHelper.RouteValues.Add("name", "value");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
typeof(ButtonTagHelper).GetProperty(propertyName).SetValue(buttonTagHelper, "Home");
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedErrorMessage = "Cannot override the 'formaction' attribute for <button>. A <button> with a specified " +
|
|
||||||
"'formaction' must not have attributes starting with 'asp-route-' or an " +
|
|
||||||
"'asp-action', 'asp-controller', 'asp-area', or 'asp-route' attribute.";
|
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => buttonTagHelper.ProcessAsync(context, output));
|
|
||||||
|
|
||||||
Assert.Equal(expectedErrorMessage, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Action")]
|
|
||||||
[InlineData("Controller")]
|
|
||||||
public async Task ProcessAsync_ThrowsIfRouteAndActionOrControllerProvided(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
|
||||||
|
|
||||||
var buttonTagHelper = new ButtonTagHelper(urlHelperFactory)
|
|
||||||
{
|
|
||||||
Route = "Default",
|
|
||||||
};
|
|
||||||
|
|
||||||
typeof(ButtonTagHelper).GetProperty(propertyName).SetValue(buttonTagHelper, "Home");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"button",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
|
|
||||||
var expectedErrorMessage = "Cannot determine a 'formaction' attribute for <button>. A <button> with a specified " +
|
|
||||||
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
|
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => buttonTagHelper.ProcessAsync(context, output));
|
|
||||||
|
|
||||||
Assert.Equal(expectedErrorMessage, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,519 @@
|
||||||
|
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
{
|
||||||
|
public class FormActionTagHelperTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ProcessAsync_GeneratesExpectedOutput()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedTagName = "not-button-or-submit";
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var tagHelperContext = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "id", "my-id" },
|
||||||
|
},
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
expectedTagName,
|
||||||
|
attributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "id", "my-id" },
|
||||||
|
},
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
var tagHelperContent = new DefaultTagHelperContent();
|
||||||
|
tagHelperContent.SetContent("Something Else"); // ignored
|
||||||
|
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
||||||
|
.Returns<UrlActionContext>(c => $"{c.Controller}/{c.Action}/{(c.Values as RouteValueDictionary)["name"]}");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Action = "index",
|
||||||
|
Controller = "home",
|
||||||
|
RouteValues =
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
},
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
output.Attributes,
|
||||||
|
attribute =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", attribute.Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal("my-id", attribute.Value as string, StringComparer.Ordinal);
|
||||||
|
},
|
||||||
|
attribute =>
|
||||||
|
{
|
||||||
|
Assert.Equal("formaction", attribute.Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal("home/index/value", attribute.Value as string, StringComparer.Ordinal);
|
||||||
|
});
|
||||||
|
Assert.False(output.IsContentModified);
|
||||||
|
Assert.False(output.PostContent.IsModified);
|
||||||
|
Assert.False(output.PostElement.IsModified);
|
||||||
|
Assert.False(output.PreContent.IsModified);
|
||||||
|
Assert.False(output.PreElement.IsModified);
|
||||||
|
Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode);
|
||||||
|
Assert.Equal(expectedTagName, output.TagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ProcessAsync_GeneratesExpectedOutput_WithRoute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedTagName = "not-button-or-submit";
|
||||||
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
||||||
|
var tagHelperContext = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "id", "my-id" },
|
||||||
|
},
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
expectedTagName,
|
||||||
|
attributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "id", "my-id" },
|
||||||
|
},
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
var tagHelperContent = new DefaultTagHelperContent();
|
||||||
|
tagHelperContent.SetContent("Something Else"); // ignored
|
||||||
|
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.RouteUrl(It.IsAny<UrlRouteContext>()))
|
||||||
|
.Returns<UrlRouteContext>(c => $"{c.RouteName}/{(c.Values as RouteValueDictionary)["name"]}");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Route = "routine",
|
||||||
|
RouteValues =
|
||||||
|
{
|
||||||
|
{ "name", "value" },
|
||||||
|
},
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(tagHelperContext, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
output.Attributes,
|
||||||
|
attribute =>
|
||||||
|
{
|
||||||
|
Assert.Equal("id", attribute.Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal("my-id", attribute.Value as string, StringComparer.Ordinal);
|
||||||
|
},
|
||||||
|
attribute =>
|
||||||
|
{
|
||||||
|
Assert.Equal("formaction", attribute.Name, StringComparer.Ordinal);
|
||||||
|
Assert.Equal("routine/value", attribute.Value as string, StringComparer.Ordinal);
|
||||||
|
});
|
||||||
|
Assert.False(output.IsContentModified);
|
||||||
|
Assert.False(output.PostContent.IsModified);
|
||||||
|
Assert.False(output.PostElement.IsModified);
|
||||||
|
Assert.False(output.PreContent.IsModified);
|
||||||
|
Assert.False(output.PreElement.IsModified);
|
||||||
|
Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode);
|
||||||
|
Assert.Equal(expectedTagName, output.TagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteValues property value, expected RouteValuesDictionary content.
|
||||||
|
public static TheoryData<IDictionary<string, string>, IDictionary<string, object>> RouteValuesData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<IDictionary<string, string>, IDictionary<string, object>>
|
||||||
|
{
|
||||||
|
{ null, null },
|
||||||
|
// FormActionTagHelper ignores an empty route values dictionary.
|
||||||
|
{ new Dictionary<string, string>(), null },
|
||||||
|
{
|
||||||
|
new Dictionary<string, string> { { "name", "value" } },
|
||||||
|
new Dictionary<string, object> { { "name", "value" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new SortedDictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
{ "name1", "value1" },
|
||||||
|
{ "name2", "value2" },
|
||||||
|
},
|
||||||
|
new SortedDictionary<string, object>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
{ "name1", "value1" },
|
||||||
|
{ "name2", "value2" },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RouteValuesData))]
|
||||||
|
public async Task ProcessAsync_CallsActionWithExpectedParameters(
|
||||||
|
IDictionary<string, string> routeValues,
|
||||||
|
IDictionary<string, object> expectedRouteValues)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"button",
|
||||||
|
attributes: new TagHelperAttributeList(),
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
||||||
|
.Callback<UrlActionContext>(param =>
|
||||||
|
{
|
||||||
|
Assert.Equal("delete", param.Action, StringComparer.Ordinal);
|
||||||
|
Assert.Equal("books", param.Controller, StringComparer.Ordinal);
|
||||||
|
Assert.Null(param.Fragment);
|
||||||
|
Assert.Null(param.Host);
|
||||||
|
Assert.Null(param.Protocol);
|
||||||
|
Assert.Equal<KeyValuePair<string, object>>(expectedRouteValues, param.Values as RouteValueDictionary);
|
||||||
|
})
|
||||||
|
.Returns("home/index");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Action = "delete",
|
||||||
|
Controller = "books",
|
||||||
|
RouteValues = routeValues,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(context, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("button", output.TagName);
|
||||||
|
var attribute = Assert.Single(output.Attributes);
|
||||||
|
Assert.Equal("formaction", attribute.Name);
|
||||||
|
Assert.Equal("home/index", attribute.Value);
|
||||||
|
Assert.Empty(output.Content.GetContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RouteValuesData))]
|
||||||
|
public async Task ProcessAsync_CallsRouteUrlWithExpectedParameters(
|
||||||
|
IDictionary<string, string> routeValues,
|
||||||
|
IDictionary<string, object> expectedRouteValues)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"button",
|
||||||
|
attributes: new TagHelperAttributeList(),
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.RouteUrl(It.IsAny<UrlRouteContext>()))
|
||||||
|
.Callback<UrlRouteContext>(param =>
|
||||||
|
{
|
||||||
|
Assert.Null(param.Fragment);
|
||||||
|
Assert.Null(param.Host);
|
||||||
|
Assert.Null(param.Protocol);
|
||||||
|
Assert.Equal("Default", param.RouteName, StringComparer.Ordinal);
|
||||||
|
Assert.Equal<KeyValuePair<string, object>>(expectedRouteValues, param.Values as RouteValueDictionary);
|
||||||
|
})
|
||||||
|
.Returns("home/index");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Route = "Default",
|
||||||
|
RouteValues = routeValues,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(context, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("button", output.TagName);
|
||||||
|
var attribute = Assert.Single(output.Attributes);
|
||||||
|
Assert.Equal("formaction", attribute.Name);
|
||||||
|
Assert.Equal("home/index", attribute.Value);
|
||||||
|
Assert.Empty(output.Content.GetContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Area property value, RouteValues property value, expected "area" in final RouteValuesDictionary.
|
||||||
|
public static TheoryData<string, Dictionary<string, string>, string> AreaRouteValuesData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<string, Dictionary<string, string>, string>
|
||||||
|
{
|
||||||
|
{ "Area", null, "Area" },
|
||||||
|
// Explicit Area overrides value in the dictionary.
|
||||||
|
{ "Area", new Dictionary<string, string> { { "area", "Home" } }, "Area" },
|
||||||
|
// Empty string is also passed through to the helper.
|
||||||
|
{ string.Empty, null, string.Empty },
|
||||||
|
{ string.Empty, new Dictionary<string, string> { { "area", "Home" } }, string.Empty },
|
||||||
|
// Fall back "area" entry in the provided route values if Area is null.
|
||||||
|
{ null, new Dictionary<string, string> { { "area", "Admin" } }, "Admin" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(AreaRouteValuesData))]
|
||||||
|
public async Task ProcessAsync_CallsActionWithExpectedRouteValues(
|
||||||
|
string area,
|
||||||
|
Dictionary<string, string> routeValues,
|
||||||
|
string expectedArea)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"submit",
|
||||||
|
attributes: new TagHelperAttributeList(),
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
var expectedRouteValues = new Dictionary<string, object> { { "area", expectedArea } };
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
||||||
|
.Callback<UrlActionContext>(param => Assert.Equal(expectedRouteValues, param.Values as RouteValueDictionary))
|
||||||
|
.Returns("admin/dashboard/index");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Action = "Index",
|
||||||
|
Area = area,
|
||||||
|
Controller = "Dashboard",
|
||||||
|
RouteValues = routeValues,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(context, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("submit", output.TagName);
|
||||||
|
var attribute = Assert.Single(output.Attributes);
|
||||||
|
Assert.Equal("formaction", attribute.Name);
|
||||||
|
Assert.Equal("admin/dashboard/index", attribute.Value);
|
||||||
|
Assert.Empty(output.Content.GetContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(AreaRouteValuesData))]
|
||||||
|
public async Task ProcessAsync_CallsRouteUrlWithExpectedRouteValues(
|
||||||
|
string area,
|
||||||
|
Dictionary<string, string> routeValues,
|
||||||
|
string expectedArea)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
"submit",
|
||||||
|
attributes: new TagHelperAttributeList(),
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
|
||||||
|
});
|
||||||
|
|
||||||
|
var expectedRouteValues = new Dictionary<string, object> { { "area", expectedArea } };
|
||||||
|
var urlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);
|
||||||
|
urlHelper
|
||||||
|
.Setup(mock => mock.RouteUrl(It.IsAny<UrlRouteContext>()))
|
||||||
|
.Callback<UrlRouteContext>(param => Assert.Equal(expectedRouteValues, param.Values as RouteValueDictionary))
|
||||||
|
.Returns("admin/dashboard/index");
|
||||||
|
|
||||||
|
var viewContext = new ViewContext();
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>(MockBehavior.Strict);
|
||||||
|
urlHelperFactory
|
||||||
|
.Setup(f => f.GetUrlHelper(viewContext))
|
||||||
|
.Returns(urlHelper.Object);
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory.Object)
|
||||||
|
{
|
||||||
|
Area = area,
|
||||||
|
Route = "routine",
|
||||||
|
RouteValues = routeValues,
|
||||||
|
ViewContext = viewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await tagHelper.ProcessAsync(context, output);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("submit", output.TagName);
|
||||||
|
var attribute = Assert.Single(output.Attributes);
|
||||||
|
Assert.Equal("formaction", attribute.Name);
|
||||||
|
Assert.Equal("admin/dashboard/index", attribute.Value);
|
||||||
|
Assert.Empty(output.Content.GetContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("button", "Action")]
|
||||||
|
[InlineData("button", "Controller")]
|
||||||
|
[InlineData("button", "Route")]
|
||||||
|
[InlineData("button", "asp-route-")]
|
||||||
|
[InlineData("submit", "Action")]
|
||||||
|
[InlineData("submit", "Controller")]
|
||||||
|
[InlineData("submit", "Route")]
|
||||||
|
[InlineData("submit", "asp-route-")]
|
||||||
|
public async Task ProcessAsync_ThrowsIfFormActionConflictsWithBoundAttributes(string tagName, string propertyName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory);
|
||||||
|
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
tagName,
|
||||||
|
attributes: new TagHelperAttributeList
|
||||||
|
{
|
||||||
|
{ "formaction", "my-action" }
|
||||||
|
},
|
||||||
|
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
|
||||||
|
if (propertyName == "asp-route-")
|
||||||
|
{
|
||||||
|
tagHelper.RouteValues.Add("name", "value");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeof(FormActionTagHelper).GetProperty(propertyName).SetValue(tagHelper, "Home");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.";
|
||||||
|
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(
|
||||||
|
Enumerable.Empty<TagHelperAttribute>()),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => tagHelper.ProcessAsync(context, output));
|
||||||
|
|
||||||
|
Assert.Equal(expectedErrorMessage, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("button", "Action")]
|
||||||
|
[InlineData("button", "Controller")]
|
||||||
|
[InlineData("submit", "Action")]
|
||||||
|
[InlineData("submit", "Controller")]
|
||||||
|
public async Task ProcessAsync_ThrowsIfRouteAndActionOrControllerProvided(string tagName, string propertyName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
||||||
|
|
||||||
|
var tagHelper = new FormActionTagHelper(urlHelperFactory)
|
||||||
|
{
|
||||||
|
Route = "Default",
|
||||||
|
};
|
||||||
|
|
||||||
|
typeof(FormActionTagHelper).GetProperty(propertyName).SetValue(tagHelper, "Home");
|
||||||
|
var output = new TagHelperOutput(
|
||||||
|
tagName,
|
||||||
|
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.";
|
||||||
|
|
||||||
|
var context = new TagHelperContext(
|
||||||
|
allAttributes: new TagHelperAttributeList(
|
||||||
|
Enumerable.Empty<TagHelperAttribute>()),
|
||||||
|
items: new Dictionary<object, object>(),
|
||||||
|
uniqueId: "test");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => tagHelper.ProcessAsync(context, output));
|
||||||
|
|
||||||
|
Assert.Equal(expectedErrorMessage, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,385 +0,0 @@
|
||||||
// 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 System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
{
|
|
||||||
public class SubmitTagHelperTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_GeneratesExpectedOutput()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expectedTagName = "not-input";
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
|
|
||||||
var tagHelperContext = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "id", "myinput" },
|
|
||||||
{ "type", "submit" },
|
|
||||||
{ "asp-route-name", "value" },
|
|
||||||
{ "asp-action", "index" },
|
|
||||||
{ "asp-controller", "home" },
|
|
||||||
},
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
expectedTagName,
|
|
||||||
attributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "id", "myinput" },
|
|
||||||
{ "type", "submit" },
|
|
||||||
},
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something Else");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent("Something");
|
|
||||||
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>())).Returns("home/index").Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
|
||||||
htmlGenerator: htmlGenerator,
|
|
||||||
metadataProvider: metadataProvider);
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "index",
|
|
||||||
Controller = "home",
|
|
||||||
RouteValues =
|
|
||||||
{
|
|
||||||
{ "name", "value" },
|
|
||||||
},
|
|
||||||
ViewContext = viewContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await submitTagHelper.ProcessAsync(tagHelperContext, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal(3, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("id"));
|
|
||||||
Assert.Equal("myinput", attribute.Value);
|
|
||||||
attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("type"));
|
|
||||||
Assert.Equal("submit", attribute.Value);
|
|
||||||
attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("home/index", attribute.Value);
|
|
||||||
Assert.Equal("Something", output.Content.GetContent());
|
|
||||||
Assert.Equal(expectedTagName, output.TagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.RouteUrl(It.IsAny<UrlRouteContext>())).Returns("home/index").Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
|
||||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider, urlHelper.Object);
|
|
||||||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
|
||||||
htmlGenerator: htmlGenerator,
|
|
||||||
metadataProvider: metadataProvider);
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Route = "Default",
|
|
||||||
ViewContext = viewContext,
|
|
||||||
RouteValues =
|
|
||||||
{
|
|
||||||
{ "name", "value" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await submitTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("input", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("home/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_AddsAreaToRouteValuesAndCallsIntoActionLinkWithExpectedParameters()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", "Admin" } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = "Admin",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await submitTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("input", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_AspAreaOverridesAspRouteArea()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", "Admin" } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = "Admin",
|
|
||||||
RouteValues = new Dictionary<string, string> { { "area", "Home" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await submitTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("input", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ProcessAsync_EmptyStringOnAspAreaIsPassedThroughToRouteValues()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) =>
|
|
||||||
{
|
|
||||||
var tagHelperContent = new DefaultTagHelperContent();
|
|
||||||
tagHelperContent.SetContent("Something");
|
|
||||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
|
||||||
});
|
|
||||||
output.Content.SetContent(string.Empty);
|
|
||||||
|
|
||||||
var expectedRouteValues = new RouteValueDictionary(new Dictionary<string, string> { { "area", string.Empty } });
|
|
||||||
var urlHelper = new Mock<IUrlHelper>();
|
|
||||||
urlHelper
|
|
||||||
.Setup(mock => mock.Action(It.IsAny<UrlActionContext>()))
|
|
||||||
.Returns("admin/dashboard/index")
|
|
||||||
.Callback<UrlActionContext>(param => Assert.Equal(param.Values, expectedRouteValues))
|
|
||||||
.Verifiable();
|
|
||||||
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>();
|
|
||||||
urlHelperFactory
|
|
||||||
.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(urlHelper.Object);
|
|
||||||
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory.Object)
|
|
||||||
{
|
|
||||||
Action = "Index",
|
|
||||||
Controller = "Dashboard",
|
|
||||||
Area = string.Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await submitTagHelper.ProcessAsync(context, output);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
urlHelper.Verify();
|
|
||||||
Assert.Equal("input", output.TagName);
|
|
||||||
Assert.Equal(1, output.Attributes.Count);
|
|
||||||
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("formaction"));
|
|
||||||
Assert.Equal("admin/dashboard/index", attribute.Value);
|
|
||||||
Assert.True(output.Content.GetContent().Length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Action")]
|
|
||||||
[InlineData("Controller")]
|
|
||||||
[InlineData("Route")]
|
|
||||||
[InlineData("asp-route-")]
|
|
||||||
public async Task ProcessAsync_ThrowsIfFormActionConflictsWithBoundAttributes(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
|
||||||
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory);
|
|
||||||
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList
|
|
||||||
{
|
|
||||||
{ "formaction", "my-action" }
|
|
||||||
},
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
|
|
||||||
if (propertyName == "asp-route-")
|
|
||||||
{
|
|
||||||
submitTagHelper.RouteValues.Add("name", "value");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
typeof(SubmitTagHelper).GetProperty(propertyName).SetValue(submitTagHelper, "Home");
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedErrorMessage = "Cannot override the 'formaction' attribute for <input>. An <input> with a specified " +
|
|
||||||
"'formaction' must not have attributes starting with 'asp-route-' or an " +
|
|
||||||
"'asp-action', 'asp-controller', 'asp-area', or 'asp-route' attribute.";
|
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => submitTagHelper.ProcessAsync(context, output));
|
|
||||||
|
|
||||||
Assert.Equal(expectedErrorMessage, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("Action")]
|
|
||||||
[InlineData("Controller")]
|
|
||||||
public async Task ProcessAsync_ThrowsIfRouteAndActionOrControllerProvided(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
|
|
||||||
|
|
||||||
var submitTagHelper = new SubmitTagHelper(urlHelperFactory)
|
|
||||||
{
|
|
||||||
Route = "Default",
|
|
||||||
};
|
|
||||||
|
|
||||||
typeof(SubmitTagHelper).GetProperty(propertyName).SetValue(submitTagHelper, "Home");
|
|
||||||
var output = new TagHelperOutput(
|
|
||||||
"input",
|
|
||||||
attributes: new TagHelperAttributeList(),
|
|
||||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
|
|
||||||
var expectedErrorMessage = "Cannot determine a 'formaction' attribute for <input>. An <input> with a specified " +
|
|
||||||
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
|
|
||||||
|
|
||||||
var context = new TagHelperContext(
|
|
||||||
allAttributes: new TagHelperAttributeList(
|
|
||||||
Enumerable.Empty<TagHelperAttribute>()),
|
|
||||||
items: new Dictionary<object, object>(),
|
|
||||||
uniqueId: "test");
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => submitTagHelper.ProcessAsync(context, output));
|
|
||||||
|
|
||||||
Assert.Equal(expectedErrorMessage, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue