diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs new file mode 100644 index 0000000000..a6149fcf1d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc +{ + public class RedirectToActionResult : IActionResult + { + public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName, + string controllerName, IDictionary routeValues) + : this(urlHelper, actionName, controllerName, routeValues, permanent: false) + { + } + + public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName, + string controllerName, IDictionary routeValues, bool permanent) + { + UrlHelper = urlHelper; + ActionName = actionName; + ControllerName = controllerName; + RouteValues = routeValues; + Permanent = permanent; + } + + public IUrlHelper UrlHelper { get; private set; } + + public string ActionName { get; private set; } + + public string ControllerName { get; private set; } + + public IDictionary RouteValues { get; private set; } + + public bool Permanent { get; private set; } + + public async Task ExecuteResultAsync([NotNull] ActionContext context) + { + var destinationUrl = UrlHelper.Action(ActionName, ControllerName, RouteValues); + + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs new file mode 100644 index 0000000000..dab9c9311a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc +{ + public class RedirectToRouteResult : IActionResult + { + public RedirectToRouteResult([NotNull] IUrlHelper urlHelper, IDictionary routeValues) + : this(urlHelper, routeValues, permanent: false) + { + } + + public RedirectToRouteResult([NotNull] IUrlHelper urlHelper, + IDictionary routeValues, bool permanent) + { + UrlHelper = urlHelper; + RouteValues = routeValues; + Permanent = permanent; + } + + public IUrlHelper UrlHelper { get; private set; } + + public IDictionary RouteValues { get; private set; } + + public bool Permanent { get; private set; } + + public async Task ExecuteResultAsync([NotNull] ActionContext context) + { + var destinationUrl = UrlHelper.RouteUrl(RouteValues); + + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index f652bc911a..9040abb6e1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -119,5 +119,59 @@ namespace Microsoft.AspNet.Mvc return new RedirectResult(url, permanent: true); } + + public RedirectToActionResult RedirectToAction(string actionName) + { + return RedirectToAction(actionName, routeValues: null); + } + + public RedirectToActionResult RedirectToAction(string actionName, object routeValues) + { + return RedirectToAction(actionName, controllerName: null, routeValues: routeValues); + } + + public RedirectToActionResult RedirectToAction(string actionName, string controllerName) + { + return RedirectToAction(actionName, controllerName, routeValues: null); + } + + public RedirectToActionResult RedirectToAction(string actionName, string controllerName, + object routeValues) + { + return new RedirectToActionResult(Url, actionName, controllerName, + TypeHelper.ObjectToDictionary(routeValues)); + } + + public RedirectToActionResult RedirectToActionPermanent(string actionName) + { + return RedirectToActionPermanent(actionName, routeValues: null); + } + + public RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues) + { + return RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues); + } + + public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName) + { + return RedirectToActionPermanent(actionName, controllerName, routeValues: null); + } + + public RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName, + object routeValues) + { + return new RedirectToActionResult(Url, actionName, controllerName, + TypeHelper.ObjectToDictionary(routeValues), permanent: true); + } + + public RedirectToRouteResult RedirectToRoute(object routeValues) + { + return new RedirectToRouteResult(Url, TypeHelper.ObjectToDictionary(routeValues)); + } + + public RedirectToRouteResult RedirectToRoutePermanent(object routeValues) + { + return new RedirectToRouteResult(Url, TypeHelper.ObjectToDictionary(routeValues), permanent: true); + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index f6b97780d3..f6177376f7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -538,6 +538,22 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("ViewData_WrongTModelType"), p0, p1); } + /// + /// No route matches the supplied values. + /// + internal static string NoRoutesMatched + { + get { return GetString("NoRoutesMatched"); } + } + + /// + /// No route matches the supplied values. + /// + internal static string FormatNoRoutesMatched() + { + return GetString("NoRoutesMatched"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 2f6151a79f..1ba191cef9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -216,4 +216,7 @@ The model item passed into the ViewDataDictionary is of type '{0}', but this ViewDataDictionary instance requires a model item of type '{1}'. + + No route matches the supplied values. + diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs new file mode 100644 index 0000000000..f55cdd2092 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults +{ + public class RedirectToActionResultTest + { + [Fact] + public async void RedirectToAction_Execute_PassesCorrectValuesToRedirect() + { + // Arrange + var expectedUrl = "SampleAction"; + var expectedPermanentFlag = false; + var httpContext = new Mock(); + var httpResponse = new Mock(); + httpContext.Setup(o => o.Response).Returns(httpResponse.Object); + + var actionContext = new ActionContext(httpContext.Object, + Mock.Of(), + new Dictionary(), + new ActionDescriptor()); + IUrlHelper urlHelper = GetMockUrlHelper(expectedUrl); + RedirectToActionResult result = new RedirectToActionResult(urlHelper, "SampleAction", null, null); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + // Verifying if Redirect was called with the specific Url and parameter flag. + // Thus we verify that the Url returned by UrlHelper is passed properly to + // Redirect method and that the method is called exactly once. + httpResponse.Verify(r => r.Redirect(expectedUrl, expectedPermanentFlag), Times.Exactly(1)); + } + + [Fact] + public void RedirectToAction_Execute_ThrowsOnNullUrl() + { + // Arrange + var httpContext = new Mock(); + httpContext.Setup(o => o.Response).Returns(new Mock().Object); + var actionContext = new ActionContext(httpContext.Object, + Mock.Of(), + new Dictionary(), + new ActionDescriptor()); + + IUrlHelper urlHelper = GetMockUrlHelper(returnValue: null); + RedirectToActionResult result = new RedirectToActionResult(urlHelper, null, null, null); + + // Act & Assert + ExceptionAssert.ThrowsAsync( + async () => + { + await result.ExecuteResultAsync(actionContext); + }, + "No route matches the supplied values."); + } + + private static IUrlHelper GetMockUrlHelper(string returnValue) + { + var urlHelper = new Mock(); + urlHelper.Setup(o => o.Action(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())).Returns(returnValue); + + return urlHelper.Object; + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs new file mode 100644 index 0000000000..62388bda89 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Core +{ + public class RedirectToRouteResultTest + { + [Theory] + [MemberData("RedirectToRouteData")] + public async void RedirectToRoute_Execute_PassesCorrectValuesToRedirect(object values) + { + // Arrange + var expectedUrl = "SampleAction"; + var expectedPermanentFlag = false; + var httpContext = new Mock(); + var httpResponse = new Mock(); + httpContext.Setup(o => o.Response).Returns(httpResponse.Object); + + var actionContext = new ActionContext(httpContext.Object, + Mock.Of(), + new Dictionary(), + new ActionDescriptor()); + IUrlHelper urlHelper = GetMockUrlHelper(expectedUrl); + RedirectToRouteResult result = new RedirectToRouteResult(urlHelper, TypeHelper.ObjectToDictionary(values)); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + // Verifying if Redirect was called with the specific Url and parameter flag. + // Thus we verify that the Url returned by UrlHelper is passed properly to + // Redirect method and that the method is called exactly once. + httpResponse.Verify(r => r.Redirect(expectedUrl, expectedPermanentFlag), Times.Exactly(1)); + } + + [Fact] + public void RedirectToRoute_Execute_ThrowsOnNullUrl() + { + // Arrange + var httpContext = new Mock(); + httpContext.Setup(o => o.Response).Returns(new Mock().Object); + var actionContext = new ActionContext(httpContext.Object, + Mock.Of(), + new Dictionary(), + new ActionDescriptor()); + + IUrlHelper urlHelper = GetMockUrlHelper(returnValue: null); + RedirectToRouteResult result = new RedirectToRouteResult(urlHelper, new Dictionary()); + + // Act & Assert + ExceptionAssert.ThrowsAsync( + async () => + { + await result.ExecuteResultAsync(actionContext); + }, + "No route matches the supplied values."); + } + + public static IEnumerable RedirectToRouteData + { + get + { + yield return new object[] { null }; + yield return + new object[] { + new Dictionary() { { "hello", "world" } } + }; + yield return + new object[] { + new RouteValueDictionary(new Dictionary() { + { "test", "case" }, { "sample", "route" } }) + }; + } + } + + private static IUrlHelper GetMockUrlHelper(string returnValue) + { + var urlHelper = new Mock(); + urlHelper.Setup(o => o.RouteUrl(It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(returnValue); + return urlHelper.Object; + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 0fa90f45a5..d20f2f2331 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -1,6 +1,7 @@ -using System; +using System.Collections.Generic; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Xunit; @@ -83,5 +84,182 @@ namespace Microsoft.AspNet.Mvc.Core ExceptionAssert.ThrowsArgument( () => controller.RedirectPermanent(url: url), "url", "The value cannot be null or empty"); } + + [Fact] + public void RedirectToAction_Temporary_Returns_SameAction() + { + // Arrange + var controller = new Controller(); + + // Act + var resultTemporary = controller.RedirectToAction("SampleAction"); + + // Assert + Assert.False(resultTemporary.Permanent); + Assert.Equal("SampleAction", resultTemporary.ActionName); + } + + [Fact] + public void RedirectToAction_Permanent_Returns_SameAction() + { + // Arrange + var controller = new Controller(); + + // Act + var resultPermanent = controller.RedirectToActionPermanent("SampleAction"); + + // Assert + Assert.True(resultPermanent.Permanent); + Assert.Equal("SampleAction", resultPermanent.ActionName); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("SampleController")] + public void RedirectToAction_Temporary_Returns_SameController(string controllerName) + { + // Arrange + var controller = new Controller(); + + // Act + var resultTemporary = controller.RedirectToAction("SampleAction", controllerName); + + // Assert + Assert.False(resultTemporary.Permanent); + Assert.Equal("SampleAction", resultTemporary.ActionName); + Assert.Equal(controllerName, resultTemporary.ControllerName); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("SampleController")] + public void RedirectToAction_Permanent_Returns_SameController(string controllerName) + { + // Arrange + var controller = new Controller(); + + // Act + var resultPermanent = controller.RedirectToActionPermanent("SampleAction", controllerName); + + // Assert + Assert.True(resultPermanent.Permanent); + Assert.Equal("SampleAction", resultPermanent.ActionName); + Assert.Equal(controllerName, resultPermanent.ControllerName); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToAction_Temporary_Returns_SameActionControllerAndRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultTemporary = controller.RedirectToAction("SampleAction", "SampleController", routeValues); + + // Assert + Assert.False(resultTemporary.Permanent); + Assert.Equal("SampleAction", resultTemporary.ActionName); + Assert.Equal("SampleController", resultTemporary.ControllerName); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultTemporary.RouteValues); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToAction_Permanent_Returns_SameActionControllerAndRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultPermanent = controller.RedirectToActionPermanent("SampleAction", "SampleController", routeValues); + + // Assert + Assert.True(resultPermanent.Permanent); + Assert.Equal("SampleAction", resultPermanent.ActionName); + Assert.Equal("SampleController", resultPermanent.ControllerName); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultPermanent.RouteValues); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToAction_Temporary_Returns_SameActionAndRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultTemporary = controller.RedirectToAction(actionName: null, routeValues: routeValues); + + // Assert + Assert.False(resultTemporary.Permanent); + Assert.Null(resultTemporary.ActionName); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultTemporary.RouteValues); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToAction_Permanent_Returns_SameActionAndRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultPermanent = controller.RedirectToActionPermanent(null, routeValues); + + // Assert + Assert.True(resultPermanent.Permanent); + Assert.Null(resultPermanent.ActionName); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultPermanent.RouteValues); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToRoute_Temporary_Returns_SameRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultTemporary = controller.RedirectToRoute(routeValues); + + // Assert + Assert.False(resultTemporary.Permanent); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultTemporary.RouteValues); + } + + [Theory] + [MemberData("RedirectTestData")] + public void RedirectToRoute_Permanent_Returns_SameRouteValues(object routeValues) + { + // Arrange + var controller = new Controller(); + + // Act + var resultPermanent = controller.RedirectToRoutePermanent(routeValues); + + // Assert + Assert.True(resultPermanent.Permanent); + Assert.Equal(TypeHelper.ObjectToDictionary(routeValues), resultPermanent.RouteValues); + } + + public static IEnumerable RedirectTestData + { + get + { + yield return new object[] { null }; + yield return + new object[] { + new Dictionary() { { "hello", "world" } } + }; + yield return + new object[] { + new RouteValueDictionary(new Dictionary() { + { "test", "case" }, { "sample", "route" } }) + }; + } + } } }