diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageUrlHelperExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageUrlHelperExtensions.cs index 09131d5ce8..c786bf5a11 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageUrlHelperExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageUrlHelperExtensions.cs @@ -89,9 +89,9 @@ namespace Microsoft.AspNetCore.Mvc } var routeValues = new RouteValueDictionary(values); + var ambientValues = urlHelper.ActionContext.RouteData.Values; if (pageName == null) { - var ambientValues = urlHelper.ActionContext.RouteData.Values; if (!routeValues.ContainsKey("page") && ambientValues.TryGetValue("page", out var value)) { @@ -103,6 +103,13 @@ namespace Microsoft.AspNetCore.Mvc routeValues["page"] = pageName; } + if (!routeValues.ContainsKey("formaction") && + ambientValues.TryGetValue("formaction", out var formaction)) + { + // Clear out formaction unless it's explicitly specified in the routeValues. + routeValues["formaction"] = null; + } + return urlHelper.RouteUrl( routeName: null, values: routeValues, diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 507b872b17..8ca4d70e11 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -763,6 +763,34 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP Assert.Equal(expected, response.Headers.Location.ToString()); } + [Fact] + public async Task RedirectDoesNotIncludeFormActionByDefault() + { + // Arrange + var expected = "/Pages/Redirects/RedirectFromFormActionHandler"; + + // Act + var response = await Client.GetAsync("/Pages/Redirects/RedirectFromFormActionHandler/RedirectToPage/10"); + + // Assert + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal(expected, response.Headers.Location.ToString()); + } + + [Fact] + public async Task RedirectToOtherHandlersWorks() + { + // Arrange + var expected = "/Pages/Redirects/RedirectFromFormActionHandler/RedirectToPage/11"; + + // Act + var response = await Client.GetAsync("/Pages/Redirects/RedirectFromFormActionHandler/RedirectToAnotherHandler/11"); + + // Assert + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal(expected, response.Headers.Location.ToString()); + } + private async Task AddAntiforgeryHeaders(HttpRequestMessage request) { var getResponse = await Client.GetAsync(request.RequestUri); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageUrlHelperExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageUrlHelperExtensionsTest.cs index 716640c14b..5fd67db646 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageUrlHelperExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageUrlHelperExtensionsTest.cs @@ -27,9 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { RouteData = routeData, }; - var urlHelper = new Mock(); - urlHelper.SetupGet(h => h.ActionContext) - .Returns(actionContext); + var urlHelper = CreateUrlHelper(actionContext); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -77,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { // Arrange UrlRouteContext actual = null; - var urlHelper = new Mock(); + var urlHelper = CreateUrlHelper(); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -109,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { // Arrange UrlRouteContext actual = null; - var urlHelper = new Mock(); + var urlHelper = CreateUrlHelper(); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -141,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { // Arrange UrlRouteContext actual = null; - var urlHelper = new Mock(); + var urlHelper = CreateUrlHelper(); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -173,7 +171,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { // Arrange UrlRouteContext actual = null; - var urlHelper = new Mock(); + var urlHelper = CreateUrlHelper(); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -217,9 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages RouteData = routeData, }; - var urlHelper = new Mock(); - urlHelper.SetupGet(p => p.ActionContext) - .Returns(actionContext); + var urlHelper = CreateUrlHelper(actionContext); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext context) => actual = context); @@ -246,5 +242,112 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages Assert.Equal("mytesthost", actual.Host); Assert.Equal("#toc", actual.Fragment); } + + [Fact] + public void Page_SetsFormActionToNull_IfValueIsNotSpecifiedInRouteValues() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "formaction", "ambient-formaction" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, new { id = 13 }, "https", "mytesthost", "#toc"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }, + value => + { + Assert.Equal("formaction", value.Key); + Assert.Null(value.Value); + }); + } + + [Fact] + public void Page_UsesExplicitlySpecifiedFormActionValue() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "formaction", "ambient-formaction" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, new { formaction = "exact-formaction" }, "https", "mytesthost", "#toc"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("formaction", value.Key); + Assert.Equal("exact-formaction", value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }); + } + + private static Mock CreateUrlHelper(ActionContext context = null) + { + if (context == null) + { + context = new ActionContext + { + RouteData = new RouteData(), + }; + } + + var urlHelper = new Mock(); + urlHelper.SetupGet(h => h.ActionContext) + .Returns(context); + return urlHelper; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/RedirectToPageResultTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/RedirectToPageResultTest.cs index da5429971d..ffb90722f7 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/RedirectToPageResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/RedirectToPageResultTest.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var urlHelper = GetUrlHelper(returnValue: null); + var urlHelper = GetUrlHelper(actionContext, returnValue: null); var result = new RedirectToPageResult("some-page", new Dictionary()) { UrlHelper = urlHelper, @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages new RouteData(), new ActionDescriptor()); - var urlHelper = GetUrlHelper(expectedUrl); + var urlHelper = GetUrlHelper(actionContext, expectedUrl); var result = new RedirectToPageResult("MyPage", new { id = 10, test = "value" }, permanentRedirect) { UrlHelper = urlHelper, @@ -89,10 +89,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages var pageContext = new PageContext { HttpContext = httpContext, + RouteData = new RouteData(), }; UrlRouteContext context = null; var urlHelper = new Mock(); + urlHelper.SetupGet(h => h.ActionContext).Returns(pageContext); urlHelper.Setup(h => h.RouteUrl(It.IsAny())) .Callback((UrlRouteContext c) => context = c) .Returns("some-value"); @@ -174,6 +176,62 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages }); } + [Fact] + public async Task RedirectToPage_DoesNotUseAmbientFormAction() + { + // Arrange + var expected = "path/to/this-page"; + var httpContext = new Mock(); + var httpResponse = new Mock(); + httpContext.SetupGet(c => c.Response) + .Returns(httpResponse.Object); + httpContext.SetupGet(c => c.RequestServices) + .Returns(CreateServices()); + var routeData = new RouteData + { + Values = + { + ["page"] = expected, + ["formaction"] = "delete", + } + }; + + var actionContext = new ActionContext( + httpContext.Object, + routeData, + new ActionDescriptor()); + + UrlRouteContext context = null; + var urlHelper = new Mock(); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext c) => context = c) + .Returns("some-value"); + urlHelper.SetupGet(h => h.ActionContext) + .Returns(actionContext); + var pageName = (string)null; + var result = new RedirectToPageResult(pageName) + { + UrlHelper = urlHelper.Object, + }; + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.NotNull(context); + Assert.Collection(Assert.IsType(context.Values), + value => + { + Assert.Equal("page", value.Key); + Assert.Equal(expected, value.Value); + }, + value => + { + Assert.Equal("formaction", value.Key); + Assert.Null(value.Value); + }); + } + private static IServiceProvider CreateServices(IUrlHelperFactory factory = null) { var services = new ServiceCollection(); @@ -192,9 +250,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages return services.BuildServiceProvider(); } - private static IUrlHelper GetUrlHelper(string returnValue) + private static IUrlHelper GetUrlHelper(ActionContext context, string returnValue) { var urlHelper = new Mock(); + urlHelper.SetupGet(h => h.ActionContext).Returns(context); urlHelper.Setup(o => o.RouteUrl(It.IsAny())).Returns(returnValue); return urlHelper.Object; } diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromFormActionHandler.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromFormActionHandler.cshtml new file mode 100644 index 0000000000..1bd4a1d1e3 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromFormActionHandler.cshtml @@ -0,0 +1,13 @@ +@page "{formaction?}/{id:int?}" +@functions +{ + public IActionResult OnGetRedirectToPage(int id) + { + return RedirectToPage(); + } + + public IActionResult OnGetRedirectToAnotherHandler(int id) + { + return RedirectToPage(new { formaction = "RedirectToPage", id = id }); + } +}