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);