diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 0a1aa08e24..d371d02f79 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -268,24 +268,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } + // Replace parameter with literal value var parameterRouteValue = action.RouteValues[parameterPart.Name]; - // Replace parameter with literal value - if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) + // Route value could be null if it is a "known" route value. + // Do not use the null value to de-normalize the route pattern, + // instead leave the parameter unchanged. + // e.g. + // RouteValues will contain a null "page" value if there are Razor pages + // Skip replacing the {page} parameter + if (parameterRouteValue != null) { - // Check if the parameter has a transformer policy - // Use the first transformer policy - for (var k = 0; k < parameterPolicies.Count; k++) + if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { - if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) + // Check if the parameter has a transformer policy + // Use the first transformer policy + for (var k = 0; k < parameterPolicies.Count; k++) { - parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); - break; + if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) + { + parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); + break; + } } } - } - segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); + segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index d35dea52ca..24622f85cd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -251,6 +251,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Collection(endpoints, inspectors); } + [Fact] + public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction", page = (string)null }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}/{page}", + new RouteValueDictionary(new { action = "TestAction" }))); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Empty(endpoints); + } + + [Fact] + public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + "{controller}/{action}/{page}", + new { controller = "TestController", action = "TestAction", page = (string)null }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection(endpoints, + (e) => Assert.Equal("TestController/TestAction/{page}", Assert.IsType(e).RoutePattern.RawText)); + } + [Fact] public void Endpoints_SingleAction_WithActionDefault() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs new file mode 100644 index 0000000000..94d7a90f4c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs @@ -0,0 +1,47 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + public class LinkBuilder + { + public LinkBuilder(string url) + { + Url = url; + + Values = new Dictionary + { + { "link", string.Empty } + }; + } + + public string Url { get; set; } + + public Dictionary Values { get; set; } + + public LinkBuilder To(object values) + { + var dictionary = new RouteValueDictionary(values); + foreach (var kvp in dictionary) + { + Values.Add("link_" + kvp.Key, kvp.Value); + } + + return this; + } + + public override string ToString() + { + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); + } + + public static implicit operator string(LinkBuilder builder) + { + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs new file mode 100644 index 0000000000..3a72c75fae --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs @@ -0,0 +1,25 @@ +// 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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc +{ + // See TestResponseGenerator for the code that generates this data. + public class RoutingResult + { + public string[] ExpectedUrls { get; set; } + + public string ActualUrl { get; set; } + + public Dictionary RouteValues { get; set; } + + public string RouteName { get; set; } + + public string Action { get; set; } + + public string Controller { get; set; } + + public string Link { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs index 6761579f04..ffaebb4fe0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs @@ -382,7 +382,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var expected = "ConventionalRoute - Hello from mypage"; // Act - var response = await Client.GetStringAsync("/PageRoute/ConventionalRoute/mypage"); + var response = await Client.GetStringAsync("/PageRoute/ConventionalRouteView/mypage"); // Assert Assert.Equal(expected, response.Trim()); @@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var expected = "AttributeRoute - Hello from test-page"; // Act - var response = await Client.GetStringAsync("/PageRoute/Attribute/test-page"); + var response = await Client.GetStringAsync("/PageRoute/AttributeView/test-page"); // Assert Assert.Equal(expected, response.Trim()); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index bce36312a4..3573b91863 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -10,7 +10,6 @@ - @@ -29,6 +28,8 @@ + + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs similarity index 93% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs index daee488bae..e8a3d6a97d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -10,13 +11,34 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class EndpointRoutingTest : RoutingTestsBase + public class RoutingEndpointRoutingTest : RoutingTestsBase { - public EndpointRoutingTest(MvcTestFixture fixture) + public RoutingEndpointRoutingTest(MvcTestFixture fixture) : base(fixture) { } + [Fact] + public async Task AttributeRoutedAction_ContainsPage_RouteMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls); + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("AttributeRoute", result.Action); + + Assert.Contains( + new KeyValuePair("page", "pagevalue"), + result.RouteValues); + } + [Fact] public async Task ParameterTransformer_TokenReplacement_Found() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs new file mode 100644 index 0000000000..8b597e0ab8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs @@ -0,0 +1,19 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class RoutingEndpointRoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase + { + public RoutingEndpointRoutingWithoutRazorPagesTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index fbdb36f53d..b5b407d4e2 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -27,6 +27,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("ConventionalRoute", result.Action); + + // pagevalue is not used in "page" route value because it is a required value + Assert.False(result.RouteValues.ContainsKey("page")); + } + [Fact] public abstract Task HasEndpointMatch(); @@ -1282,61 +1301,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { return new LinkBuilder(url); } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - protected class RoutingResult - { - public string[] ExpectedUrls { get; set; } - - public string ActualUrl { get; set; } - - public Dictionary RouteValues { get; set; } - - public string RouteName { get; set; } - - public string Action { get; set; } - - public string Controller { get; set; } - - public string Link { get; set; } - } - - protected class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary - { - { "link", string.Empty } - }; - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs new file mode 100644 index 0000000000..6e90048fc6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs @@ -0,0 +1,19 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class RoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase + { + public RoutingWithoutRazorPagesTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs new file mode 100644 index 0000000000..061bf537fe --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs @@ -0,0 +1,68 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class RoutingWithoutRazorPagesTestsBase : IClassFixture> where TStartup : class + { + protected RoutingWithoutRazorPagesTestsBase(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 AttributeRoutedAction_ContainsPage_RouteMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls); + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("AttributeRoute", result.Action); + + Assert.Contains( + new KeyValuePair("page", "pagevalue"), + result.RouteValues); + } + + [Fact] + public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("ConventionalRoute", result.Action); + + Assert.Equal("pagevalue", result.RouteValues["page"]); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/test/WebSites/BasicWebSite/BasicWebSite.csproj index fd4b7e9416..2744228b2e 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.csproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + + diff --git a/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs b/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs index 2022d5e221..2f2073be06 100644 --- a/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs +++ b/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs @@ -9,14 +9,32 @@ namespace BasicWebSite.Controllers // without affecting view lookups. public class PageRouteController : Controller { + private readonly TestResponseGenerator _generator; + + public PageRouteController(TestResponseGenerator generator) + { + _generator = generator; + } + public IActionResult ConventionalRoute(string page) + { + return _generator.Generate("/PageRoute/ConventionalRoute/" + page); + } + + [HttpGet("/PageRoute/Attribute/{page}")] + public IActionResult AttributeRoute(string page) + { + return _generator.Generate("/PageRoute/Attribute/" + page); + } + + public IActionResult ConventionalRouteView(string page) { ViewData["page"] = page; return View(); } - [HttpGet("/PageRoute/Attribute/{page}")] - public IActionResult AttributeRoute(string page) + [HttpGet("/PageRoute/AttributeView/{page}")] + public IActionResult AttributeRouteView(string page) { ViewData["page"] = page; return View(); diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 86d5bab150..4cd7646bfb 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -42,6 +43,8 @@ namespace BasicWebSite services.AddSingleton(); services.AddScoped(); services.AddTransient(); + services.AddScoped(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs index 3f3356d7ef..01726c53e5 100644 --- a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -22,6 +23,8 @@ namespace BasicWebSite services.AddHttpContextAccessor(); services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app) @@ -35,6 +38,8 @@ namespace BasicWebSite "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute("PageRoute", "{controller}/{action}/{page}"); }); } } diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRoute.cshtml b/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/AttributeRoute.cshtml rename to test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRoute.cshtml b/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRoute.cshtml rename to test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml diff --git a/test/WebSites/VersioningWebSite/TestResponseGenerator.cs b/test/WebSites/Common/TestResponseGenerator.cs similarity index 96% rename from test/WebSites/VersioningWebSite/TestResponseGenerator.cs rename to test/WebSites/Common/TestResponseGenerator.cs index d9ff985c34..69427de69a 100644 --- a/test/WebSites/VersioningWebSite/TestResponseGenerator.cs +++ b/test/WebSites/Common/TestResponseGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; -namespace VersioningWebSite +namespace Microsoft.AspNetCore.Mvc { // Generates a response based on the expected URL and action context public class TestResponseGenerator @@ -63,4 +63,4 @@ namespace VersioningWebSite return urlHelper; } } -} \ No newline at end of file +} diff --git a/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs b/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs new file mode 100644 index 0000000000..0c629474f0 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs @@ -0,0 +1,28 @@ +// 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 PageRouteController + { + private readonly TestResponseGenerator _generator; + + public PageRouteController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult ConventionalRoute(string page) + { + return _generator.Generate("/PageRoute/ConventionalRoute/" + page); + } + + [HttpGet("/PageRoute/Attribute/{page}")] + public IActionResult AttributeRoute(string page) + { + return _generator.Generate("/PageRoute/Attribute/" + page); + } + } +} diff --git a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs index 40d40aa355..c04493b421 100644 --- a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs +++ b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs @@ -10,11 +10,11 @@ namespace RoutingWebSite { public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider { - private readonly Type _controllerType; + private readonly ControllerToRemove[] _controllerTypes; - public RemoveControllerActionDescriptorProvider(Type controllerType) + public RemoveControllerActionDescriptorProvider(params ControllerToRemove[] controllerTypes) { - _controllerType = controllerType; + _controllerTypes = controllerTypes; } public int Order => int.MaxValue; @@ -29,12 +29,22 @@ namespace RoutingWebSite { if (item is ControllerActionDescriptor controllerActionDescriptor) { - if (controllerActionDescriptor.ControllerTypeInfo == _controllerType) + var controllerToRemove = _controllerTypes.SingleOrDefault(c => c.ControllerType == controllerActionDescriptor.ControllerTypeInfo); + if (controllerToRemove != null) { - context.Results.Remove(item); + if (controllerToRemove.Actions == null || controllerToRemove.Actions.Contains(controllerActionDescriptor.ActionName)) + { + context.Results.Remove(item); + } } } } } } + + public class ControllerToRemove + { + public Type ControllerType { get; set; } + public string[] Actions { get; set; } + } } \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj index 212a619b2e..a570b1471e 100644 --- a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj +++ b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + + diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index c00450c9b7..1bf0041a0d 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -58,6 +58,12 @@ namespace RoutingWebSite defaults: new { controller = "Home", action = "Index" }, constraints: new { area = "Travel" }); + routes.MapRoute( + "PageRoute", + "{controller}/{action}/{page}", + defaults: null, + constraints: new { controller = "PageRoute" }); + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 44a6abfc61..e35a9b681a 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -28,7 +28,17 @@ namespace RoutingWebSite // EndpointRoutingController is not compatible with old routing // Remove its action to avoid errors - var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(typeof(EndpointRoutingController)); + var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider( + new ControllerToRemove + { + ControllerType = typeof(EndpointRoutingController), + Actions = null, // remove all + }, + new ControllerToRemove + { + ControllerType = typeof(PageRouteController), + Actions = new [] { nameof(PageRouteController.AttributeRoute) } + }); services.TryAddEnumerable(ServiceDescriptor.Singleton(actionDescriptorProvider)); } diff --git a/test/WebSites/RoutingWebSite/TestResponseGenerator.cs b/test/WebSites/RoutingWebSite/TestResponseGenerator.cs deleted file mode 100644 index 9b85f04456..0000000000 --- a/test/WebSites/RoutingWebSite/TestResponseGenerator.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace RoutingWebSite -{ - // Generates a response based on the expected URL and action context - public class TestResponseGenerator - { - private readonly ActionContext _actionContext; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory) - { - _urlHelperFactory = urlHelperFactory; - - _actionContext = contextAccessor.ActionContext; - if (_actionContext == null) - { - throw new InvalidOperationException("ActionContext should not be null here."); - } - } - - public JsonResult Generate(params string[] expectedUrls) - { - var link = (string)null; - var query = _actionContext.HttpContext.Request.Query; - if (query.ContainsKey("link")) - { - var values = query - .Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller") - .ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]); - - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext); - link = urlHelper.Action(query["link_action"], query["link_controller"], values); - } - - var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo; - - return new JsonResult(new - { - expectedUrls = expectedUrls, - actualUrl = _actionContext.HttpContext.Request.Path.Value, - routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name, - routeValues = new Dictionary(_actionContext.RouteData.Values), - - action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName, - controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName, - - link, - }); - } - } -} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/VersioningWebSite.csproj b/test/WebSites/VersioningWebSite/VersioningWebSite.csproj index cfda2f9c35..28f147e981 100644 --- a/test/WebSites/VersioningWebSite/VersioningWebSite.csproj +++ b/test/WebSites/VersioningWebSite/VersioningWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + +