Add convenience methods for redirecting to another Razor Page

Fixes #5956
This commit is contained in:
Pranav K 2017-03-31 14:34:29 -07:00
parent 2d19a82678
commit 925ad75cdf
16 changed files with 945 additions and 3 deletions

View File

@ -53,15 +53,19 @@ namespace Microsoft.Extensions.DependencyInjection
// Internal for testing.
internal static void AddServices(IServiceCollection services)
{
// Options
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
// Action Invoker
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
// Page and Page model creation and activation
services.TryAddSingleton<IPageModelActivatorProvider, DefaultPageModelActivatorProvider>();
services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>();
@ -70,12 +74,15 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
services.TryAddSingleton<PageResultExecutor>();
// Page model binding
services.TryAddSingleton<PageArgumentBinder, DefaultPageArgumentBinder>();
services.TryAddSingleton<IActionDescriptorChangeProvider, PageActionDescriptorChangeProvider>();
// Action executors
services.TryAddSingleton<PageResultExecutor>();
services.TryAddSingleton<RedirectToPageResultExecutor>();
// Random infrastructure
services.TryAddSingleton<TempDataPropertyProvider>();
}
}

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private static readonly Action<ILogger, string, double, Exception> _pageExecuted;
private static readonly Action<ILogger, object, Exception> _exceptionFilterShortCircuit;
private static readonly Action<ILogger, object, Exception> _pageFilterShortCircuit;
private static readonly Action<ILogger, string, Exception> _redirectToPageResultExecuting;
static PageLoggerExtensions()
{
@ -41,6 +42,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
3,
"Request was short circuited at page filter '{PageFilter}'.");
_redirectToPageResultExecuting = LoggerMessage.Define<string>(
LogLevel.Information,
5,
"Executing RedirectToPageResult, redirecting to {Page}.");
}
public static IDisposable PageScope(this ILogger logger, ActionDescriptor actionDescriptor)
@ -82,6 +87,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_pageFilterShortCircuit(logger, filter, null);
}
public static void RedirectToPageResultExecuting(this ILogger logger, string page)
=> _redirectToPageResultExecuting(logger, page, null);
private class PageLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly ActionDescriptor _action;
@ -111,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < Count; ++i)
for (var i = 0; i < Count; ++i)
{
yield return this[i];
}

View File

@ -0,0 +1,50 @@
// 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.Routing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RedirectToPageResultExecutor
{
private readonly ILogger _logger;
private readonly IUrlHelperFactory _urlHelperFactory;
public RedirectToPageResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
if (urlHelperFactory == null)
{
throw new ArgumentNullException(nameof(urlHelperFactory));
}
_logger = loggerFactory.CreateLogger<RedirectToRouteResult>();
_urlHelperFactory = urlHelperFactory;
}
public void Execute(ActionContext context, RedirectToPageResult result)
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
var destinationUrl = urlHelper.Page(
result.PageName,
result.RouteValues,
result.Protocol,
result.Host,
fragment: result.Fragment);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.FormatNoRoutesMatched(result.PageName));
}
_logger.RedirectToPageResultExecuting(result.PageName);
context.HttpContext.Response.Redirect(destinationUrl, result.Permanent);
}
}
}

View File

