From 66cac69adfb48b01b729033c3e3680b3c99f605e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 11 Jul 2018 18:49:30 -0700 Subject: [PATCH 1/7] Updating dependencies to 2.1.2 and adding a section for pinned variable versions --- build/dependencies.props | 15 +++++++++++---- korebuild-lock.txt | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index bb7c16b4b6..3f8a481862 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -2,10 +2,12 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - + + + 0.9.9 0.10.13 - 2.1.1-rtm-15793 + 2.1.3-rtm-15802 2.1.1 2.1.1 2.1.1 @@ -36,7 +38,7 @@ 2.1.1 2.1.1 2.1.1 - 2.1.1 + 2.1.2 2.1.1 2.1.1 2.1.1 @@ -77,7 +79,7 @@ 2.1.1 2.1.1 2.0.0 - 2.1.1 + 2.1.2 2.1.1 2.1.1 15.6.1 @@ -91,5 +93,10 @@ 2.3.1 2.4.0-beta.1.build3945 + + + + + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index bc84e0cd53..251c227c83 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.1-rtm-15793 -commithash:988313f4b064d6c69fc6f7b845b6384a6af3447a +version:2.1.3-rtm-15802 +commithash:a7c08b45b440a7d2058a0aa1eaa3eb6ba811976a From d46948da1d4709bcb8ae0094074a307ece636b4d Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 12 Jul 2018 11:56:13 -0700 Subject: [PATCH 2/7] Pin version variables to the ASP.NET Core 2.1.2 baseline This reverts our previous policy of cascading versions on all servicing updates. This moves variables into the 'pinned' section, and points them to the latest stable release (versions that were used at the time of the 2.1.2 release). --- build/dependencies.props | 51 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3f8a481862..3e21512352 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,10 +4,35 @@ - + 0.9.9 0.10.13 2.1.3-rtm-15802 + 5.2.6 + 2.8.0 + 2.8.0 + 1.7.0 + 2.1.0 + 2.0.0 + 2.1.2 + 2.1.1 + 15.6.1 + 4.7.49 + 2.0.3 + 1.0.1 + 4.5.0 + 4.5.0 + 4.5.1 + 0.8.0 + 2.3.1 + 2.4.0-beta.1.build3945 + + + + + + + 2.1.1 2.1.1 2.1.1 @@ -44,18 +69,13 @@ 2.1.1 2.1.0 2.1.1 - 5.2.6 - 2.8.0 - 2.8.0 2.1.1 - 1.7.0 2.1.1 2.1.1 2.1.1 2.1.1 2.1.1 2.1.1 - 2.1.0 2.1.0 2.1.1 2.1.1 @@ -78,25 +98,6 @@ 2.1.1 2.1.1 2.1.1 - 2.0.0 - 2.1.2 2.1.1 - 2.1.1 - 15.6.1 - 4.7.49 - 2.0.3 - 1.0.1 - 4.5.0 - 4.5.0 - 4.5.1 - 0.8.0 - 2.3.1 - 2.4.0-beta.1.build3945 - - - - - - From f850e31bfbfc03bba32df296101192e33cf4b4c0 Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Tue, 24 Jul 2018 10:54:26 -0700 Subject: [PATCH 3/7] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64ff041d5c..eac4268e4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing ====== -Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. +Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. From 630aeade07b101e7e45b3140868c157beb2d74ec Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 23 Jul 2018 14:01:11 -0700 Subject: [PATCH 4/7] Added tests related to generating urls with route name --- .../Routing/GlobalRoutingUrlHelperTest.cs | 182 ++++++++++++++++-- .../Routing/UrlHelperTest.cs | 5 + .../Routing/UrlHelperTestBase.cs | 31 ++- 3 files changed, 201 insertions(+), 17 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs index 78c7551c43..b207c8dda4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs @@ -16,6 +16,83 @@ namespace Microsoft.AspNetCore.Mvc.Routing { public class GlobalRoutingUrlHelperTest : UrlHelperTestBase { + [Fact] + public void RouteUrl_WithRouteName_GeneratesUrl_UsingDefaults() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Equal("/" + endpoint2.RoutePattern.RawText, url); + } + + [Fact] + public void RouteUrl_WithRouteName_UsesAmbientValues() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + urlHelper.ActionContext.RouteData.Values["id"] = "500"; + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Equal("/api/orders/500", url); + } + + [Fact] + public void RouteUrl_WithRouteName_UsesSuppliedValue_OverridingAmbientValue() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + urlHelper.ActionContext.RouteData.Values["id"] = "500"; + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { id = "10" }); + + // Assert + Assert.Equal("/api/orders/10", url); + } + protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); @@ -84,6 +161,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing return CreateUrlHelper(actionContext); } + private IUrlHelper CreateUrlHelper(IEnumerable endpoints, ActionContext actionContext = null) + { + var serviceProvider = CreateServices(endpoints); + var httpContext = CreateHttpContext(serviceProvider, null, null, "http"); + actionContext = actionContext ?? CreateActionContext(httpContext); + return CreateUrlHelper(actionContext); + } + private IUrlHelper CreateUrlHelper( IEnumerable endpoints, string appRoot, @@ -99,33 +184,98 @@ namespace Microsoft.AspNetCore.Mvc.Routing private List GetDefaultEndpoints() { var endpoints = new List(); - endpoints.Add(CreateEndpoint(null, "home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 1)); - endpoints.Add(CreateEndpoint(null, "home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 2)); - endpoints.Add(CreateEndpoint(null, "home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3)); - endpoints.Add(CreateEndpoint(null, "home2/contact/{id}", new { id = "defaultid", controller = "home2", action = "contact" }, 4)); - endpoints.Add(CreateEndpoint(null, "home3/contact/{id}", new { id = "defaultid", controller = "home3", action = "contact" }, 5)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 6)); - endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 8)); - endpoints.Add(CreateEndpoint("MyRouteName", "any/url", new { }, 9)); + endpoints.Add( + CreateEndpoint( + "home/newaction/{id}", + defaults: new { id = "defaultid", controller = "home", action = "newaction" }, + requiredValues: new { controller = "home", action = "newaction" }, + order: 1)); + endpoints.Add( + CreateEndpoint( + "home/contact/{id}", + defaults: new { id = "defaultid", controller = "home", action = "contact" }, + requiredValues: new { controller = "home", action = "contact" }, + order: 2)); + endpoints.Add( + CreateEndpoint( + "home2/newaction/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "newaction" }, + requiredValues: new { controller = "home2", action = "newaction" }, + order: 3)); + endpoints.Add( + CreateEndpoint( + "home2/contact/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "contact" }, + requiredValues: new { controller = "home2", action = "contact" }, + order: 4)); + endpoints.Add( + CreateEndpoint( + "home3/contact/{id}", + defaults: new { id = "defaultid", controller = "home3", action = "contact" }, + requiredValues: new { controller = "home3", action = "contact" }, + order: 5)); + endpoints.Add( + CreateEndpoint( + "named/home/newaction/{id}", + defaults: new { id = "defaultid", controller = "home", action = "newaction" }, + requiredValues: new { controller = "home", action = "newaction" }, + order: 6, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "named/home2/newaction/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "newaction" }, + requiredValues: new { controller = "home2", action = "newaction" }, + order: 7, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "named/home/contact/{id}", + defaults: new { id = "defaultid", controller = "home", action = "contact" }, + requiredValues: new { controller = "home", action = "contact" }, + order: 8, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "any/url", + defaults: new { }, + requiredValues: new { }, + order: 9, + routeName: "MyRouteName")); + endpoints.Add( + CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + order: 10, + routeName: "OrdersApi")); return endpoints; } - private MatcherEndpoint CreateEndpoint(string routeName, string template, object defaults, int order) + private MatcherEndpoint CreateEndpoint( + string template, + object defaults = null, + object requiredValues = null, + int order = 0, + string routeName = null, + EndpointMetadataCollection metadataCollection = null) { - var metadata = EndpointMetadataCollection.Empty; - if (!string.IsNullOrEmpty(routeName)) + if (metadataCollection == null) { - metadata = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + metadataCollection = EndpointMetadataCollection.Empty; + if (!string.IsNullOrEmpty(routeName)) + { + metadataCollection = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + } } return new MatcherEndpoint( next => (httpContext) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(), + new RouteValueDictionary(requiredValues), order, - metadata, - "DisplayName"); + metadataCollection, + null); } private IServiceProvider CreateServices(IEnumerable endpoints) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs index 63c88896ab..2d7e4a0f15 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs @@ -101,6 +101,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing .Returns(context => null); routeBuilder.DefaultHandler = target.Object; + routeBuilder.MapRoute( + "OrdersApi", + "api/orders/{id}", + new RouteValueDictionary(new { controller = "Orders", action = "GetById" })); + routeBuilder.MapRoute( string.Empty, "{controller}/{action}/{id}", diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs index 300efe8c4a..d6dfe08822 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs @@ -457,6 +457,36 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("https://pingüino/app/named/home2/newaction/someid", url); } + [Fact] + public void RouteUrl_GeneratesUrl_WithRouteName_UsingDefaultValues_WhenExplicitOrAmbientValues_NotPresent() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { id = "500" }); + + // Assert + Assert.Equal("/app/api/orders/500", url); + } + + [Fact] + public void RouteUrl_WithRouteName_DoesNotGenerateUrl_WhenRequiredValueForParameter_NotPresent() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Null(url); + } + [Fact] public void RouteUrlWithRouteNameAndDictionary() { @@ -922,7 +952,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("/b/Store/Checkout", url); } - protected abstract IServiceProvider CreateServices(); protected abstract IUrlHelper CreateUrlHelper(ActionContext actionContext); From c08504b08ab404e5c58699d94fc092e9e2e7f87a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 25 Jul 2018 14:30:51 +1200 Subject: [PATCH 5/7] MVC startup experience (#8131) --- .../MvcApplicationBuilderExtensions.cs | 92 +++++++++++++++++-- .../Builder/MvcEndpointInfo.cs | 9 +- ...MvcOptionsConfigureCompatibilityOptions.cs | 5 + .../Internal/MvcEndpointDataSource.cs | 15 +-- .../Internal/NullRouter.cs | 27 ++++++ .../MvcOptions.cs | 10 ++ .../ConsumesAttributeGlobalRoutingTests.cs | 20 ++++ .../ConsumesAttributeTests.cs | 20 ++++ .../ConsumesAttributeTestsBase.cs | 3 + .../GlobalRoutingTest.cs | 15 +++ .../RequestServicesGlobalRoutingTest.cs | 20 ++++ .../RequestServicesTest.cs | 20 ++++ .../RequestServicesTestBase.cs | 3 + .../RoutingTests.cs | 15 +++ .../RoutingTestsBase.cs | 3 + .../VersioningGlobalRoutingTests.cs | 20 ++++ .../VersioningTests.cs | 20 ++++ .../VersioningTestsBase.cs | 3 + .../CompatibilitySwitchIntegrationTest.cs | 28 ++++++ .../Controllers/RoutingController.cs | 17 ++++ test/WebSites/BasicWebSite/Startup.cs | 3 + .../BasicWebSite/StartupWithGlobalRouting.cs | 6 +- .../Controllers/RoutingController.cs | 17 ++++ .../StartupWithGlobalRouting.cs | 15 ++- .../Controllers/RoutingController.cs | 17 ++++ .../StartupWithGlobalRouting.cs | 14 +-- 26 files changed, 391 insertions(+), 46 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs create mode 100644 test/WebSites/BasicWebSite/Controllers/RoutingController.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/RoutingController.cs create mode 100644 test/WebSites/VersioningWebSite/Controllers/RoutingController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 74e5f8e5ec..0b190b0231 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { @@ -16,6 +18,9 @@ namespace Microsoft.AspNetCore.Builder /// public static class MvcApplicationBuilderExtensions { + // Property key set in routing package by UseGlobalRouting to indicate middleware is registered + private const string GlobalRoutingRegisteredKey = "__GlobalRoutingMiddlewareRegistered"; + /// /// Adds MVC to the request execution pipeline. /// @@ -79,16 +84,91 @@ namespace Microsoft.AspNetCore.Builder VerifyMvcIsRegistered(app); - var routes = new RouteBuilder(app) + var options = app.ApplicationServices.GetRequiredService>(); + + if (options.Value.EnableGlobalRouting) { - DefaultHandler = app.ApplicationServices.GetRequiredService(), - }; + var mvcEndpointDataSource = app.ApplicationServices + .GetRequiredService>() + .OfType() + .First(); + var constraintResolver = app.ApplicationServices + .GetRequiredService(); - configureRoutes(routes); + var endpointRouteBuilder = new EndpointRouteBuilder(app); - routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); + configureRoutes(endpointRouteBuilder); - return app.UseRouter(routes.Build()); + foreach (var router in endpointRouteBuilder.Routes) + { + // Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint + // Sub-types could have additional customization that we can't knowingly convert + if (router is Route route && router.GetType() == typeof(Route)) + { + var endpointInfo = new MvcEndpointInfo( + route.Name, + route.RouteTemplate, + route.Defaults, + route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value), + route.DataTokens, + constraintResolver); + + mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo); + } + else + { + throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Global Routing."); + } + } + + if (!app.Properties.TryGetValue(GlobalRoutingRegisteredKey, out _)) + { + // Matching middleware has not been registered yet + // For back-compat register middleware so an endpoint is matched and then immediately used + app.UseGlobalRouting(); + } + + return app.UseEndpoint(); + } + else + { + var routes = new RouteBuilder(app) + { + DefaultHandler = app.ApplicationServices.GetRequiredService(), + }; + + configureRoutes(routes); + + routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); + + return app.UseRouter(routes.Build()); + } + } + + private class EndpointRouteBuilder : IRouteBuilder + { + public EndpointRouteBuilder(IApplicationBuilder applicationBuilder) + { + ApplicationBuilder = applicationBuilder; + Routes = new List(); + DefaultHandler = NullRouter.Instance; + } + + public IApplicationBuilder ApplicationBuilder { get; } + + public IRouter DefaultHandler { get; set; } + + public IServiceProvider ServiceProvider + { + get { return ApplicationBuilder.ApplicationServices; } + } + + public IList Routes { get; } + + public IRouter Build() + { + throw new NotSupportedException(); + } } public static IApplicationBuilder UseMvcWithEndpoint( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index bb6a9df95f..ba59c34b44 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -94,10 +94,13 @@ namespace Microsoft.AspNetCore.Builder { if (parameter.DefaultValue != null) { - if (result.ContainsKey(parameter.Name)) + if (result.TryGetValue(parameter.Name, out var value)) { - throw new InvalidOperationException( - string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + if (!object.Equals(value, parameter.DefaultValue)) + { + throw new InvalidOperationException( + string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + } } else { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index 71957b40cf..28fd0605e7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -32,6 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true; } + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(MvcOptions.EnableGlobalRouting)] = true; + } + return values; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index f725da108b..d6bc8c8f68 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // // REVIEW: This is really ugly if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) - && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return false; @@ -260,19 +260,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal return false; } - private class DummyRouter : IRouter - { - public VirtualPathData GetVirtualPath(VirtualPathContext context) - { - return null; - } - - public Task RouteAsync(RouteContext context) - { - return Task.CompletedTask; - } - } - private MatcherEndpoint CreateEndpoint( ActionDescriptor action, string routeName, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs new file mode 100644 index 0000000000..c1b800c5bd --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs @@ -0,0 +1,27 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal class NullRouter : IRouter + { + public static IRouter Instance = new NullRouter(); + + private NullRouter() + { + } + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + return null; + } + + public Task RouteAsync(RouteContext context) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index 72634a49fa..e87ddb2678 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc private readonly CompatibilitySwitch _allowValidatingTopLevelNodes; private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; + private readonly CompatibilitySwitch _enableGlobalRouting; private readonly ICompatibilitySwitch[] _switches; /// @@ -54,6 +55,7 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes = new CompatibilitySwitch(nameof(AllowValidatingTopLevelNodes)); _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); + _enableGlobalRouting = new CompatibilitySwitch(nameof(EnableGlobalRouting)); _switches = new ICompatibilitySwitch[] { @@ -62,9 +64,17 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes, _inputFormatterExceptionPolicy, _suppressBindingUndefinedValueToEnumType, + _enableGlobalRouting, }; } + // REVIEW: Add documentation + public bool EnableGlobalRouting + { + get => _enableGlobalRouting.Value; + set => _enableGlobalRouting.Value = value; + } + /// /// Gets or sets the flag which decides whether body model binding (for example, on an /// action method parameter with ) should treat empty diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs index 889e77e80b..1683864c17 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class ConsumesAttributeGlobalRoutingTests : ConsumesAttributeTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs index a8db21d806..d694aad1c4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class ConsumesAttributeTests : ConsumesAttributeTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index 9a414488b1..acc68febbc 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -28,6 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Fact] public async Task NoRequestContentType_SelectsActionWithoutConstraint() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index 1ab9950d68..a95147250d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -16,6 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } + [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs index 0c1471df03..0ca18c250e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class RequestServicesGlobalRoutingTest : RequestServicesTestBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs index f62521bd6c..73c0e5af15 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class RequestServicesTest : RequestServicesTestBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs index 425bda5ea5..cc4e06ba74 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs @@ -26,6 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Theory] [InlineData("http://localhost/RequestScopedService/FromFilter")] [InlineData("http://localhost/RequestScopedService/FromView")] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index e4c3f088ea..a4fbb4ab58 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -17,6 +17,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } + [Fact] public async override Task RouteData_Routers_ConventionalRoute() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 20918254b9..f707655dce 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -28,6 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Fact] public abstract Task RouteData_Routers_ConventionalRoute(); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs index 8c022a076c..af2147949f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class VersioningGlobalRoutingTests : VersioningTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs index 8490fda117..40e14bdc57 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs @@ -1,6 +1,11 @@ // 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.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class VersioningTests : VersioningTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs index 46cfdbe4a8..152c08a799 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs @@ -25,6 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Theory] [InlineData("1")] [InlineData("2")] diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 998343d7bb..78f8ddb4ae 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(razorPagesOptions.AllowAreas); + Assert.False(mvcOptions.EnableGlobalRouting); } [Fact] @@ -63,6 +64,32 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); + Assert.False(mvcOptions.EnableGlobalRouting); + } + + [Fact] + public void CompatibilitySwitches_Version_2_2() + { + // Arrange + var serviceCollection = new ServiceCollection(); + AddHostingServices(serviceCollection); + serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + var services = serviceCollection.BuildServiceProvider(); + + // Act + var mvcOptions = services.GetRequiredService>().Value; + var jsonOptions = services.GetRequiredService>().Value; + var razorPagesOptions = services.GetRequiredService>().Value; + + // Assert + Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); + Assert.True(mvcOptions.AllowBindingHeaderValuesToNonStringModelTypes); + Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); + Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); + Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); + Assert.True(razorPagesOptions.AllowAreas); + Assert.True(mvcOptions.EnableGlobalRouting); } [Fact] @@ -87,6 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); + Assert.True(mvcOptions.EnableGlobalRouting); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..afe914f572 --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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 BasicWebSite +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 9e20cb447c..c6e406fb34 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -26,6 +26,9 @@ namespace BasicWebSite options.Conventions.Add(new ApplicationDescription("This is a basic website.")); // Filter that records a value in HttpContext.Items options.Filters.Add(new TraceResourceFilter()); + + // Remove when all URL generation tests are passing - https://github.com/aspnet/Routing/issues/590 + options.EnableGlobalRouting = false; }) .SetCompatibilityVersion(CompatibilityVersion.Latest) .AddXmlDataContractSerializerFormatters(); diff --git a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs index 037a5312f8..d3a540a86c 100644 --- a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs @@ -15,7 +15,7 @@ namespace BasicWebSite services.AddRouting(); services.AddMvc() - .SetCompatibilityVersion(CompatibilityVersion.Latest) + .SetCompatibilityVersion(CompatibilityVersion.Latest) // this compat version enables global routing .AddXmlDataContractSerializerFormatters(); services.ConfigureBaseWebSiteAuthPolicies(); @@ -31,9 +31,9 @@ namespace BasicWebSite app.UseGlobalRouting(); - app.UseMvcWithEndpoint(routes => + app.UseMvc(routes => { - routes.MapEndpoint( + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); diff --git a/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..354ff5539a --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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 +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs index abc0abf4fb..62a84f5ecb 100644 --- a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs @@ -12,9 +12,8 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddRouting(); - - services.AddMvc(); + services.AddMvc() + .AddMvcOptions(options => options.EnableGlobalRouting = true); services.AddScoped(); services.AddSingleton(); @@ -22,23 +21,21 @@ namespace RoutingWebSite public void Configure(IApplicationBuilder app) { - app.UseGlobalRouting(); - - app.UseMvcWithEndpoint(routes => + app.UseMvc(routes => { - routes.MapAreaEndpoint( + routes.MapAreaRoute( "flightRoute", "adminRoute", "{area:exists}/{controller}/{action}", new { controller = "Home", action = "Index" }, new { area = "Travel" }); - routes.MapEndpoint( + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); - routes.MapEndpoint( + routes.MapRoute( "RouteWithOptionalSegment", "{controller}/{action}/{path?}"); }); diff --git a/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..aad2099d1d --- /dev/null +++ b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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 VersioningWebSite +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs index c7ea9739f7..249aefabd4 100644 --- a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs @@ -13,10 +13,9 @@ namespace VersioningWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddRouting(); - // Add MVC services to the services container - services.AddMvc(); + services.AddMvc() + .AddMvcOptions(options => options.EnableGlobalRouting = true); services.AddScoped(); services.AddSingleton(); @@ -24,14 +23,7 @@ namespace VersioningWebSite public void Configure(IApplicationBuilder app) { - app.UseGlobalRouting(); - - app.UseMvcWithEndpoint(endpoints => - { - endpoints.MapEndpoint( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); - }); + app.UseMvcWithDefaultRoute(); } } } \ No newline at end of file From b71d5da05ee1c68ae43b081f1e49db16b2fc8496 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 24 Jul 2018 21:50:13 -0700 Subject: [PATCH 6/7] Fix test break due to 405 returned from routing --- build/dependencies.props | 144 +++++++++--------- .../GlobalRoutingTest.cs | 84 ++++++++++ .../RoutingTestsBase.cs | 8 +- 3 files changed, 160 insertions(+), 76 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 889ffe96a4..6b40e23716 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 2.2.0-preview1-17099 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 1.7.0 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 2.1.0 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 15.6.1 4.7.49 2.0.3 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index a95147250d..574b7f8160 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -3,6 +3,7 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -76,5 +77,88 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Array.Empty(), result.Routers); } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Fact] + public override async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Fact] + public override async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + { + // Arrange + var url = "http://localhost/api/v1/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + public override async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + string url, + string method) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Theory] + [InlineData("Post", "/Friends")] + [InlineData("Put", "/Friends")] + [InlineData("Patch", "/Friends")] + [InlineData("Options", "/Friends")] + [InlineData("Head", "/Friends")] + public override async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // These verbs don't match + [Theory] + [InlineData("/Bank/Deposit", "GET")] + [InlineData("/Bank/Deposit/5", "DELETE")] + [InlineData("/Bank/Withdraw/5", "GET")] + public override async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } } } \ 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 f707655dce..a3d236d318 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("Patch", "/Friends")] [InlineData("Options", "/Friends")] [InlineData("Head", "/Friends")] - public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + public virtual async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( string method, string url) { @@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() { // Arrange var url = "http://localhost/api/v1/Maps"; @@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( string url, string method) { @@ -1212,7 +1212,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("/Bank/Deposit", "GET")] [InlineData("/Bank/Deposit/5", "DELETE")] [InlineData("/Bank/Withdraw/5", "GET")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + public virtual async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) { // Arrange var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); From bcd6e83591aaf9613ae990b3382a46c9005db5c0 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 25 Jul 2018 06:15:17 -0700 Subject: [PATCH 7/7] Upgraded dependencies.props --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 6b40e23716..e24177e98d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 2.2.0-preview1-17099 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 1.7.0 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 2.1.0 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 15.6.1 4.7.49 2.0.3