diff --git a/build/dependencies.props b/build/dependencies.props
index 7d118388d6..0223333d6a 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -16,87 +16,87 @@
0.43.0
2.1.1.1
2.1.1
- 2.2.0-preview3-35301
+ 2.2.0-preview3-35358
2.2.0-preview1-20180918.1
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
5.2.6
2.8.0
2.8.0
- 2.2.0-preview3-35301
+ 2.2.0-preview3-35358
1.7.0
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
2.1.0
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
2.0.9
2.1.3
- 2.2.0-preview2-26905-02
- 2.2.0-preview3-35301
- 2.2.0-preview3-35301
+ 2.2.0-preview3-26927-02
+ 2.2.0-preview3-35358
+ 2.2.0-preview3-35358
15.6.1
4.7.49
2.0.3
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
index c00c53bcf8..8acff61ab2 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs
@@ -1313,7 +1313,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0);
///
- /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
+ /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.
///
internal static string UrlHelper_RelativePagePathIsNotSupported
{
@@ -1321,10 +1321,10 @@ namespace Microsoft.AspNetCore.Mvc.Core
}
///
- /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
+ /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.
///
- internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0)
- => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);
+ internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0, object p1, object p2)
+ => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0, p1, p2);
///
/// One or more validation errors occurred.
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
index 30faf9ec02..f1d2e78ade 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx
@@ -410,7 +410,7 @@
No page named '{0}' matches the supplied values.
- The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
+ The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages.
One or more validation errors occurred.
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs
index 5c48596532..c468e5280c 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs
@@ -361,7 +361,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing
if (string.IsNullOrEmpty(currentPagePath))
{
// Disallow the use sibling page routing, a Razor page specific feature, from a non-page action.
- throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName));
+ // OR - this is a call from LinkGenerator where the HttpContext was not specified.
+ //
+ // We can't use a relative path in either case, because we don't know the base path.
+ throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(
+ pageName,
+ nameof(LinkGenerator),
+ nameof(HttpContext)));
}
return ViewEnginePath.CombinePath(currentPagePath, pageName);
diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs
index 16f29e1d67..aa246881eb 100644
--- a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs
@@ -10,6 +10,20 @@ namespace Microsoft.AspNetCore.Mvc.Testing
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Mvc.Testing.Resources", typeof(Resources).GetTypeInfo().Assembly);
+ ///
+ /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
+ ///
+ internal static string InvalidAssemblyEntryPoint
+ {
+ get => GetString("InvalidAssemblyEntryPoint");
+ }
+
+ ///
+ /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
+ ///
+ internal static string FormatInvalidAssemblyEntryPoint(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0);
+
///
/// No method 'public static {0} CreateWebHostBuilder(string[] args)' found on '{1}'. Alternatively, {2} can be extended and 'protected virtual {0} {3}()' can be overridden to provide your own {0} instance.
///
@@ -38,20 +52,6 @@ namespace Microsoft.AspNetCore.Mvc.Testing
internal static string FormatMissingDepsFile(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingDepsFile"), p0, p1);
- ///
- /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
- ///
- internal static string InvalidAssemblyEntryPoint
- {
- get => GetString("InvalidAssemblyEntryPoint");
- }
-
- ///
- /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.
- ///
- internal static string FormatInvalidAssemblyEntryPoint(string p0)
- => string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0);
-
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs
index f6b212c7e5..24d9ce80f5 100644
--- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs
@@ -501,8 +501,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing
// Act & Assert
var ex = Assert.Throws(() => urlHelper.Object.Page(expected));
- Assert.Equal($"The relative page path '{expected}' can only be used while executing a Razor Page. " +
- "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.", ex.Message);
+ Assert.Equal(
+ $"The relative page path '{expected}' can only be used while executing a Razor Page. " +
+ "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. " +
+ "If you are using LinkGenerator then you must provide the current HttpContext to use relative pages.",
+ ex.Message);
}
[Fact]
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs
new file mode 100644
index 0000000000..755a844425
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs
@@ -0,0 +1,221 @@
+// 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.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.FunctionalTests
+{
+ // Functional tests for MVC's scenarios with LinkGenerator (2.2+ only)
+ public class LinkGeneratorTest : IClassFixture>
+ {
+ public LinkGeneratorTest(MvcTestFixture fixture)
+ {
+ var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
+ Client = factory.CreateDefaultClient();
+ }
+
+ private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
+ builder.UseStartup();
+
+ public HttpClient Client { get; }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathToSelf()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToSelf");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LG1/LinkToSelf", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathToSelf_PreserveAmbientValues()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToSelf/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LG1/LinkToSelf/17?another-value=5", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathToAnotherAction_RemovesAmbientValues()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToAnotherAction/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LG1/LinkToSelf?another-value=5", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathToAnotherController_RemovesAmbientValues()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToAnotherController/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LG2/SomeAction?another-value=5", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathToAnotherControllerInArea_RemovesAmbientValues()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToAnArea/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/Admin/LG3/SomeAction?another-value=5", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathWithinArea()
+ {
+ // Act
+ var response = await Client.GetAsync("Admin/LG3/LinkInsideOfArea/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/Admin/LG3/SomeAction", responseContent);
+ }
+
+ // Rejected because the calling code relies on ambient values, but doesn't pass
+ // the HttpContext.
+ [Fact]
+ public async Task GetPathByAction_FailsToGenerateLinkInsideArea()
+ {
+ // Act
+ var response = await Client.GetAsync("Admin/LG3/LinkInsideOfAreaFail/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ Assert.Equal(string.Empty, responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathOutsideOfArea()
+ {
+ // Act
+ var response = await Client.GetAsync("Admin/LG3/LinkOutsideOfArea/17?another-value=5");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ Assert.Equal(string.Empty, responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByAction_CanGeneratePathFromPath()
+ {
+ // Act
+ var response = await Client.GetAsync("LGAnotherPage/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LG2/SomeAction", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByPage_FromPage_CanGeneratePathWithRelativePageName()
+ {
+ // Act
+ var response = await Client.GetAsync("LGPage/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LGAnotherPage", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByPage_CanGeneratePathToPage()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToPage/17?another-value=4");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/LGPage?another-value=4", responseContent);
+ }
+
+ [Fact]
+ public async Task GetPathByPage_CanGeneratePathToPageInArea()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToPageInArea/17?another-value=4");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("/Admin/LGAreaPage?another-value=4&handler=a-handler", responseContent);
+ }
+
+ [Fact]
+ public async Task GetUriByAction_CanGenerateFullUri()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkWithFullUri/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("http://localhost/LG1/LinkWithFullUri/17#hi", responseContent);
+ }
+
+ [Fact]
+ public async Task GetUriByAction_CanGenerateFullUri_WithoutHttpContext()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkWithFullUriWithoutHttpContext/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("https://www.example.com/LG1/LinkWithFullUri#hi", responseContent);
+ }
+
+ [Fact]
+ public async Task GetUriByPage_CanGenerateFullUri()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToPageWithFullUri/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("http://localhost/LGPage", responseContent);
+ }
+
+ [Fact]
+ public async Task GetUriByPage_CanGenerateFullUri_WithoutHttpContext()
+ {
+ // Act
+ var response = await Client.GetAsync("LG1/LinkToPageWithFullUriWithoutHttpContext/17");
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("https://www.example.com/Admin/LGAreaPage?handler=a-handler", responseContent);
+ }
+ }
+}
diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs b/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs
new file mode 100644
index 0000000000..dafe56ee22
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs
@@ -0,0 +1,44 @@
+// 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 Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Routing;
+
+namespace RoutingWebSite
+{
+ [Area("Admin")]
+ [Route("[area]/[controller]/[action]/{id?}")]
+ public class LG3Controller : Controller
+ {
+ private readonly LinkGenerator _linkGenerator;
+
+ public LG3Controller(LinkGenerator linkGenerator)
+ {
+ _linkGenerator = linkGenerator;
+ }
+
+ public void SomeAction()
+ {
+ }
+
+ public string LinkInsideOfArea()
+ {
+ return _linkGenerator.GetPathByAction(HttpContext, action: nameof(SomeAction));
+ }
+
+ public string LinkInsideOfAreaFail()
+ {
+ // No ambient values - this will fail.
+ return _linkGenerator.GetPathByAction(controller: "LG3", action: nameof(SomeAction));
+ }
+
+ public string LinkOutsideOfArea()
+ {
+ return _linkGenerator.GetPathByAction(
+ HttpContext,
+ action: nameof(SomeAction),
+ controller: "LG1",
+ values: new { area = "", });
+ }
+ }
+}
diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml
new file mode 100644
index 0000000000..11f5e9acec
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml
@@ -0,0 +1,4 @@
+@page "{id?}"
+@model RoutingWebSite.Areas.Admin.Pages.LGAreaPageModel
+@{
+}
diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs
new file mode 100644
index 0000000000..bc3137d121
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs
@@ -0,0 +1,14 @@
+// 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 Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace RoutingWebSite.Areas.Admin.Pages
+{
+ public class LGAreaPageModel : PageModel
+ {
+ public void OnGet()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs
new file mode 100644
index 0000000000..f8fb863add
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs
@@ -0,0 +1,119 @@
+// 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.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Routing;
+
+namespace RoutingWebSite
+{
+ public class LG1Controller : Controller
+ {
+ private readonly LinkGenerator _linkGenerator;
+
+ public LG1Controller(LinkGenerator linkGenerator)
+ {
+ _linkGenerator = linkGenerator;
+ }
+
+ public string LinkToSelf()
+ {
+ return _linkGenerator.GetPathByAction(HttpContext, values: QueryToRouteValues(HttpContext.Request.Query));
+ }
+
+ public string LinkToAnotherAction()
+ {
+ return _linkGenerator.GetPathByAction(
+ HttpContext,
+ action: nameof(LinkToSelf),
+ values: QueryToRouteValues(HttpContext.Request.Query));
+ }
+
+ public string LinkToAnotherController()
+ {
+ return _linkGenerator.GetPathByAction(
+ HttpContext,
+ controller: "LG2",
+ action: nameof(LG2Controller.SomeAction),
+ values: QueryToRouteValues(HttpContext.Request.Query));
+ }
+
+ public string LinkToAnArea()
+ {
+ var values = QueryToRouteValues(HttpContext.Request.Query);
+ values["area"] = "Admin";
+
+ return _linkGenerator.GetPathByAction(
+ HttpContext,
+ controller: "LG3",
+ action: nameof(LG3Controller.SomeAction),
+ values: values);
+ }
+
+ public string LinkToPage()
+ {
+ return _linkGenerator.GetPathByPage(
+ HttpContext,
+ page: "/LGPage",
+ values: QueryToRouteValues(HttpContext.Request.Query));
+ }
+
+ public string LinkToPageInArea()
+ {
+ var values = QueryToRouteValues(HttpContext.Request.Query);
+ values["area"] = "Admin";
+ return _linkGenerator.GetPathByPage(
+ HttpContext,
+ page: "/LGAreaPage",
+ handler: "a-handler",
+ values: values);
+ }
+
+ public string LinkWithFullUri()
+ {
+ return _linkGenerator.GetUriByAction(
+ HttpContext,
+ controller: "LG1",
+ action: nameof(LinkWithFullUri),
+ values: QueryToRouteValues(HttpContext.Request.Query),
+ fragment: new FragmentString("#hi"));
+ }
+
+ public string LinkToPageWithFullUri()
+ {
+ return _linkGenerator.GetUriByPage(
+ HttpContext,
+ page: "/LGPage",
+ values: QueryToRouteValues(HttpContext.Request.Query));
+ }
+
+ public string LinkWithFullUriWithoutHttpContext()
+ {
+ return _linkGenerator.GetUriByAction(
+ scheme: "https",
+ host: new HostString("www.example.com"),
+ controller: "LG1",
+ action: nameof(LinkWithFullUri),
+ values: QueryToRouteValues(HttpContext.Request.Query),
+ fragment: new FragmentString("#hi"));
+ }
+
+ public string LinkToPageWithFullUriWithoutHttpContext()
+ {
+ var values = QueryToRouteValues(HttpContext.Request.Query);
+ values["area"] = "Admin";
+ return _linkGenerator.GetUriByPage(
+ scheme: "https",
+ host: new HostString("www.example.com"),
+ page: "/LGAreaPage",
+ handler: "a-handler",
+ values: values);
+ }
+
+ private static RouteValueDictionary QueryToRouteValues(IQueryCollection query)
+ {
+ return new RouteValueDictionary(query.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString()));
+ }
+ }
+}
diff --git a/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs b/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs
new file mode 100644
index 0000000000..b8a25750fe
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs
@@ -0,0 +1,14 @@
+// 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 Microsoft.AspNetCore.Mvc;
+
+namespace RoutingWebSite
+{
+ public class LG2Controller : Controller
+ {
+ public void SomeAction()
+ {
+ }
+ }
+}
diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml
new file mode 100644
index 0000000000..d9a0e02177
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml
@@ -0,0 +1,4 @@
+@page "{id?}"
+@model RoutingWebSite.Pages.LGAnotherPageModel
+@{
+}
diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs
new file mode 100644
index 0000000000..1cc95da552
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs
@@ -0,0 +1,24 @@
+// 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 Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Routing;
+
+namespace RoutingWebSite.Pages
+{
+ public class LGAnotherPageModel : PageModel
+ {
+ private readonly LinkGenerator _linkGenerator;
+
+ public LGAnotherPageModel(LinkGenerator linkGenerator)
+ {
+ _linkGenerator = linkGenerator;
+ }
+
+ public ContentResult OnGet()
+ {
+ return Content(_linkGenerator.GetPathByAction(HttpContext, action: nameof(LG2Controller.SomeAction), controller: "LG2"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml
new file mode 100644
index 0000000000..598b475d1c
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml
@@ -0,0 +1,4 @@
+@page "{id?}"
+@model BasicWebSite.Pages.LGPageModel
+@{
+}
diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs
new file mode 100644
index 0000000000..56e82afd2d
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs
@@ -0,0 +1,24 @@
+// 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 Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Routing;
+
+namespace BasicWebSite.Pages
+{
+ public class LGPageModel : PageModel
+ {
+ private readonly LinkGenerator _linkGenerator;
+
+ public LGPageModel(LinkGenerator linkGenerator)
+ {
+ _linkGenerator = linkGenerator;
+ }
+
+ public ContentResult OnGet()
+ {
+ return Content(_linkGenerator.GetPathByPage(HttpContext, "./LGAnotherPage"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj
index cfda2f9c35..212a619b2e 100644
--- a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj
+++ b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj
@@ -10,5 +10,6 @@
+
diff --git a/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs
new file mode 100644
index 0000000000..3a85de8fda
--- /dev/null
+++ b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs
@@ -0,0 +1,34 @@
+// 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 Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace RoutingWebSite
+{
+ // A very basic routing configuration for LinkGenerator tests
+ public class StartupForLinkGenerator
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services
+ .AddMvc()
+ .SetCompatibilityVersion(CompatibilityVersion.Latest);
+ services
+ .AddRouting(options =>
+ {
+ options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+
+ services.AddScoped();
+ services.AddSingleton();
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseMvcWithDefaultRoute();
+ }
+ }
+}
\ No newline at end of file