diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs index 40c673da8c..05fc09c335 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -117,8 +117,8 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// This method uses the value of to populate the host section of the generated URI. - /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless - /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly /// validate the Host header in your deployment environment. /// /// @@ -286,8 +286,8 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// This method uses the value of to populate the host section of the generated URI. - /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless - /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly /// validate the Host header in your deployment environment. /// /// @@ -443,8 +443,8 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// This method uses the value of to populate the host section of the generated URI. - /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless - /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly /// validate the Host header in your deployment environment. /// /// @@ -457,7 +457,7 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler, values, protocol, host: null, fragment: null); /// - /// Generates a URL with an absolute path for the specified . See the remarks section for + /// Generates a URL with an absolute path for the specified . See the remarks section for /// important security information. /// /// The . @@ -530,5 +530,57 @@ namespace Microsoft.AspNetCore.Mvc host: host, fragment: fragment); } + + /// + /// Generates an absolute URL for an action method, which contains the specified + /// name, name, route , + /// to use, name, and . + /// Generates an absolute URL if the and are + /// non-null. See the remarks section for important security information. + /// + /// The . + /// The name of the action method. When , defaults to the current executing action. + /// The name of the controller. When , defaults to the current executing controller. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The fragment for the URL. + /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// + public static string ActionLink( + this IUrlHelper helper, + string action = null, + string controller = null, + object values = null, + string protocol = null, + string host = null, + string fragment = null) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + var httpContext = helper.ActionContext.HttpContext; + + if (protocol == null) + { + protocol = httpContext.Request.Protocol; + } + + if (host == null) + { + host = httpContext.Request.Host.ToUriComponent(); + } + + return Action(helper, action, controller, values, protocol, host, fragment); + } } } diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs index 1e0f48bf34..a57daa314d 100644 --- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs +++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.ObjectPool; using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs index b63c66e0fe..8c9286e3f3 100644 --- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs +++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; @@ -601,6 +602,112 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing }); } + [Fact] + public void ActionLink_WithActionName_Works() + { + // Arrange + var expectedAction = "TestAction"; + var expectedProtocol = "testprotocol://"; + var expectedHost = "www.example.com"; + UrlActionContext actual = null; + + var httpContext = new DefaultHttpContext + { + Request = + { + Protocol = expectedProtocol, + Host = new HostString(expectedHost), + } + }; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.Action(It.IsAny())) + .Callback((UrlActionContext context) => actual = context); + + // Act + urlHelper.Object.ActionLink(expectedAction); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Equal(expectedAction, actual.Action); + Assert.Null(actual.Controller); + Assert.Null(actual.Values); + + Assert.Equal(expectedProtocol, actual.Protocol); + Assert.Equal(expectedHost, actual.Host); + } + + [Fact] + public void ActionLink_UsesSpecifiedProtocol() + { + // Arrange + var expectedProtocol = "testprotocol://"; + var expectedHost = "www.example.com"; + UrlActionContext actual = null; + + var httpContext = new DefaultHttpContext + { + Request = + { + Protocol = "http://", + Host = new HostString(expectedHost), + } + }; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.Action(It.IsAny())) + .Callback((UrlActionContext context) => actual = context); + + // Act + urlHelper.Object.ActionLink(protocol: expectedProtocol); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.Action); + Assert.Null(actual.Controller); + Assert.Null(actual.Values); + + Assert.Equal(expectedProtocol, actual.Protocol); + Assert.Equal(expectedHost, actual.Host); + } + + [Fact] + public void ActionLink_UsesSpecifiedHost() + { + // Arrange + var expectedProtocol = "testprotocol://"; + var expectedHost = "www.example.com"; + UrlActionContext actual = null; + + var httpContext = new DefaultHttpContext + { + Request = + { + Protocol = expectedProtocol, + Host = new HostString("www.asp.net"), + } + }; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.Action(It.IsAny())) + .Callback((UrlActionContext context) => actual = context); + + // Act + urlHelper.Object.ActionLink(host: expectedHost); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.Action); + Assert.Null(actual.Controller); + Assert.Null(actual.Values); + + Assert.Equal(expectedProtocol, actual.Protocol); + Assert.Equal(expectedHost, actual.Host); + } + private static Mock CreateMockUrlHelper(ActionContext context = null) { if (context == null) diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs index dd57e88248..b1e21e515c 100644 --- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs +++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.ObjectPool; using Moq; using Xunit; @@ -952,6 +951,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("/b/Store/Checkout", url); } + [Fact] + public void ActionLink_ReturnsAbsoluteUrlToAction() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.ActionLink("contact", "home"); + + // Assert + Assert.Equal("http://localhost/app/home/contact", url); + } + protected abstract IServiceProvider CreateServices(); protected abstract IUrlHelper CreateUrlHelper(ActionContext actionContext);