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. 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.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); 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..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; @@ -16,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.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() { @@ -61,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/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..a3d236d318 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(); @@ -251,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) { @@ -318,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"; @@ -392,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) { @@ -1209,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); 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