Fix asp-page-handler to generate form tags correctly.

- Prior to this change using asp-page-handler on its own did not create correct `<form>` elements. There were multiple issues, one as that the `FormTagHelper` would purposefully drop into a no-op code path. Second is the `DefaultHtmlGenerator` didn't call through to the `UrlHelper` correctly.
- Added functional test cases to validate asp-page-handler can live on its own on a form tag. This also included adding a variant where method="post".
- Added a `FormTagHelper` unit test to validate the `PageHandler` property is consumed properly.

#6208
This commit is contained in:
N. Taylor Mullen 2017-04-26 16:18:57 -07:00
parent b1b3a816cc
commit 8bece90c83
5 changed files with 82 additions and 43 deletions

View File

@ -159,18 +159,19 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
var antiforgeryDefault = true;
var routeableParametersProvided = Action != null ||
Controller != null ||
Area != null ||
Page != null ||
PageHandler != null ||
Fragment != null ||
Route != null ||
(_routeValues != null && _routeValues.Count > 0);
// If "action" is already set, it means the user is attempting to use a normal <form>.
if (output.Attributes.TryGetAttribute(HtmlActionAttributeName, out var actionAttribute))
{
if (Action != null ||
Controller != null ||
Area != null ||
Page != null ||
PageHandler != null ||
Fragment != null ||
Route != null ||
(_routeValues != null && _routeValues.Count > 0))
if (routeableParametersProvided)
{
// User also specified bound attributes we cannot use.
throw new InvalidOperationException(
@ -254,13 +255,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
TagBuilder tagBuilder = null;
if (Action == null &&
Controller == null &&
Route == null &&
if (!routeableParametersProvided &&
_routeValues == null &&
Fragment == null &&
Area == null &&
Page == null &&
// Antiforgery will sometime be set globally via TagHelper Initializers, verify it was provided in the cshtml.
!context.AllAttributes.ContainsName(AntiforgeryAttributeName))
{

View File

@ -323,34 +323,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
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, pageHandler, routeValues, protocol: null, host: null, fragment: fragment);
}
var urlHelper = _urlHelperFactory.GetUrlHelper(viewContext);
var action = urlHelper.Page(pageName, pageHandler, routeValues, protocol: null, host: null, fragment: fragment);
return GenerateFormCore(viewContext, action, method, htmlAttributes);
}

View File

@ -156,6 +156,26 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Contains(expected, response.Trim());
}
[Fact]
public async Task FormTagHelper_WithPageHandler_AllowsPostingToSelf()
{
//Arrange
var expected =
@"<form action=""/TagHelper/PostWithHandler/Edit"" method=""post""><input name=""__RequestVerificationToken"" type=""hidden"" value=""{0}"" /></form>
<form method=""post"" action=""/TagHelper/PostWithHandler/Edit""><input name=""__RequestVerificationToken"" type=""hidden"" value=""{0}"" /></form>
<form method=""post"" action=""/TagHelper/PostWithHandler/Edit/10""></form>";
// Act
var response = await Client.GetStringAsync("/TagHelper/PostWithHandler");
// Assert
var responseContent = response.Trim();
var forgeryToken = AntiforgeryTestHelper.RetrieveAntiforgeryToken(responseContent, "/TagHelper/PostWithHandler");
var expectedContent = string.Format(expected, forgeryToken);
Assert.Equal(expectedContent, responseContent, ignoreLineEndingDifferences: true);
}
[Fact]
public async Task FormTagHelper_WithPage_AllowsPostingToAnotherPage()
{

View File

@ -25,6 +25,53 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
public class FormTagHelperTest
{
[Fact]
public async Task ProcessAsync_InvokesGeneratePageForm_WithOnlyPageHandler()
{
// Arrange
var viewContext = CreateViewContext();
var context = new TagHelperContext(
tagName: "form",
allAttributes: new TagHelperAttributeList()
{
{ "asp-handler", "page-handler" },
{ "method", "get" }
},
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,
null,
"page-handler",
null,
null,
null,
null))
.Returns(new TagBuilder("form"))
.Verifiable();
var formTagHelper = new FormTagHelper(generator.Object)
{
ViewContext = viewContext,
PageHandler = "page-handler",
Method = "get"
};
// Act & Assert
await formTagHelper.ProcessAsync(context, output);
generator.Verify();
}
[Fact]
public async Task ProcessAsync_ActionAndControllerGenerateAntiforgery()
{

View File

@ -1,6 +1,6 @@
@page "{handler?}/{id?}"
@function
@functions
{
public IActionResult OnPostEdit(int id)
{
@ -13,4 +13,6 @@
}
}
<form asp-page-handler="Edit"></form>
<form method="post" asp-page-handler="Edit"></form>
<form method="post" asp-page-handler="Edit" asp-route-id="10" asp-antiforgery="false"></form>