Add page to AnchorTagHelper and FormTagHelper

Fixes #6088
This commit is contained in:
Pranav K 2017-04-19 14:18:58 -07:00
parent f568d3c2bc
commit ca017eced2
25 changed files with 855 additions and 229 deletions

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("a", Attributes = ActionAttributeName)]
[HtmlTargetElement("a", Attributes = ControllerAttributeName)]
[HtmlTargetElement("a", Attributes = AreaAttributeName)]
[HtmlTargetElement("a", Attributes = PageAttributeName)]
[HtmlTargetElement("a", Attributes = FragmentAttributeName)]
[HtmlTargetElement("a", Attributes = HostAttributeName)]
[HtmlTargetElement("a", Attributes = ProtocolAttributeName)]
@ -27,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
private const string ActionAttributeName = "asp-action";
private const string ControllerAttributeName = "asp-controller";
private const string AreaAttributeName = "asp-area";
private const string PageAttributeName = "asp-page";
private const string FragmentAttributeName = "asp-fragment";
private const string HostAttributeName = "asp-host";
private const string ProtocolAttributeName = "asp-protocol";
@ -59,24 +61,40 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// <summary>
/// The name of the action method.
/// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
/// <remarks>
/// Must be <c>null</c> if <see cref="Route"/> or <see cref="Page"/> is non-<c>null</c>.
/// </remarks>
[HtmlAttributeName(ActionAttributeName)]
public string Action { get; set; }
/// <summary>
/// The name of the controller.
/// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
/// <remarks>
/// Must be <c>null</c> if <see cref="Route"/> or <see cref="Page"/> is non-<c>null</c>.
/// </remarks>
[HtmlAttributeName(ControllerAttributeName)]
public string Controller { get; set; }
/// <summary>
/// The name of the area.
/// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
/// <remarks>
/// Must be <c>null</c> if <see cref="Route"/> or <see cref="Page"/> is non-<c>null</c>.
/// </remarks>
[HtmlAttributeName(AreaAttributeName)]
public string Area { get; set; }
/// <summary>
/// The name of the page.
/// </summary>
/// <remarks>
/// Must be <c>null</c> if <see cref="Route"/> or <see cref="Action"/>, <see cref="Controller"/>
/// or <see cref="Area"/> is non-<c>null</c>.
/// </remarks>
[HtmlAttributeName(PageAttributeName)]
public string Page { get; set; }
/// <summary>
/// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;.
/// </summary>
@ -99,7 +117,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// Name of the route.
/// </summary>
/// <remarks>
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
/// Must be <c>null</c> if one of <see cref="Action"/>, <see cref="Controller"/>, <see cref="Area"/>
/// or <see cref="Page"/> is non-<c>null</c>.
/// </remarks>
[HtmlAttributeName(RouteAttributeName)]
public string Route { get; set; }
@ -134,12 +153,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
/// <inheritdoc />
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <c>href</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>,
/// <see cref="Fragment"/>, <see cref="Host"/>, <see cref="Protocol"/>, 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)
@ -158,6 +171,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
if (Action != null ||
Controller != null ||
Area != null ||
Page != null ||
Route != null ||
Protocol != null ||
Host != null ||
@ -167,7 +181,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// User specified an href and one of the bound attributes; can't determine the href attribute.
throw new InvalidOperationException(
Resources.FormatAnchorTagHelper_CannotOverrideHref(
Href,
"<a>",
RouteValuesPrefix,
ActionAttributeName,
ControllerAttributeName,
AreaAttributeName,
@ -175,72 +191,84 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
ProtocolAttributeName,
HostAttributeName,
FragmentAttributeName,
RouteValuesPrefix,
Href));
PageAttributeName));
}
return;
}
var routeLink = Route != null;
var actionLink = Controller != null || Action != null;
var pageLink = Page != null;
if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink))
{
var message = string.Join(
Environment.NewLine,
Resources.FormatCannotDetermineAttributeFor(Href, "<a>"),
RouteAttributeName,
ControllerAttributeName + ", " + ActionAttributeName,
PageAttributeName);
throw new InvalidOperationException(message);
}
RouteValueDictionary routeValues = null;
if (_routeValues != null && _routeValues.Count > 0)
{
routeValues = new RouteValueDictionary(_routeValues);
}
if (Area != null)
{
// Unconditionally replace any value from asp-route-area.
if (routeValues == null)
{
routeValues = new RouteValueDictionary();
}
routeValues["area"] = Area;
}
TagBuilder tagBuilder;
if (pageLink)
{
tagBuilder = Generator.GeneratePageLink(
ViewContext,
linkText: string.Empty,
pageName: Page,
protocol: Protocol,
hostname: Host,
fragment: Fragment,
routeValues: routeValues,
htmlAttributes: null);
}
else if (routeLink)
{
tagBuilder = Generator.GenerateRouteLink(
ViewContext,
linkText: string.Empty,
routeName: Route,
protocol: Protocol,
hostName: Host,
fragment: Fragment,
routeValues: routeValues,
htmlAttributes: null);
}
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;
}
TagBuilder tagBuilder;
if (Route == null)
{
tagBuilder = Generator.GenerateActionLink(
ViewContext,
linkText: string.Empty,
actionName: Action,
controllerName: Controller,
protocol: Protocol,
hostname: Host,
fragment: Fragment,
routeValues: routeValues,
htmlAttributes: null);
}
else if (Action != null || Controller != null)
{
// Route and Action or Controller were specified. Can't determine the href attribute.
throw new InvalidOperationException(
Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(
"<a>",
RouteAttributeName,
ActionAttributeName,
ControllerAttributeName,
Href));
}
else
{
tagBuilder = Generator.GenerateRouteLink(
ViewContext,
linkText: string.Empty,
routeName: Route,
protocol: Protocol,
hostName: Host,
fragment: Fragment,
routeValues: routeValues,
htmlAttributes: null);
}
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
}
tagBuilder = Generator.GenerateActionLink(
ViewContext,
linkText: string.Empty,
actionName: Action,
controllerName: Controller,
protocol: Protocol,
hostname: Host,
fragment: Fragment,
routeValues: routeValues,
htmlAttributes: null);
}
output.MergeAttributes(tagBuilder);
}
}
}

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("button", Attributes = ActionAttributeName)]
[HtmlTargetElement("button", Attributes = ControllerAttributeName)]
[HtmlTargetElement("button", Attributes = AreaAttributeName)]
[HtmlTargetElement("button", Attributes = PageAttributeName)]
[HtmlTargetElement("button", Attributes = FragmentAttributeName)]
[HtmlTargetElement("button", Attributes = RouteAttributeName)]
[HtmlTargetElement("button", Attributes = RouteValuesDictionaryName)]
@ -25,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("input", Attributes = ImageActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImageControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImageAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImagePageAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImageFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImageRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = ImageRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)]
@ -32,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("input", Attributes = SubmitActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitPageAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)]
[HtmlTargetElement("input", Attributes = SubmitRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)]
@ -41,6 +44,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 PageAttributeName = "asp-page";
private const string FragmentAttributeName = "asp-fragment";
private const string RouteAttributeName = "asp-route";
private const string RouteValuesDictionaryName = "asp-all-route-data";
@ -50,6 +54,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 ImagePageAttributeSelector = ImageTypeSelector + PageAttributeName;
private const string ImageFragmentAttributeSelector = ImageTypeSelector + FragmentAttributeName;
private const string ImageControllerAttributeSelector = ImageTypeSelector + ControllerAttributeName;
private const string ImageRouteAttributeSelector = ImageTypeSelector + RouteAttributeName;
@ -59,6 +64,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 SubmitPageAttributeSelector = SubmitTypeSelector + PageAttributeName;
private const string SubmitFragmentAttributeSelector = SubmitTypeSelector + FragmentAttributeName;
private const string SubmitControllerAttributeSelector = SubmitTypeSelector + ControllerAttributeName;
private const string SubmitRouteAttributeSelector = SubmitTypeSelector + RouteAttributeName;
@ -106,6 +112,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlAttributeName(AreaAttributeName)]
public string Area { get; set; }
/// <summary>
/// The name of the page.
/// </summary>
[HtmlAttributeName(PageAttributeName)]
public string Page { get; set; }
/// <summary>
/// Gets or sets the URL fragment.
/// </summary>
@ -185,51 +197,59 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
RouteValuesPrefix,
FormAction));
}
return;
}
var routeLink = Route != null;
var actionLink = Controller != null || Action != null;
var pageLink = Page != null;
if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink))
{
var message = string.Join(
Environment.NewLine,
Resources.FormatCannotDetermineAttributeFor(FormAction, '<' + output.TagName + '>'),
RouteAttributeName,
ControllerAttributeName + ", " + ActionAttributeName,
PageAttributeName);
throw new InvalidOperationException(message);
}
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;
}
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
string url;
if (pageLink)
{
url = urlHelper.Page(Page, routeValues, protocol: null, host: null, fragment: Fragment);
}
else if (routeLink)
{
url = urlHelper.RouteUrl(Route, routeValues, protocol: null, host: null, fragment: Fragment);
}
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, protocol: null, host: null, fragment: Fragment);
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.FormatFormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified(
output.TagName,
RouteAttributeName,
ActionAttributeName,
ControllerAttributeName,
FormAction,
FragmentAttributeName));
}
else
{
var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext);
var url = urlHelper.RouteUrl(Route, routeValues, protocol: null, host: null, fragment: Fragment);
output.Attributes.SetAttribute(FormAction, url);
}
url = urlHelper.Action(Action, Controller, routeValues, protocol: null, host: null, fragment: Fragment);
}
output.Attributes.SetAttribute(FormAction, url);
}
}
}

View File

@ -7,6 +7,7 @@ using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("form", Attributes = ActionAttributeName)]
[HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)]
[HtmlTargetElement("form", Attributes = AreaAttributeName)]
[HtmlTargetElement("form", Attributes = PageAttributeName)]
[HtmlTargetElement("form", Attributes = FragmentAttributeName)]
[HtmlTargetElement("form", Attributes = ControllerAttributeName)]
[HtmlTargetElement("form", Attributes = RouteAttributeName)]
@ -26,6 +28,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 PageAttributeName = "asp-page";
private const string FragmentAttributeName = "asp-fragment";
private const string ControllerAttributeName = "asp-controller";
private const string RouteAttributeName = "asp-route";
@ -72,6 +75,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlAttributeName(AreaAttributeName)]
public string Area { get; set; }
/// <summary>
/// The name of the page.
/// </summary>
[HtmlAttributeName(PageAttributeName)]
public string Page { get; set; }
/// <summary>
/// Whether the antiforgery token should be generated.
/// </summary>
@ -156,6 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
if (Action != null ||
Controller != null ||
Area != null ||
Page != null ||
Fragment != null ||
Route != null ||
(_routeValues != null && _routeValues.Count > 0))
@ -163,14 +173,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// User also specified bound attributes we cannot use.
throw new InvalidOperationException(
Resources.FormatFormTagHelper_CannotOverrideAction(
"<form>",
HtmlActionAttributeName,
"<form>",
RouteValuesPrefix,
ActionAttributeName,
ControllerAttributeName,
FragmentAttributeName,
AreaAttributeName,
RouteAttributeName,
RouteValuesPrefix));
PageAttributeName));
}
// User is using the FormTagHelper like a normal <form> tag. Antiforgery default should be false to
@ -179,22 +190,33 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
else
{
IDictionary<string, object> routeValues = null;
var routeLink = Route != null;
var actionLink = Controller != null || Action != null;
var pageLink = Page != null;
if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink))
{
var message = string.Join(
Environment.NewLine,
Resources.FormatCannotDetermineAttributeFor(HtmlActionAttributeName, "<form>"),
RouteAttributeName,
ControllerAttributeName + ", " + ActionAttributeName,
PageAttributeName);
throw new InvalidOperationException(message);
}
RouteValueDictionary routeValues = null;
if (_routeValues != null && _routeValues.Count > 0)
{
// Convert from Dictionary<string, string> to Dictionary<string, object>.
routeValues = new Dictionary<string, object>(_routeValues.Count, StringComparer.OrdinalIgnoreCase);
foreach (var routeValue in _routeValues)
{
routeValues.Add(routeValue.Key, routeValue.Value);
}
routeValues = new RouteValueDictionary(_routeValues);
}
if (Area != null)
{
if (routeValues == null)
{
routeValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
routeValues = new RouteValueDictionary();
}
// Unconditionally replace any value from asp-route-area.
@ -202,7 +224,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
TagBuilder tagBuilder;
if (Route == null)
if (pageLink)
{
tagBuilder = Generator.GeneratePageForm(
ViewContext,
Page,
routeValues,
Fragment,
method: null,
htmlAttributes: null);
}
else if (routeLink)
{
tagBuilder = Generator.GenerateRouteForm(
ViewContext,
Route,
routeValues,
Fragment,
method: null,
htmlAttributes: null);
}
else
{
tagBuilder = Generator.GenerateForm(
ViewContext,
@ -213,42 +255,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
method: null,
htmlAttributes: null);
}
else if (Action != null || Controller != null)
output.MergeAttributes(tagBuilder);
if (tagBuilder.HasInnerHtml)
{
// Route and Action or Controller were specified. Can't determine the action attribute.
throw new InvalidOperationException(
Resources.FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified(
"<form>",
RouteAttributeName,
ActionAttributeName,
ControllerAttributeName,
HtmlActionAttributeName,
FragmentAttributeName));
}
else
{
tagBuilder = Generator.GenerateRouteForm(
ViewContext,
Route,
routeValues,
Fragment,
method: null,
htmlAttributes: null);
output.PostContent.AppendHtml(tagBuilder.InnerHtml);
}
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
if (tagBuilder.HasInnerHtml)
{
output.PostContent.AppendHtml(tagBuilder.InnerHtml);
}
}
if (string.Equals(Method, "get", StringComparison.OrdinalIgnoreCase))
{
antiforgeryDefault = false;
}
antiforgeryDefault = !string.Equals(Method, "get", StringComparison.OrdinalIgnoreCase);
}
if (Antiforgery ?? antiforgeryDefault)

View File

@ -11,21 +11,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
= new ResourceManager("Microsoft.AspNetCore.Mvc.TagHelpers.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
/// Cannot determine the '{0}' attribute for {1}. The following attributes are mutually exclusive:
/// </summary>
internal static string AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified
internal static string CannotDetermineAttributeFor
{
get => GetString("AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified");
get => GetString("CannotDetermineAttributeFor");
}
/// <summary>
/// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
/// Cannot determine the '{0}' attribute for {1}. The following attributes are mutually exclusive:
/// </summary>
internal static string FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
=> string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4);
internal static string FormatCannotDetermineAttributeFor(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotDetermineAttributeFor"), p0, p1);
/// <summary>
/// Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute.
/// Cannot override the '{0}' attribute for {1}. An {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', or '{10}' attribute.
/// </summary>
internal static string AnchorTagHelper_CannotOverrideHref
{
@ -33,13 +33,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// Cannot override the '{9}' attribute for {0}. An {0} with a specified '{9}' must not have attributes starting with '{8}' or an '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', or '{7}' attribute.
/// Cannot override the '{0}' attribute for {1}. An {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', or '{10}' attribute.
/// </summary>
internal static string FormatAnchorTagHelper_CannotOverrideHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8, object p9)
=> string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
internal static string FormatAnchorTagHelper_CannotOverrideHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8, object p9, object p10)
=> string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
/// <summary>
/// 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.
/// Cannot override the '{0}' attribute for {1}. A {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}' or '{8}' attribute.
/// </summary>
internal static string FormTagHelper_CannotOverrideAction
{
@ -47,10 +47,10 @@ 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 '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute.
/// Cannot override the '{0}' attribute for {1}. A {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}' or '{8}' attribute.
/// </summary>
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7)
=> string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5, p6, p7);
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8)
=> string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4, p5, p6, p7, p8);
/// <summary>
/// Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' or '{4}' that can be parsed as a '{3}' if '{5}' is '{6}'.
@ -136,20 +136,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
internal static string FormatTagHelperOutput_AttributeDoesNotExist(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("TagHelperOutput_AttributeDoesNotExist"), p0, p1);
/// <summary>
/// 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 FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified
{
get => GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified");
}
/// <summary>
/// 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, object p5)
=> string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified"), p0, p1, p2, p3, p4, p5);
/// <summary>
/// The '{0}' property of '{1}' must not be null.
/// </summary>
@ -178,20 +164,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
internal static string FormatFormActionTagHelper_CannotOverrideFormAction(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7)
=> string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotOverrideFormAction"), p0, p1, p2, p3, p4, p5, p6, p7);
/// <summary>
/// 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 FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified
{
get => GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified");
}
/// <summary>
/// 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, object p5)
=> string.Format(CultureInfo.CurrentCulture, GetString("FormActionTagHelper_CannotDetermineFormActionRouteActionOrControllerSpecified"), p0, p1, p2, p3, p4, p5);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -117,14 +117,14 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified" xml:space="preserve">
<value>Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
<data name="CannotDetermineAttributeFor" xml:space="preserve">
<value>Cannot determine the '{0}' attribute for {1}. The following attributes are mutually exclusive:</value>
</data>
<data name="AnchorTagHelper_CannotOverrideHref" xml:space="preserve">
<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>
<value>Cannot override the '{0}' attribute for {1}. An {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', or '{10}' 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 '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute.</value>
<value>Cannot override the '{0}' attribute for {1}. A {1} with a specified '{0}' must not have attributes starting with '{2}' or an '{3}', '{4}', '{5}', '{6}', '{7}' or '{8}' 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}' or '{4}' that can be parsed as a '{3}' if '{5}' is '{6}'.</value>
@ -144,16 +144,10 @@
<data name="TagHelperOutput_AttributeDoesNotExist" xml:space="preserve">
<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}', '{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 '{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}', '{3}', or '{5}' attribute.</value>
</data>
</root>

View File

@ -142,6 +142,32 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return GenerateLink(linkText, url, htmlAttributes);
}
/// <inheritdoc />
public virtual TagBuilder GeneratePageLink(
ViewContext viewContext,
string linkText,
string pageName,
string protocol,
string hostname,
string fragment,
object routeValues,
object htmlAttributes)
{
if (viewContext == null)
{
throw new ArgumentNullException(nameof(viewContext));
}
if (linkText == null)
{
throw new ArgumentNullException(nameof(linkText));
}
var urlHelper = _urlHelperFactory.GetUrlHelper(viewContext);
var url = urlHelper.Page(pageName, routeValues, protocol, hostname, fragment);
return GenerateLink(linkText, url, htmlAttributes);
}
/// <inheritdoc />
public virtual IHtmlContent GenerateAntiforgery(ViewContext viewContext)
{
@ -281,6 +307,52 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
return GenerateFormCore(viewContext, action, method, htmlAttributes);
}
/// <inheritdoc />
public virtual TagBuilder GeneratePageForm(
ViewContext viewContext,
string pageName,
object routeValues,
string fragment,
string method,
object htmlAttributes)
{
if (viewContext == null)
{
throw new ArgumentNullException(nameof(viewContext));
}
var defaultMethod = false;
if (string.IsNullOrEmpty(method))
{
defaultMethod = true;
}
else if (string.Equals(method, "post", StringComparison.OrdinalIgnoreCase))
{
defaultMethod = true;
}
string action;
if (pageName == null && routeValues == null && defaultMethod)
{
// Submit to the original URL in the special case that user called the BeginForm() overload without
// parameters (except for the htmlAttributes parameter). Also reachable in the even-more-unusual case
// that user called another BeginForm() overload with default argument values.
var request = viewContext.HttpContext.Request;
action = request.PathBase + request.Path + request.QueryString;
if (fragment != null)
{
action += "#" + fragment;
}
}
else
{
var urlHelper = _urlHelperFactory.GetUrlHelper(viewContext);
action = urlHelper.Page(pageName, values: routeValues,protocol: null, host: null, fragment: fragment);
}
return GenerateFormCore(viewContext, action, method, htmlAttributes);
}
/// <inheritdoc />
public TagBuilder GenerateRouteForm(
ViewContext viewContext,

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures

View File

@ -54,6 +54,38 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
object routeValues,
object htmlAttributes);
/// <summary>
/// Generate a &lt;a&gt; element for a link to an action.
/// </summary>
/// <param name="viewContext">The <see cref="ViewContext"/> instance for the current scope.</param>
/// <param name="linkText">The text to insert inside the element.</param>
/// <param name="pageName">The page name.</param>
/// <param name="protocol">The protocol (scheme) for the generated link.</param>
/// <param name="hostname">The hostname for the generated link.</param>
/// <param name="fragment">The fragment for the genrated link.</param>
/// <param name="routeValues">
/// An <see cref="object"/> that contains the parameters for a route. The parameters are retrieved through
/// reflection by examining the properties of the <see cref="object"/>. This <see cref="object"/> is typically
/// created using <see cref="object"/> initializer syntax. Alternatively, an
/// <see cref="IDictionary{String, Object}"/> instance containing the route parameters.
/// </param>
/// <param name="htmlAttributes">
/// An <see cref="object"/> that contains the HTML attributes for the element. Alternatively, an
/// <see cref="IDictionary{String, Object}"/> instance containing the HTML attributes.
/// </param>
/// <returns>
/// A <see cref="TagBuilder"/> instance for the &lt;a&gt; element.
/// </returns>
TagBuilder GeneratePageLink(
ViewContext viewContext,
string linkText,
string pageName,
string protocol,
string hostname,
string fragment,
object routeValues,
object htmlAttributes);
/// <summary>
/// Generate an &lt;input type="hidden".../&gt; element containing an antiforgery token.
/// </summary>
@ -124,6 +156,35 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
string method,
object htmlAttributes);
/// <summary>
/// Generate a &lt;form&gt; element. When the user submits the form, the page with name
/// <paramref name="pageName"/> will process the request.
/// </summary>
/// <param name="viewContext">A <see cref="ViewContext"/> instance for the current scope.</param>
/// <param name="pageName">The name of the action method.</param>
/// <param name="routeValues">
/// An <see cref="object"/> that contains the parameters for a route. The parameters are retrieved through
/// reflection by examining the properties of the <see cref="object"/>. This <see cref="object"/> is typically
/// created using <see cref="object"/> initializer syntax. Alternatively, an
/// <see cref="IDictionary{String, Object}"/> instance containing the route parameters.
/// </param>
/// <param name="fragment">The url fragment.</param>
/// <param name="method">The HTTP method for processing the form, either GET or POST.</param>
/// <param name="htmlAttributes">
/// An <see cref="object"/> that contains the HTML attributes for the element. Alternatively, an
/// <see cref="IDictionary{String, Object}"/> instance containing the HTML attributes.
/// </param>
/// <returns>
/// A <see cref="TagBuilder"/> instance for the &lt;/form&gt; element.
/// </returns>
TagBuilder GeneratePageForm(
ViewContext viewContext,
string pageName,
object routeValues,
string fragment,
string method,
object htmlAttributes);
/// <summary>
/// Generate a &lt;form&gt; element. The route with name <paramref name="routeName"/> generates the
/// &lt;form&gt;'s <c>action</c> attribute value.

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var urlHelper = GetUrlHelper(actionContext, returnValue: null);
var result = new RedirectToPageResult("some-page", new Dictionary<string, object>())
var result = new RedirectToPageResult("/some-page", new Dictionary<string, object>())
{
UrlHelper = urlHelper,
};
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc
// Act & Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
() => result.ExecuteResultAsync(actionContext),
"No page named 'some-page' matches the supplied values.");
"No page named '/some-page' matches the supplied values.");
}
[Theory]
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc
new ActionDescriptor());
var urlHelper = GetUrlHelper(actionContext, expectedUrl);
var result = new RedirectToPageResult("MyPage", new { id = 10, test = "value" }, permanentRedirect)
var result = new RedirectToPageResult("/MyPage", new { id = 10, test = "value" }, permanentRedirect)
{
UrlHelper = urlHelper,
};
@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc
.Callback((UrlRouteContext c) => context = c)
.Returns("some-value");
var values = new { test = "test-value" };
var result = new RedirectToPageResult("MyPage", values, true, "test-fragment")
var result = new RedirectToPageResult("/MyPage", values, true, "test-fragment")
{
UrlHelper = urlHelper.Object,
Protocol = "ftp",
@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("MyPage", value.Value);
Assert.Equal("/MyPage", value.Value);
});
Assert.Equal("ftp", context.Protocol);
Assert.Equal("test-fragment", context.Fragment);