@ -138,6 +138,84 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
return new RedirectResult(url);
}
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected RedirectToPageResult RedirectToPage(string pageName)
=> RedirectToPage(pageName, routeValues: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected RedirectToPageResult RedirectToPage(string pageName, object routeValues)
=> RedirectToPage(pageName, routeValues, fragment: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected RedirectToPageResult RedirectToPage(string pageName, string fragment)
=> RedirectToPage(pageName, routeValues: null, fragment: fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/> and <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected RedirectToPageResult RedirectToPage(string pageName, object routeValues, string fragment)
=> new RedirectToPageResult(pageName, routeValues, fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected RedirectToPageResult RedirectToPagePermanent(string pageName)
=> RedirectToPagePermanent(pageName, routeValues: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues)
=> RedirectToPagePermanent(pageName, routeValues, fragment: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected RedirectToPageResult RedirectToPagePermanent(string pageName, string fragment)
=> RedirectToPagePermanent(pageName, routeValues: null, fragment: fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/> and <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues, string fragment)
=> new RedirectToPageResult(pageName, routeValues, permanent: true, fragment: fragment);
/// <summary>
/// Creates a <see cref="PageViewResult"/> object that renders this page as a view to the response.
/// </summary>

View File

@ -176,6 +176,84 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
return new RedirectResult(url);
}
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected internal RedirectToPageResult RedirectToPage(string pageName)
=> RedirectToPage(pageName, routeValues: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected internal RedirectToPageResult RedirectToPage(string pageName, object routeValues)
=> RedirectToPage(pageName, routeValues, fragment: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected internal RedirectToPageResult RedirectToPage(string pageName, string fragment)
=> RedirectToPage(pageName, routeValues: null, fragment: fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status302Found"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/> and <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/>.</returns>
protected internal RedirectToPageResult RedirectToPage(string pageName, object routeValues, string fragment)
=> new RedirectToPageResult(pageName, routeValues, fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected internal RedirectToPageResult RedirectToPagePermanent(string pageName)
=> RedirectToPagePermanent(pageName, routeValues: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected internal RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues)
=> RedirectToPagePermanent(pageName, routeValues, fragment: null);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected internal RedirectToPageResult RedirectToPagePermanent(string pageName, string fragment)
=> RedirectToPagePermanent(pageName, routeValues: null, fragment: fragment);
/// <summary>
/// Redirects (<see cref="StatusCodes.Status301MovedPermanently"/>) to the specified <paramref name="pageName"/>
/// using the specified <paramref name="routeValues"/> and <paramref name="fragment"/>.
/// </summary>
/// <param name="pageName">The name of the page.</param>
/// <param name="routeValues">The parameters for a route.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
/// <returns>The <see cref="RedirectToPageResult"/> with <see cref="RedirectToPageResult.Permanent"/> set.</returns>
protected internal RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues, string fragment)
=> new RedirectToPageResult(pageName, routeValues, permanent: true, fragment: fragment);
/// <summary>
/// Creates a <see cref="PageViewResult"/> object that renders the page.
/// </summary>

View File

@ -0,0 +1,114 @@
// 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.Routing;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Razor page specific extensions for <see cref="IUrlHelper"/>.
/// </summary>
public static class PageNameUrlHelperExtensions
{
/// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param>
/// <returns>The generated URL.</returns>
public static string Page(this IUrlHelper urlHelper, string pageName)
=> Page(urlHelper, pageName, values: null);
/// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param>
/// <param name="values">An object that contains route values.</param>
/// <returns>The generated URL.</returns>
public static string Page(
this IUrlHelper urlHelper,
string pageName,
object values)
=> Page(urlHelper, pageName, values, protocol: null);
/// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
/// <returns>The generated URL.</returns>
public static string Page(
this IUrlHelper urlHelper,
string pageName,
object values,
string protocol)
=> Page(urlHelper, pageName, values, protocol, host: null, fragment: null);
/// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
/// <param name="host">The host name for the URL.</param>
/// <returns>The generated URL.</returns>
public static string Page(
this IUrlHelper urlHelper,
string pageName,
object values,
string protocol,
string host)
=> Page(urlHelper, pageName, values, protocol, host, fragment: null);
/// <summary>
/// Generates a URL with an absolute path for the specified <paramref name="pageName"/>.
/// </summary>
/// <param name="urlHelper">The <see cref="IUrlHelper"/>.</param>
/// <param name="pageName">The page name to generate the url for.</param>
/// <param name="values">An object that contains route values.</param>
/// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
/// <param name="host">The host name for the URL.</param>
/// <param name="fragment">The fragment for the URL.</param>
/// <returns>The generated URL.</returns>
public static string Page(
this IUrlHelper urlHelper,
string pageName,
object values,
string protocol,
string host,
string fragment)
{
if (urlHelper == null)
{
throw new ArgumentNullException(nameof(urlHelper));
}
var routeValues = new RouteValueDictionary(values);
if (pageName == null)
{
var ambientValues = urlHelper.ActionContext.RouteData.Values;
if (!routeValues.ContainsKey("page") &&
ambientValues.TryGetValue("page", out var value))
{
routeValues["page"] = value;
}
}
else
{
routeValues["page"] = pageName;
}
return urlHelper.RouteUrl(
routeName: null,
values: routeValues,
protocol: protocol,
host: host,
fragment: fragment);
}
}
}

View File

@ -122,6 +122,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
internal static string FormatPathMustBeAnAppRelativePath()
=> GetString("PathMustBeAnAppRelativePath");
/// <summary>
/// No page named '{0}' matches the supplied values.
/// </summary>
internal static string NoRoutesMatched
{
get => GetString("NoRoutesMatched");
}
/// <summary>
/// No page named '{0}' matches the supplied values.
/// </summary>
internal static string FormatNoRoutesMatched(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatched"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -0,0 +1,143 @@
// 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.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Found (302)
/// or Moved Permanently (301) response with a Location header.
/// Targets a registered route.
/// </summary>
public class RedirectToPageResult : ActionResult, IKeepTempDataResult
{
/// <summary>
/// Initializes a new instance of the <see cref="RedirectToPageResult"/> with the values
/// provided.
/// </summary>
/// <param name="pageName">The page to redirect to.</param>
public RedirectToPageResult(string pageName)
: this(pageName, routeValues: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RedirectToPageResult"/> with the values
/// provided.
/// </summary>
/// <param name="pageName">The page to redirect to.</param>
/// <param name="routeValues">The parameters for the route.</param>
public RedirectToPageResult(string pageName, object routeValues)
: this(pageName, routeValues, permanent: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RedirectToPageResult"/> with the values
/// provided.
/// </summary>
/// <param name="pageName">The name of the route.</param>
/// <param name="routeValues">The parameters for the route.</param>
/// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
public RedirectToPageResult(
string pageName,
object routeValues,
bool permanent)
: this(pageName, routeValues, permanent, fragment: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RedirectToPageResult"/> with the values
/// provided.
/// </summary>
/// <param name="pageName">The name of the route.</param>
/// <param name="routeValues">The parameters for the route.</param>
/// <param name="fragment">The fragment to add to the URL.</param>
public RedirectToPageResult(
string pageName,
object routeValues,
string fragment)
: this(pageName, routeValues, permanent: false, fragment: fragment)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RedirectToPageResult"/> with the values
/// provided.
/// </summary>
/// <param name="pageName">The name of the route.</param>
/// <param name="routeValues">The parameters for the route.</param>
/// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
/// <param name="fragment">The fragment to add to the URL.</param>
public RedirectToPageResult(
string pageName,
object routeValues,
bool permanent,
string fragment)
{
PageName = pageName;
RouteValues = routeValues == null ? new RouteValueDictionary() : new RouteValueDictionary(routeValues);
Permanent = permanent;
Fragment = fragment;
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper" /> used to generate URLs.
/// </summary>
public IUrlHelper UrlHelper { get; set; }
/// <summary>
/// Gets or sets the name of the page to route to.
/// </summary>
public string PageName { get; set; }
/// <summary>
/// Gets or sets the route data to use for generating the URL.
/// </summary>
public RouteValueDictionary RouteValues { get; set; }
/// <summary>
/// Gets or sets an indication that the redirect is permanent.
/// </summary>
public bool Permanent { get; set; }
/// <summary>
/// Gets or sets the fragment to add to the URL.
/// </summary>
public string Fragment { get; set; }
/// <summary>
/// Gets or sets the protocol for the URL, such as "http" or "https".
/// </summary>
public string Protocol { get; set; }
/// <summary>
/// Gets or sets the host name of the URL.
/// </summary>
public string Host { get; set; }
/// <inheritdoc />
public override void ExecuteResult(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(PageName))
{
throw new InvalidOperationException(
Resources.FormatPropertyOfTypeCannotBeNull(nameof(PageName), nameof(RedirectToPageResult)));
}
var executor = context.HttpContext.RequestServices.GetRequiredService<RedirectToPageResultExecutor>();
executor.Execute(context, this);
}
}
}

View File

@ -141,4 +141,7 @@
<data name="PathMustBeAnAppRelativePath" xml:space="preserve">
<value>Path must be an application relative path that starts with a forward slash '/'.</value>
</data>
<data name="NoRoutesMatched" xml:space="preserve">
<value>No page named '{0}' matches the supplied values.</value>
</data>
</root>

View File

@ -649,6 +649,34 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task RedirectFromPageWorks()
{
// Arrange
var expected = "/Pages/Redirects/Redirect/10";
// Act
var response = await Client.GetAsync("/Pages/Redirects/RedirectFromPage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal(expected, response.Headers.Location.ToString());
}
[Fact]
public async Task RedirectFromPageModelWorks()
{
// Arrange
var expected = "/Pages/Redirects/Redirect/12";
// Act
var response = await Client.GetAsync("/Pages/Redirects/RedirectFromModel");
// 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);

View File

@ -0,0 +1,250 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
public class PageUrlHelperExtensionsTest
{
[Fact]
public void Page_WithName_Works()
{
// Arrange
UrlRouteContext actual = null;
var routeData = new RouteData
{
Values =
{
{ "page", "ambient-page" },
}
};
var actionContext = new ActionContext
{
RouteData = routeData,
};
var urlHelper = new Mock<IUrlHelper>();
urlHelper.SetupGet(h => h.ActionContext)
.Returns(actionContext);
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext context) => actual = context);
// Act
urlHelper.Object.Page("TestPage");
// Assert
urlHelper.Verify();
Assert.NotNull(actual);
Assert.Null(actual.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(actual.Values),
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("TestPage", value.Value);
});
Assert.Null(actual.Host);
Assert.Null(actual.Protocol);
Assert.Null(actual.Fragment);
}
public static TheoryData Page_WithNameAndRouteValues_WorksData
{
get => new TheoryData<object>
{
{ new { id = 10 } },
{
new Dictionary<string, object>
{
["id"] = 10,
}
},
{
new RouteValueDictionary
{
["id"] = 10,
}
},
};
}
[Theory]
[MemberData(nameof(Page_WithNameAndRouteValues_WorksData))]
public void Page_WithNameAndRouteValues_Works(object values)
{
// Arrange
UrlRouteContext actual = null;
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext context) => actual = context);
// Act
urlHelper.Object.Page("TestPage", values);
// Assert
urlHelper.Verify();
Assert.NotNull(actual);
Assert.Null(actual.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(actual.Values),
value =>
{
Assert.Equal("id", value.Key);
Assert.Equal(10, value.Value);
},
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("TestPage", value.Value);
});
Assert.Null(actual.Host);
Assert.Null(actual.Protocol);
Assert.Null(actual.Fragment);
}
[Fact]
public void Page_WithNameRouteValuesAndProtocol_Works()
{
// Arrange
UrlRouteContext actual = null;
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext context) => actual = context);
// Act
urlHelper.Object.Page("TestPage", new { id = 13 }, "https");
// Assert
urlHelper.Verify();
Assert.NotNull(actual);
Assert.Null(actual.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(actual.Values),
value =>
{
Assert.Equal("id", value.Key);
Assert.Equal(13, value.Value);
},
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("TestPage", value.Value);
});
Assert.Equal("https", actual.Protocol);
Assert.Null(actual.Host);
Assert.Null(actual.Fragment);
}
[Fact]
public void Page_WithNameRouteValuesProtocolAndHost_Works()
{
// Arrange
UrlRouteContext actual = null;
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext context) => actual = context);
// Act
urlHelper.Object.Page("TestPage", new { id = 13 }, "https", "mytesthost");
// Assert
urlHelper.Verify();
Assert.NotNull(actual);
Assert.Null(actual.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(actual.Values),
value =>
{
Assert.Equal("id", value.Key);
Assert.Equal(13, value.Value);
},
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("TestPage", value.Value);
});
Assert.Equal("https", actual.Protocol);
Assert.Equal("mytesthost", actual.Host);
Assert.Null(actual.Fragment);
}
[Fact]
public void Page_WithNameRouteValuesProtocolHostAndFragment_Works()
{
// Arrange
UrlRouteContext actual = null;
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext context) => actual = context);
// Act
urlHelper.Object.Page("TestPage", new { id = 13 }, "https", "mytesthost", "#toc");
// Assert
urlHelper.Verify();
Assert.NotNull(actual);
Assert.Null(actual.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(actual.Values),
value =>
{
Assert.Equal("id", value.Key);
Assert.Equal(13, value.Value);
},
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("TestPage", value.Value);
});
Assert.Equal("https", actual.Protocol);
Assert.Equal("mytesthost", actual.Host);
Assert.Equal("#toc", actual.Fragment);
}
[Fact]
public void Page_UsesAmbientRouteValue_WhenPageIsNull()
{
// Arrange
UrlRouteContext actual = null;
var routeData = new RouteData
{
Values =
{
{ "page", "ambient-page" },
}
};
var actionContext = new ActionContext
{
RouteData = routeData,
};
var urlHelper = new Mock<IUrlHelper>();
urlHelper.SetupGet(p => p.ActionContext)
.Returns(actionContext);
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.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<RouteValueDictionary>(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);
});
Assert.Equal("https", actual.Protocol);
Assert.Equal("mytesthost", actual.Host);
Assert.Equal("#toc", actual.Fragment);
}
}
}

View File

@ -0,0 +1,151 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
public class RedirectToPageResultTest
{
[Fact]
public async Task ExecuteResultAsync_ThrowsOnNullUrl()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = CreateServices(),
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var urlHelper = GetUrlHelper(returnValue: null);
var result = new RedirectToPageResult("some-page", new Dictionary<string, object>())
{
UrlHelper = urlHelper,
};
// Act & Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
() => result.ExecuteResultAsync(actionContext),
"No page named 'some-page' matches the supplied values.");
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task ExecuteResultAsync_PassesCorrectValuesToRedirect(bool permanentRedirect)
{
// Arrange
var expectedUrl = "SampleAction";
var httpContext = new Mock<HttpContext>();
var httpResponse = new Mock<HttpResponse>();
httpContext.SetupGet(c => c.RequestServices)
.Returns(CreateServices());
httpContext.SetupGet(c => c.Response)
.Returns(httpResponse.Object);
var actionContext = new ActionContext(
httpContext.Object,
new RouteData(),
new ActionDescriptor());
var urlHelper = GetUrlHelper(expectedUrl);
var result = new RedirectToPageResult("MyPage", new { id = 10, test = "value" }, permanentRedirect)
{
UrlHelper = urlHelper,
};
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
httpResponse.Verify(r => r.Redirect(expectedUrl, permanentRedirect), Times.Exactly(1));
}
[Fact]
public async Task ExecuteResultAsync_WithAllParameters()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = CreateServices(),
};
var pageContext = new PageContext
{
HttpContext = httpContext,
};
UrlRouteContext context = null;
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
.Callback((UrlRouteContext c) => context = c)
.Returns("some-value");
var values = new { test = "test-value" };
var result = new RedirectToPageResult("MyPage", values, true, "test-fragment")
{
UrlHelper = urlHelper.Object,
Protocol = "ftp",
};
// Act
await result.ExecuteResultAsync(pageContext);
// Assert
Assert.NotNull(context);
Assert.Null(context.RouteName);
Assert.Collection(Assert.IsType<RouteValueDictionary>(context.Values),
value =>
{
Assert.Equal("test", value.Key);
Assert.Equal("test-value", value.Value);
},
value =>
{
Assert.Equal("page", value.Key);
Assert.Equal("MyPage", value.Value);
});
Assert.Equal("ftp", context.Protocol);
Assert.Equal("test-fragment", context.Fragment);
}
private static IServiceProvider CreateServices(IUrlHelperFactory factory = null)
{
var services = new ServiceCollection();
services.AddSingleton<RedirectToPageResultExecutor>();
if (factory != null)
{
services.AddSingleton(factory);
}
else
{
services.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
}
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return services.BuildServiceProvider();
}
private static IUrlHelper GetUrlHelper(string returnValue)
{
var urlHelper = new Mock<IUrlHelper>();
urlHelper.Setup(o => o.RouteUrl(It.IsAny<UrlRouteContext>())).Returns(returnValue);
return urlHelper.Object;
}
}
}

View File

@ -0,0 +1 @@
@page "{id:int}"

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
public class RedirectFromModel : PageModel
{
public IActionResult OnGet() => RedirectToPage("/Pages/Redirects/Redirect", new { id = 12});
}
}

View File

@ -0,0 +1,2 @@
@page
@model RazorPagesWebSite.RedirectFromModel

View File

@ -0,0 +1,5 @@
@page
@functions
{
public IActionResult OnGet() => RedirectToPage("/Pages/Redirects/Redirect", new { id = 10});
}