diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs
index 5fc7b1257d..6adfa1738e 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs
@@ -469,7 +469,44 @@ namespace Microsoft.AspNetCore.Mvc
string controllerName,
object routeValues)
{
- return new RedirectToActionResult(actionName, controllerName, routeValues)
+ return RedirectToAction(actionName, controllerName, routeValues, fragment: null);
+ }
+
+ ///
+ /// Redirects to the specified action using the specified ,
+ /// , and .
+ ///
+ /// The name of the action.
+ /// The name of the controller.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToActionResult RedirectToAction(
+ string actionName,
+ string controllerName,
+ string fragment)
+ {
+ return RedirectToAction(actionName, controllerName, routeValues: null, fragment: fragment);
+ }
+
+ ///
+ /// Redirects to the specified action using the specified ,
+ /// , ,
+ /// and .
+ ///
+ /// The name of the action.
+ /// The name of the controller.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToActionResult RedirectToAction(
+ string actionName,
+ string controllerName,
+ object routeValues,
+ string fragment)
+ {
+ return new RedirectToActionResult(actionName, controllerName, routeValues, fragment)
{
UrlHelper = Url,
};
@@ -513,6 +550,24 @@ namespace Microsoft.AspNetCore.Mvc
return RedirectToActionPermanent(actionName, controllerName, routeValues: null);
}
+ ///
+ /// Redirects to the specified action with set to true
+ /// using the specified ,
+ /// , and .
+ ///
+ /// The name of the action.
+ /// The name of the controller.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToActionResult RedirectToActionPermanent(
+ string actionName,
+ string controllerName,
+ string fragment)
+ {
+ return RedirectToActionPermanent(actionName, controllerName, routeValues: null, fragment: fragment);
+ }
+
///
/// Redirects to the specified action with set to true
/// using the specified , ,
@@ -527,12 +582,33 @@ namespace Microsoft.AspNetCore.Mvc
string actionName,
string controllerName,
object routeValues)
+ {
+ return RedirectToActionPermanent(actionName, controllerName, routeValues, fragment: null);
+ }
+
+ ///
+ /// Redirects to the specified action with set to true
+ /// using the specified , ,
+ /// , and .
+ ///
+ /// The name of the action.
+ /// The name of the controller.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToActionResult RedirectToActionPermanent(
+ string actionName,
+ string controllerName,
+ object routeValues,
+ string fragment)
{
return new RedirectToActionResult(
actionName,
controllerName,
routeValues,
- permanent: true)
+ permanent: true,
+ fragment: fragment)
{
UrlHelper = Url,
};
@@ -570,7 +646,37 @@ namespace Microsoft.AspNetCore.Mvc
[NonAction]
public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
{
- return new RedirectToRouteResult(routeName, routeValues)
+ return RedirectToRoute(routeName, routeValues, fragment: null);
+ }
+
+ ///
+ /// Redirects to the specified route using the specified
+ /// and .
+ ///
+ /// The name of the route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToRouteResult RedirectToRoute(string routeName, string fragment)
+ {
+ return RedirectToRoute(routeName, routeValues: null, fragment: fragment);
+ }
+
+ ///
+ /// Redirects to the specified route using the specified ,
+ /// , and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToRouteResult RedirectToRoute(
+ string routeName,
+ object routeValues,
+ string fragment)
+ {
+ return new RedirectToRouteResult(routeName, routeValues, fragment)
{
UrlHelper = Url,
};
@@ -610,7 +716,38 @@ namespace Microsoft.AspNetCore.Mvc
[NonAction]
public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues)
{
- return new RedirectToRouteResult(routeName, routeValues, permanent: true)
+ return RedirectToRoutePermanent(routeName, routeValues, fragment: null);
+ }
+
+ ///
+ /// Redirects to the specified route with set to true
+ /// using the specified and .
+ ///
+ /// The name of the route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, string fragment)
+ {
+ return RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment);
+ }
+
+ ///
+ /// Redirects to the specified route with set to true
+ /// using the specified , ,
+ /// and .
+ ///
+ /// The name of the route.
+ /// The parameters for a route.
+ /// The fragment to add to the URL.
+ /// The created for the response.
+ [NonAction]
+ public virtual RedirectToRouteResult RedirectToRoutePermanent(
+ string routeName,
+ object routeValues,
+ string fragment)
+ {
+ return new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment)
{
UrlHelper = Url,
};
@@ -1082,7 +1219,7 @@ namespace Microsoft.AspNetCore.Mvc
///
/// Creates a object that produces an Accepted (202) response.
///
- /// The name of the route to use for generating the URL.
+ /// The name of the route to use for generating the URL.
/// The created for the response.
[NonAction]
public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName)
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs
index 81c613d443..119d54d4cd 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToActionResultExecutor.cs
@@ -33,7 +33,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
- var destinationUrl = urlHelper.Action(result.ActionName, result.ControllerName, result.RouteValues);
+ var destinationUrl = urlHelper.Action(
+ result.ActionName,
+ result.ControllerName,
+ result.RouteValues,
+ protocol: null,
+ host: null,
+ fragment: result.Fragment);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs
index 4e49476875..758a639a95 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RedirectToRouteResultExecutor.cs
@@ -33,7 +33,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context);
- var destinationUrl = urlHelper.RouteUrl(result.RouteName, result.RouteValues);
+ var destinationUrl = urlHelper.RouteUrl(
+ result.RouteName,
+ result.RouteValues,
+ protocol: null,
+ host: null,
+ fragment: result.Fragment);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs
index 908dca4644..ae83e5601e 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs
@@ -9,8 +9,20 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc
{
+ ///
+ /// An that returns a Found (302)
+ /// or Moved Permanently (301) response with a Location header.
+ /// Targets a controller action.
+ ///
public class RedirectToActionResult : ActionResult, IKeepTempDataResult
{
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
public RedirectToActionResult(
string actionName,
string controllerName,
@@ -19,16 +31,61 @@ namespace Microsoft.AspNetCore.Mvc
{
}
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// The fragment to add to the URL.
+ public RedirectToActionResult(
+ string actionName,
+ string controllerName,
+ object routeValues,
+ string fragment)
+ : this(actionName, controllerName, routeValues, permanent: false, fragment: fragment)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
public RedirectToActionResult(
string actionName,
string controllerName,
object routeValues,
bool permanent)
+ : this(actionName, controllerName, routeValues, permanent, fragment: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the action to use for generating the URL.
+ /// The name of the controller to use for generating the URL.
+ /// The route data to use for generating the URL.
+ /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
+ /// The fragment to add to the URL.
+ public RedirectToActionResult(
+ string actionName,
+ string controllerName,
+ object routeValues,
+ bool permanent,
+ string fragment)
{
ActionName = actionName;
ControllerName = controllerName;
RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
Permanent = permanent;
+ Fragment = fragment;
}
///
@@ -51,8 +108,16 @@ namespace Microsoft.AspNetCore.Mvc
///
public RouteValueDictionary RouteValues { get; set; }
+ ///
+ /// Gets or sets an indication that the redirect is permanent.
+ ///
public bool Permanent { get; set; }
+ ///
+ /// Gets or sets the fragment to add to the URL.
+ ///
+ public string Fragment { get; set; }
+
///
public override void ExecuteResult(ActionContext context)
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs
index ba0c4970c3..3241b13731 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs
@@ -9,13 +9,29 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc
{
+ ///
+ /// An that returns a Found (302)
+ /// or Moved Permanently (301) response with a Location header.
+ /// Targets a registered route.
+ ///
public class RedirectToRouteResult : ActionResult, IKeepTempDataResult
{
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The parameters for the route.
public RedirectToRouteResult(object routeValues)
: this(routeName: null, routeValues: routeValues)
{
}
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the route.
+ /// The parameters for the route.
public RedirectToRouteResult(
string routeName,
object routeValues)
@@ -23,14 +39,54 @@ namespace Microsoft.AspNetCore.Mvc
{
}
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the route.
+ /// The parameters for the route.
+ /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
public RedirectToRouteResult(
string routeName,
object routeValues,
bool permanent)
+ : this(routeName, routeValues, permanent, fragment: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the route.
+ /// The parameters for the route.
+ /// The fragment to add to the URL.
+ public RedirectToRouteResult(
+ string routeName,
+ object routeValues,
+ string fragment)
+ : this(routeName, routeValues, permanent: false, fragment: fragment)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the with the values
+ /// provided.
+ ///
+ /// The name of the route.
+ /// The parameters for the route.
+ /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).
+ /// The fragment to add to the URL.
+ public RedirectToRouteResult(
+ string routeName,
+ object routeValues,
+ bool permanent,
+ string fragment)
{
RouteName = routeName;
RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
Permanent = permanent;
+ Fragment = fragment;
}
///
@@ -48,8 +104,16 @@ namespace Microsoft.AspNetCore.Mvc
///
public RouteValueDictionary RouteValues { get; set; }
+ ///
+ /// Gets or sets an indication that the redirect is permanent.
+ ///
public bool Permanent { get; set; }
+ ///
+ /// Gets or sets the fragment to add to the URL.
+ ///
+ public string Fragment { get; set; }
+
///
public override void ExecuteResult(ActionContext context)
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs
index c2ea012596..f256c43bd5 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs
@@ -274,6 +274,30 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
Assert.Equal(expected, resultTemporary.RouteValues);
}
+ [Theory]
+ [MemberData(nameof(RedirectTestData))]
+ public void RedirectToAction_WithParameterActionAndControllerAndRouteValuesAndFragment_SetsResultProperties(
+ object routeValues,
+ IEnumerable> expectedRouteValues)
+ {
+ // Arrange
+ var controller = new TestableController();
+ var expectedAction = "Action";
+ var expectedController = "Home";
+ var expectedFragment = "test";
+
+ // Act
+ var result = controller.RedirectToAction("Action", "Home", routeValues, "test");
+
+ // Assert
+ Assert.IsType(result);
+ Assert.False(result.Permanent);
+ Assert.Equal(expectedAction, result.ActionName);
+ Assert.Equal(expectedRouteValues, result.RouteValues);
+ Assert.Equal(expectedController, result.ControllerName);
+ Assert.Equal(expectedFragment, result.Fragment);
+ }
+
[Theory]
[MemberData(nameof(RedirectTestData))]
public void RedirectToActionPermanent_WithParameterActionAndRouteValues_SetsResultProperties(
@@ -293,6 +317,30 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
Assert.Equal(expected, resultPermanent.RouteValues);
}
+ [Theory]
+ [MemberData(nameof(RedirectTestData))]
+ public void RedirectToActionPermanent_WithParameterActionAndControllerAndRouteValuesAndFragment_SetsResultProperties(
+ object routeValues,
+ IEnumerable> expectedRouteValues)
+ {
+ // Arrange
+ var controller = new TestableController();
+ var expectedAction = "Action";
+ var expectedController = "Home";
+ var expectedFragment = "test";
+
+ // Act
+ var result = controller.RedirectToActionPermanent("Action", "Home", routeValues, "test");
+
+ // Assert
+ Assert.IsType(result);
+ Assert.True(result.Permanent);
+ Assert.Equal(expectedAction, result.ActionName);
+ Assert.Equal(expectedRouteValues, result.RouteValues);
+ Assert.Equal(expectedController, result.ControllerName);
+ Assert.Equal(expectedFragment, result.Fragment);
+ }
+
[Theory]
[MemberData(nameof(RedirectTestData))]
public void RedirectToRoute_WithParameterRouteValues_SetsResultEqualRouteValues(
@@ -311,6 +359,28 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
Assert.Equal(expected, resultTemporary.RouteValues);
}
+ [Theory]
+ [MemberData(nameof(RedirectTestData))]
+ public void RedirectToRoute_WithParameterRouteNameAndRouteValuesAndFragment_SetsResultProperties(
+ object routeValues,
+ IEnumerable> expectedRouteValues)
+ {
+ // Arrange
+ var controller = new TestableController();
+ var expectedRoute = "TestRoute";
+ var expectedFragment = "test";
+
+ // Act
+ var result = controller.RedirectToRoute("TestRoute", routeValues, "test");
+
+ // Assert
+ Assert.IsType(result);
+ Assert.False(result.Permanent);
+ Assert.Equal(expectedRoute, result.RouteName);
+ Assert.Equal(expectedRouteValues, result.RouteValues);
+ Assert.Equal(expectedFragment, result.Fragment);
+ }
+
[Theory]
[MemberData(nameof(RedirectTestData))]
public void RedirectToRoutePermanent_WithParameterRouteValues_SetsResultEqualRouteValuesAndPermanent(
@@ -329,6 +399,28 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
Assert.Equal(expected, resultPermanent.RouteValues);
}
+ [Theory]
+ [MemberData(nameof(RedirectTestData))]
+ public void RedirectToRoutePermanent_WithParameterRouteNameAndRouteValuesAndFragment_SetsResultProperties(
+ object routeValues,
+ IEnumerable> expectedRouteValues)
+ {
+ // Arrange
+ var controller = new TestableController();
+ var expectedRoute = "TestRoute";
+ var expectedFragment = "test";
+
+ // Act
+ var result = controller.RedirectToRoutePermanent("TestRoute", routeValues, "test");
+
+ // Assert
+ Assert.IsType(result);
+ Assert.True(result.Permanent);
+ Assert.Equal(expectedRoute, result.RouteName);
+ Assert.Equal(expectedRouteValues, result.RouteValues);
+ Assert.Equal(expectedFragment, result.Fragment);
+ }
+
[Fact]
public void RedirectToRoute_WithParameterRouteName_SetsResultSameRouteName()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs
index a286a7e153..4652b13f73 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs
@@ -83,6 +83,34 @@ namespace Microsoft.AspNetCore.Mvc
"No route matches the supplied values.");
}
+ [Fact]
+ public async Task RedirectToAction_Execute_WithFragment_PassesCorrectValuesToRedirect()
+ {
+ // Arrange
+ var expectedUrl = "/Home/SampleAction#test";
+ var expectedStatusCode = StatusCodes.Status302Found;
+
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = CreateServices().BuildServiceProvider(),
+ };
+
+ var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
+
+ var urlHelper = GetMockUrlHelper(expectedUrl);
+ var result = new RedirectToActionResult("SampleAction", "Home", null, false, "test")
+ {
+ UrlHelper = urlHelper,
+ };
+
+ // Act
+ await result.ExecuteResultAsync(actionContext);
+
+ // Assert
+ Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
+
private static IUrlHelper GetMockUrlHelper(string returnValue)
{
var urlHelper = new Mock();
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs
index e6a71e485b..a7b4075cfa 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs
@@ -116,6 +116,31 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(locationUrl, httpContext.Response.Headers["Location"]);
}
+ [Fact]
+ public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect()
+ {
+ // Arrange
+ var expectedUrl = "/SampleAction#test";
+ var expectedStatusCode = StatusCodes.Status301MovedPermanently;
+
+ var httpContext = GetHttpContext();
+
+ var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
+
+ var urlHelper = GetMockUrlHelper(expectedUrl);
+ var result = new RedirectToRouteResult("Sample", null, true, "test")
+ {
+ UrlHelper = urlHelper,
+ };
+
+ // Act
+ await result.ExecuteResultAsync(actionContext);
+
+ // Assert
+ Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
+
private static HttpContext GetHttpContext(IUrlHelperFactory factory = null)
{
var services = CreateServices(factory);