View File

@ -903,6 +903,54 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.Equal(expected, response.Headers.Location.ToString());
}
[Fact]
public async Task TagHelpers_SupportSiblingRoutes()
{
// Arrange
var expected =
@"<form method=""post"" action=""/Pages/TagHelper/CrossPost""></form>
<a href=""/Pages/TagHelper/SelfPost/12"" />
<input type=""image"" formaction=""/Pages/TagHelper/CrossPost#my-fragment"" />";
// Act
var response = await Client.GetStringAsync("/Pages/TagHelper/SiblingLinks");
// Assert
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task TagHelpers_SupportSubDirectoryRoutes()
{
// Arrange
var expected =
@"<form method=""post"" action=""/Pages/TagHelper/SubDir/SubDirPage""></form>
<a href=""/Pages/TagHelper/SubDir/SubDirPage/12"" />
<input type=""image"" formaction=""/Pages/TagHelper/SubDir/SubDirPage#my-fragment"" />";
// Act
var response = await Client.GetStringAsync("/Pages/TagHelper/SubDirectoryLinks");
// Assert
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task TagHelpers_SupportsPathNavigation()
{
// Arrange
var expected =
@"<form method=""post"" action=""/HelloWorld""></form>
<a href=""/Pages/Redirects/RedirectToIndex"" />
<input type=""image"" formaction=""/Pages/Admin#my-fragment"" />";
// Act
var response = await Client.GetStringAsync("/Pages/TagHelper/PathTraversalLinks");
// Assert
Assert.Equal(expected, response.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -143,6 +143,48 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task FormTagHelper_WithPage_GeneratesLinksToSelf()
{
//Arrange
var expected = "<form method=\"POST\" action=\"/TagHelper/SelfPost/10\">";
// Act
var response = await Client.GetStringAsync("/TagHelper/SelfPost");
// Assert
Assert.Contains(expected, response.Trim());
}
[Fact]
public async Task FormTagHelper_WithPage_AllowsPostingToAnotherPage()
{
//Arrange
var expected = "<form method=\"POST\" action=\"/TagHelper/SelfPost/10\">";
// Act
var response = await Client.GetStringAsync("/TagHelper/CrossPost");
// Assert
Assert.Contains(expected, response.Trim());
}
[Fact]
public async Task FormActionTagHelper_WithPage_AllowsPostingToAnotherPage()
{
//Arrange
var expected =
@"<button formaction=""/TagHelper/CrossPost/10"" />
<input type=""submit"" formaction=""/TagHelper/CrossPost/10"" />
<input type=""image"" formaction=""/TagHelper/CrossPost/10"" />";
// Act
var response = await Client.GetStringAsync("/TagHelper/FormAction");
// Assert
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task RedirectFromPage_RedirectsToPathWithoutIndexSegment()
{
@ -170,5 +212,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal(expected, response.Headers.Location.ToString());
}
[Fact]
public async Task PageRoute_UsingDefaultPageNameToRoute()
{
// Arrange
var expected = @"<a href=""/Routes/Sibling/10"">Link</a>";
// Act
var response = await Client.GetStringAsync("/Routes/RouteUsingDefaultName");
// Assert
Assert.Equal(expected, response.Trim());
}
}
}

View File

@ -4,13 +4,13 @@
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.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing;
using Moq;
using Xunit;
@ -366,6 +366,55 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Empty(output.Content.GetContent());
}
[Fact]
public async Task ProcessAsync_AddsPageToRouteValuesAndCallsPageLinkWithExpectedParameters()
{
// Arrange
var context = new TagHelperContext(
tagName: "a",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.Content.SetContent(string.Empty);
var generator = new Mock<IHtmlGenerator>();
generator
.Setup(mock => mock.GeneratePageLink(
It.IsAny<ViewContext>(),
string.Empty,
"/User/Home/Index",
"http",
"contoso.com",
"hello=world",
It.IsAny<object>(),
null))
.Returns(new TagBuilder("a"))
.Verifiable();
var anchorTagHelper = new AnchorTagHelper(generator.Object)
{
Page = "/User/Home/Index",
Fragment = "hello=world",
Host = "contoso.com",
Protocol = "http",
};
// Act
await anchorTagHelper.ProcessAsync(context, output);
// Assert
generator.Verify();
}
[Theory]
[InlineData("Action")]
[InlineData("Controller")]
@ -374,6 +423,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[InlineData("Host")]
[InlineData("Fragment")]
[InlineData("asp-route-")]
[InlineData("Page")]
public async Task ProcessAsync_ThrowsIfHrefConflictsWithBoundAttributes(string propertyName)
{
// Arrange
@ -399,9 +449,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
var expectedErrorMessage = "Cannot override the 'href' attribute for <a>. An <a> with a specified " +
"'href' must not have attributes starting with 'asp-route-' or an " +
"'asp-action', 'asp-controller', 'asp-area', 'asp-route', 'asp-protocol', 'asp-host', or " +
"'asp-fragment' attribute.";
"'href' must not have attributes starting with 'asp-route-' or an " +
"'asp-action', 'asp-controller', 'asp-area', 'asp-route', 'asp-protocol', 'asp-host', " +
"'asp-fragment', or 'asp-page' attribute.";
var context = new TagHelperContext(
tagName: "test",
@ -436,8 +486,88 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = "Cannot determine an 'href' attribute for <a>. An <a> with a specified " +
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'href' attribute for <a>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "test",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => anchorTagHelper.ProcessAsync(context, output));
Assert.Equal(expectedErrorMessage, ex.Message);
}
[Fact]
public async Task ProcessAsync_ThrowsIfRouteAndPageProvided()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var anchorTagHelper = new AnchorTagHelper(htmlGenerator)
{
Route = "Default",
Page = "Page",
};
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'href' attribute for <a>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "test",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => anchorTagHelper.ProcessAsync(context, output));
Assert.Equal(expectedErrorMessage, ex.Message);
}
[Fact]
public async Task ProcessAsync_ThrowsIfActionAndPageProvided()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var anchorTagHelper = new AnchorTagHelper(htmlGenerator)
{
Action = "Action",
Page = "Page",
};
var output = new TagHelperOutput(
"a",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'href' attribute for <a>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "test",

View File

@ -510,8 +510,88 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
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', 'asp-controller', or 'asp-fragment' attribute.";
var expectedErrorMessage = string.Join(
Environment.NewLine,
$"Cannot determine the 'formaction' attribute for <{tagName}>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form-action",
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")]
[InlineData("submit")]
public async Task ProcessAsync_ThrowsIfRouteAndPageProvided(string tagName)
{
// Arrange
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
var tagHelper = new FormActionTagHelper(urlHelperFactory)
{
Route = "Default",
Page = "Page",
};
var output = new TagHelperOutput(
tagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
$"Cannot determine the 'formaction' attribute for <{tagName}>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form-action",
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")]
[InlineData("submit")]
public async Task ProcessAsync_ThrowsIfActionAndPageProvided(string tagName)
{
// Arrange
var urlHelperFactory = new Mock<IUrlHelperFactory>().Object;
var tagHelper = new FormActionTagHelper(urlHelperFactory)
{
Action = "Default",
Page = "Page",
};
var output = new TagHelperOutput(
tagName,
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
$"Cannot determine the 'formaction' attribute for <{tagName}>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form-action",

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
@ -201,8 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Fixes Roslyn bug with lambdas
generator.ToString();
var routeValueDictionary = (Dictionary<string, object>)routeValues;
var routeValueDictionary = Assert.IsType<RouteValueDictionary>(routeValues);
Assert.Equal(2, routeValueDictionary.Count);
var routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("val"));
Assert.Equal("hello", routeValue.Value);
@ -552,7 +550,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
.Setup(mock => mock.GenerateRouteForm(
viewContext,
"Default",
It.Is<Dictionary<string, object>>(m => string.Equals(m["name"], "value")),
It.Is<RouteValueDictionary>(m => string.Equals(m["name"], "value")),
null,
null))
.Returns(new TagBuilder("form"))
@ -582,6 +580,50 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Empty(output.PostElement.GetContent());
}
[Fact]
public async Task ProcessAsync_InvokesGeneratePageForm()
{
// Arrange
var viewContext = CreateViewContext();
var context = new TagHelperContext(
tagName: "form",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
generator
.Setup(mock => mock.GeneratePageForm(
viewContext,
"/Home/Admin/Post",
null,
"hello-world",
null,
null))
.Returns(new TagBuilder("form"))
.Verifiable();
var formTagHelper = new FormTagHelper(generator.Object)
{
Antiforgery = false,
ViewContext = viewContext,
Page = "/Home/Admin/Post",
Fragment = "hello-world",
};
// Act & Assert
await formTagHelper.ProcessAsync(context, output);
generator.Verify();
}
[Theory]
[InlineData(true, "<input />")]
[InlineData(false, "")]
@ -638,6 +680,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Theory]
[InlineData("Action")]
[InlineData("Controller")]
[InlineData("Page")]
[InlineData("asp-route-")]
public async Task ProcessAsync_ThrowsIfActionConflictsWithBoundAttributes(string propertyName)
{
@ -660,8 +703,8 @@ 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', 'asp-controller', 'asp-fragment', 'asp-area', or 'asp-route' attribute.";
"'action' must not have attributes starting with 'asp-route-' or an " +
"'asp-action', 'asp-controller', 'asp-fragment', 'asp-area', 'asp-route' or 'asp-page' attribute.";
var context = new TagHelperContext(
tagName: "form",
@ -692,8 +735,80 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
"form",
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', 'asp-controller', or 'asp-fragment' attribute.";
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'action' attribute for <form>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => formTagHelper.ProcessAsync(context, output));
Assert.Equal(expectedErrorMessage, ex.Message);
}
[Fact]
public async Task ProcessAsync_ThrowsIfRouteAndPageProvided()
{
// Arrange
var formTagHelper = new FormTagHelper(new TestableHtmlGenerator(new EmptyModelMetadataProvider()))
{
Route = "Default",
Page = "Page",
};
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'action' attribute for <form>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form",
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => formTagHelper.ProcessAsync(context, output));
Assert.Equal(expectedErrorMessage, ex.Message);
}
[Fact]
public async Task ProcessAsync_ThrowsIfActionAndPageProvided()
{
// Arrange
var formTagHelper = new FormTagHelper(new TestableHtmlGenerator(new EmptyModelMetadataProvider()))
{
Action = "Default",
Page = "Page",
};
var output = new TagHelperOutput(
"form",
attributes: new TagHelperAttributeList(),
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(null));
var expectedErrorMessage = string.Join(
Environment.NewLine,
"Cannot determine the 'action' attribute for <form>. The following attributes are mutually exclusive:",
"asp-route",
"asp-controller, asp-action",
"asp-page");
var context = new TagHelperContext(
tagName: "form",

View File

@ -0,0 +1,2 @@
@page
<a asp-page="/Routes/Sibling" asp-route-id="10">Link</a>

View File

@ -0,0 +1 @@
@page "{id?}"

View File

@ -0,0 +1 @@
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"

View File

@ -0,0 +1,5 @@
@page "{id:int?}"
<form method="POST" asp-page="/TagHelper/SelfPost" asp-route-id="10">
<input type="submit" value="Submit" />
</form>

View File

@ -0,0 +1,4 @@
@page
<button asp-page="/TagHelper/CrossPost" asp-route-id="10" />
<input type="submit" asp-page="/TagHelper/CrossPost" asp-route-id="10" />
<input type="image" asp-page="/TagHelper/CrossPost" asp-route-id="10" />

View File

@ -0,0 +1,5 @@
@page
<form method="post" asp-page="../../HelloWorld" asp-antiforgery="false"></form>
<a asp-page="../Redirects/Index" asp-route-formaction="RedirectToIndex" />
<input type="image" asp-page="../Admin/Index" asp-fragment="my-fragment" />

View File

@ -0,0 +1,5 @@
@page "{id:int?}"
<form method="POST" asp-page="" asp-route-id="10">
<input type="submit" value="Submit" />
</form>

View File

@ -0,0 +1,5 @@
@page
<form method="post" asp-page="CrossPost" asp-antiforgery="false"></form>
<a asp-page="SelfPost" asp-route-id="12" />
<input type="image" asp-page="CrossPost" asp-fragment="my-fragment" />

View File

@ -0,0 +1,2 @@
@page "{id?}"

View File

@ -0,0 +1,5 @@
@page
<form method="post" asp-page="SubDir/SubDirPage" asp-antiforgery="false"></form>
<a asp-page="SubDir/SubDirPage" asp-route-id="12" />
<input type="image" asp-page="SubDir/SubDirPage" asp-fragment="my-fragment" />

View File

@ -0,0 +1 @@
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"