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