diff --git a/Mvc.sln b/Mvc.sln index eef6a57f11..f03885947a 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -170,8 +170,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatchingWebSite", "test\WebSites\DispatchingWebSite\DispatchingWebSite.csproj", "{ABB3737F-E518-4E40-8A9C-F3281D610E8F}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -896,18 +894,6 @@ Global {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.ActiveCfg = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -977,7 +963,6 @@ Global {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {ABB3737F-E518-4E40-8A9C-F3281D610E8F} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs index 99d7472b60..2d5dcb2749 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -1,285 +1,68 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class DispatchingTests : IClassFixture> + public class DispatchingTests : RoutingTestsBase { - public DispatchingTests(MvcTestFixture fixture) + public DispatchingTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/Index", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() + public override Task ConventionalRoutedController_ActionIsReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_NonActionIsNotReachable() + public override Task ConventionalRoutedController_ActionIsReachable_WithDefaults() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_InArea_ActionIsReachable() + public override Task ConventionalRoutedController_NonActionIsNotReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "area", "Travel" }, - { "controller", "Flight" }, - { "action", "Index" }, - }, - result.RouteValues); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + public override Task ConventionalRoutedController_InArea_ActionIsReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + return Task.CompletedTask; + } - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + [Fact(Skip = "Conventional routing WIP")] + public override Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + return Task.CompletedTask; } [Theory(Skip = "Conventional routing WIP")] [InlineData("", "/Home/OptionalPath/default")] [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] - public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) + public override Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("ListProducts", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Store"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "ListProducts"), - result.RouteValues); - } - - [Theory] - [InlineData("Get", "/Friends")] - [InlineData("Get", "/Friends/Peter")] - [InlineData("Delete", "/Friends")] - public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_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.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(url, result.ExpectedUrls); - Assert.Equal("Friends", result.Controller); - Assert.Equal(method, result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Friends"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", method), - result.RouteValues); - - if (result.RouteValues.ContainsKey("id")) - { - Assert.Contains( - new KeyValuePair("id", "Peter"), - result.RouteValues); - } - } - - [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] - public 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.NotFound, response.StatusCode); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("http://localhost/api/v1/Maps")] [InlineData("http://localhost/api/v2/Maps")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) + public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) { - // Arrange & Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v1/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() + public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() { - // Arrange - var url = "http://localhost/api/v2/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Post", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public 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.NotFound, response.StatusCode); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] @@ -287,269 +70,19 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("http://localhost/api/v2/Maps/5", "PUT")] [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( + public override Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( string url, string method) { - // Arrange & Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Update", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps/PartialUpdate/5", - "/api/v2/Maps/PartialUpdate/5" - }, - result.ExpectedUrls); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("http://localhost/Banks/Get/5")] [InlineData("http://localhost/Bank/Get/5")] - public async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) + public override Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Banks", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/Bank/Get/5", - "/Bank/Get/5" - }, - result.ExpectedUrls); - } - - [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 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.NotFound, response.StatusCode); - } - - // The url would be /Store/ListProducts with conventional routes - [Fact] - public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/ListProducts"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // There's two actions at this URL - but attribute routes go in the route table - // first. - [Fact] - public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/About"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/About", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("About", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); - Assert.Equal("Blog", result.Controller); - Assert.Equal("Edit", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Blog"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "Edit"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("postId", "5"), - result.RouteValues); - } - - // There's no [HttpGet] on the action here. - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Employee"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - } - - // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller - // and a test that verifies that if you define another action with a specific verb we'll route to that - // more specific action. - [Theory] - [InlineData("PUT")] - [InlineData("POST")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("Orders", result.Action); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5/Salary", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("Salary", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("GetOrders", result.Action); - } - - // There's no [HttpGet] on the action here. - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateEmployee", result.Action); - } - - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/Manager", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateManager", result.Action); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] @@ -557,703 +90,145 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("PATCH", "Bank")] [InlineData("PUT", "Bank/Update")] [InlineData("PATCH", "Bank/Update")] - public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) + public override Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) { - // Arrange - var expectedUrl = "/Bank/Update"; - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/" + path); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal("UpdateBank", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("MergeEmployee", result.Action); - } - - // There's an [HttpGet] with its own template on the action here. - [Theory] - [InlineData("GET", "GetAdministrator")] - [InlineData("DELETE", "DeleteAdministrator")] - public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5/Administrator", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal(action, result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Manager/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Manager/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("GetManager", result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Team/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Team/5", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganization", result.Action); - - Assert.Contains( - new KeyValuePair("teamId", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Teams"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Teams", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganizations", result.Action); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() + public override Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Organization/5"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Club/5", response); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() + public override Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Teams/AllOrganizations", response); - } - - [Theory] - [InlineData("", "/TeamName/DefaultName")] - [InlineData("CustomName", "/TeamName/CustomName")] - public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) - { - // Arrange & Act - var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); - - // Assert - Assert.NotNull(body); - var result = JsonConvert.DeserializeObject(body); - Assert.Single(result.ExpectedUrls, expected); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToSelf() + public override Task AttributeRoutedAction_LinkToSelf() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithAmbientController() + public override Task AttributeRoutedAction_LinkWithAmbientController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee/5", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToAttributeRoutedController() + public override Task AttributeRoutedAction_LinkToAttributeRoutedController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/Blog/ShowPosts", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToConventionalController() + public override Task AttributeRoutedAction_LinkToConventionalController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("GET", "Get")] [InlineData("PUT", "Put")] - public async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( + public override Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( string method, string actionName) { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal(actionName, result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("Company", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + public override Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() { - // Arrange & Act - var response = await Client.DeleteAsync("http://localhost/api/Company/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("Delete", result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("RemoveCompany", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() + public override Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() { - // Arrange - var url = LinkFrom("http://localhost") - .To(new { id = 5 }); - - // Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetEmployees", result.Action); - - Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); - Assert.Null(result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() + public override Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetDepartments", result.Action); - - Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); - Assert.Equal("Departments", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_LinkToArea() + public override Task ConventionalRoutedAction_LinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() + public override Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_StaysInArea() + public override Task ConventionalRoutedAction_InArea_StaysInArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToArea() + public override Task AttributeRoutedAction_LinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee") - .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() + public override Task AttributeRoutedAction_InArea_ImplicitLinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() + public override Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Index", controller = "Flight", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() + public override Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Rail", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() + public override Task ConventionalRoutedAction_InArea_LinkToAnotherArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_LinkToAnotherArea() + public override Task AttributeRoutedAction_InArea_LinkToAnotherArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Theory] - [InlineData("/Bank/Deposit", "PUT", "Deposit")] - [InlineData("/Bank/Deposit", "POST", "Deposit")] - [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] - [InlineData("/Bank/Deposit/5", "POST", "Deposit")] - [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal(actionName, result.Action); - } - - // These verbs don't match - [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] - public 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.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("/Order/Add/1", "GET", "Add")] - [InlineData("/Order/Add", "POST", "Add")] - [InlineData("/Order/Edit/1", "PUT", "Edit")] - [InlineData("/Order/GetOrder", "GET", "GetOrder")] - public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Order", result.Controller); - Assert.Equal(actionName, result.Action); - } - - private static LinkBuilder LinkFrom(string url) - { - return new LinkBuilder(url); - } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - private class RoutingResult - { - public string[] ExpectedUrls { get; set; } - - public string ActualUrl { get; set; } - - public Dictionary RouteValues { get; set; } - - public string RouteName { get; set; } - - public string Action { get; set; } - - public string Controller { get; set; } - - public string Link { get; set; } - } - - private class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary(); - Values.Add("link", string.Empty); - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } + return Task.CompletedTask; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 917d2938cb..b47eec39bf 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -31,7 +31,6 @@ - diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index e2917d8f1e..3a3e6527dd 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -1,1259 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class RoutingTests : IClassFixture> + public class RoutingTests : RoutingTestsBase { public RoutingTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact] - public async Task ConventionalRoutedController_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/Index", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_NonActionIsNotReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task ConventionalRoutedController_InArea_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "area", "Travel" }, - { "controller", "Flight" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("", "/Home/OptionalPath/default")] - [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] - public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("ListProducts", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Store"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "ListProducts"), - result.RouteValues); - } - - [Theory] - [InlineData("Get", "/Friends")] - [InlineData("Get", "/Friends/Peter")] - [InlineData("Delete", "/Friends")] - public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_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.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(url, result.ExpectedUrls); - Assert.Equal("Friends", result.Controller); - Assert.Equal(method, result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Friends"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", method), - result.RouteValues); - - if (result.RouteValues.ContainsKey("id")) - { - Assert.Contains( - new KeyValuePair("id", "Peter"), - result.RouteValues); - } - } - - [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] - public 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.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps")] - [InlineData("http://localhost/api/v2/Maps")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) - { - // Arrange & Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v1/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() - { - // Arrange - var url = "http://localhost/api/v2/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Post", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public 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.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/5", "PUT")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( - string url, - string method) - { - // Arrange & Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Update", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps/PartialUpdate/5", - "/api/v2/Maps/PartialUpdate/5" - }, - result.ExpectedUrls); - } - - [Theory] - [InlineData("http://localhost/Banks/Get/5")] - [InlineData("http://localhost/Bank/Get/5")] - public async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) - { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Banks", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/Bank/Get/5", - "/Bank/Get/5" - }, - result.ExpectedUrls); - } - - [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 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.NotFound, response.StatusCode); - } - - // The url would be /Store/ListProducts with conventional routes - [Fact] - public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/ListProducts"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // There's two actions at this URL - but attribute routes go in the route table - // first. - [Fact] - public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/About"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/About", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("About", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); - Assert.Equal("Blog", result.Controller); - Assert.Equal("Edit", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Blog"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "Edit"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("postId", "5"), - result.RouteValues); - } - - // There's no [HttpGet] on the action here. - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Employee"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - } - - // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller - // and a test that verifies that if you define another action with a specific verb we'll route to that - // more specific action. - [Theory] - [InlineData("PUT")] - [InlineData("POST")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("Orders", result.Action); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5/Salary", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("Salary", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("GetOrders", result.Action); - } - - // There's no [HttpGet] on the action here. - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateEmployee", result.Action); - } - - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/Manager", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateManager", result.Action); - } - - [Theory] - [InlineData("PUT", "Bank")] - [InlineData("PATCH", "Bank")] - [InlineData("PUT", "Bank/Update")] - [InlineData("PATCH", "Bank/Update")] - public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) - { - // Arrange - var expectedUrl = "/Bank/Update"; - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/" + path); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal("UpdateBank", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("MergeEmployee", result.Action); - } - - // There's an [HttpGet] with its own template on the action here. - [Theory] - [InlineData("GET", "GetAdministrator")] - [InlineData("DELETE", "DeleteAdministrator")] - public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee/5/Administrator", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal(action, result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Manager/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Manager/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("GetManager", result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Team/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Team/5", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganization", result.Action); - - Assert.Contains( - new KeyValuePair("teamId", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Teams"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Teams", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganizations", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Organization/5"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Club/5", response); - } - - [Fact] - public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Teams/AllOrganizations", response); - } - - [Theory] - [InlineData("", "/TeamName/DefaultName")] - [InlineData("CustomName", "/TeamName/CustomName")] - public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) - { - // Arrange & Act - var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); - - // Assert - Assert.NotNull(body); - var result = JsonConvert.DeserializeObject(body); - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToSelf() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithAmbientController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee/5", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToAttributeRoutedController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/Blog/ShowPosts", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToConventionalController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/", result.Link); - } - - [Theory] - [InlineData("GET", "Get")] - [InlineData("PUT", "Put")] - public async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( - string method, - string actionName) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal(actionName, result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("Company", result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() - { - // Arrange & Act - var response = await Client.DeleteAsync("http://localhost/api/Company/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("Delete", result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("RemoveCompany", result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() - { - // Arrange - var url = LinkFrom("http://localhost") - .To(new { id = 5 }); - - // Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetEmployees", result.Action); - - Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); - Assert.Null(result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetDepartments", result.Action); - - Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); - Assert.Equal("Departments", result.RouteName); - } - - [Fact] - public async Task ConventionalRoutedAction_LinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_StaysInArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee") - .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Index", controller = "Flight", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Rail", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_LinkToAnotherArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Theory] - [InlineData("/Bank/Deposit", "PUT", "Deposit")] - [InlineData("/Bank/Deposit", "POST", "Deposit")] - [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] - [InlineData("/Bank/Deposit/5", "POST", "Deposit")] - [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal(actionName, result.Action); - } - - // These verbs don't match - [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] - public 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.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("/Order/Add/1", "GET", "Add")] - [InlineData("/Order/Add", "POST", "Add")] - [InlineData("/Order/Edit/1", "PUT", "Edit")] - [InlineData("/Order/GetOrder", "GET", "GetOrder")] - public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Order", result.Controller); - Assert.Equal(actionName, result.Action); - } - - private static LinkBuilder LinkFrom(string url) - { - return new LinkBuilder(url); - } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - private class RoutingResult - { - public string[] ExpectedUrls { get; set; } - - public string ActualUrl { get; set; } - - public Dictionary RouteValues { get; set; } - - public string RouteName { get; set; } - - public string Action { get; set; } - - public string Controller { get; set; } - - public string Link { get; set; } - } - - private class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary(); - Values.Add("link", string.Empty); - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs new file mode 100644 index 0000000000..a5ea33a0f1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -0,0 +1,1259 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class RoutingTestsBase : IClassFixture> where TStartup : class + { + protected RoutingTestsBase(MvcTestFixture fixture) + { + Client = fixture.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact] + public virtual async Task ConventionalRoutedController_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/Index", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_NonActionIsNotReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public virtual async Task ConventionalRoutedController_InArea_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "area", "Travel" }, + { "controller", "Flight" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("", "/Home/OptionalPath/default")] + [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] + public virtual async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact] + public async Task AttributeRoutedAction_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("ListProducts", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Store"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "ListProducts"), + result.RouteValues); + } + + [Theory] + [InlineData("Get", "/Friends")] + [InlineData("Get", "/Friends/Peter")] + [InlineData("Delete", "/Friends")] + public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_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.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(url, result.ExpectedUrls); + Assert.Equal("Friends", result.Controller); + Assert.Equal(method, result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Friends"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", method), + result.RouteValues); + + if (result.RouteValues.ContainsKey("id")) + { + Assert.Contains( + new KeyValuePair("id", "Peter"), + result.RouteValues); + } + } + + [Theory] + [InlineData("Post", "/Friends")] + [InlineData("Put", "/Friends")] + [InlineData("Patch", "/Friends")] + [InlineData("Options", "/Friends")] + [InlineData("Head", "/Friends")] + public 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.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps")] + [InlineData("http://localhost/api/v2/Maps")] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) + { + // Arrange & Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v1/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() + { + // Arrange + var url = "http://localhost/api/v2/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Post", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact] + public 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.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/5", "PUT")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( + string url, + string method) + { + // Arrange & Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Update", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps/PartialUpdate/5", + "/api/v2/Maps/PartialUpdate/5" + }, + result.ExpectedUrls); + } + + [Theory] + [InlineData("http://localhost/Banks/Get/5")] + [InlineData("http://localhost/Bank/Get/5")] + public virtual async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Banks", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/Bank/Get/5", + "/Bank/Get/5" + }, + result.ExpectedUrls); + } + + [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 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.NotFound, response.StatusCode); + } + + // The url would be /Store/ListProducts with conventional routes + [Fact] + public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/ListProducts"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + // There's two actions at this URL - but attribute routes go in the route table + // first. + [Fact] + public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/About"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/About", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("About", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); + Assert.Equal("Blog", result.Controller); + Assert.Equal("Edit", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Blog"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "Edit"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("postId", "5"), + result.RouteValues); + } + + // There's no [HttpGet] on the action here. + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Employee"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + } + + // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller + // and a test that verifies that if you define another action with a specific verb we'll route to that + // more specific action. + [Theory] + [InlineData("PUT")] + [InlineData("POST")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("Orders", result.Action); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee/5/Salary", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("Salary", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("GetOrders", result.Action); + } + + // There's no [HttpGet] on the action here. + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateEmployee", result.Action); + } + + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee/Manager", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateManager", result.Action); + } + + [Theory] + [InlineData("PUT", "Bank")] + [InlineData("PATCH", "Bank")] + [InlineData("PUT", "Bank/Update")] + [InlineData("PATCH", "Bank/Update")] + public virtual async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) + { + // Arrange + var expectedUrl = "/Bank/Update"; + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/" + path); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal("UpdateBank", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/5"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("MergeEmployee", result.Action); + } + + // There's an [HttpGet] with its own template on the action here. + [Theory] + [InlineData("GET", "GetAdministrator")] + [InlineData("DELETE", "DeleteAdministrator")] + public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee/5/Administrator", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal(action, result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Manager/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Manager/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("GetManager", result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Team/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Team/5", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganization", result.Action); + + Assert.Contains( + new KeyValuePair("teamId", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Teams"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Teams", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganizations", result.Action); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Organization/5"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Club/5", response); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Teams/AllOrganizations", response); + } + + [Theory] + [InlineData("", "/TeamName/DefaultName")] + [InlineData("CustomName", "/TeamName/CustomName")] + public virtual async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) + { + // Arrange & Act + var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); + + // Assert + Assert.NotNull(body); + var result = JsonConvert.DeserializeObject(body); + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToSelf() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithAmbientController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee/5", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToAttributeRoutedController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/Blog/ShowPosts", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToConventionalController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/", result.Link); + } + + [Theory] + [InlineData("GET", "Get")] + [InlineData("PUT", "Put")] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( + string method, + string actionName) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/5"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal(actionName, result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("Company", result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + { + // Arrange & Act + var response = await Client.DeleteAsync("http://localhost/api/Company/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("Delete", result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("RemoveCompany", result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() + { + // Arrange + var url = LinkFrom("http://localhost") + .To(new { id = 5 }); + + // Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetEmployees", result.Action); + + Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); + Assert.Null(result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetDepartments", result.Action); + + Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); + Assert.Equal("Departments", result.RouteName); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/") + .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_StaysInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee") + .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Index", controller = "Flight", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Rail", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Theory] + [InlineData("/Bank/Deposit", "PUT", "Deposit")] + [InlineData("/Bank/Deposit", "POST", "Deposit")] + [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] + [InlineData("/Bank/Deposit/5", "POST", "Deposit")] + [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] + public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal(actionName, result.Action); + } + + // These verbs don't match + [Theory] + [InlineData("/Bank/Deposit", "GET")] + [InlineData("/Bank/Deposit/5", "DELETE")] + [InlineData("/Bank/Withdraw/5", "GET")] + public 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.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("/Order/Add/1", "GET", "Add")] + [InlineData("/Order/Add", "POST", "Add")] + [InlineData("/Order/Edit/1", "PUT", "Edit")] + [InlineData("/Order/GetOrder", "GET", "GetOrder")] + public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Order", result.Controller); + Assert.Equal(actionName, result.Action); + } + + private static LinkBuilder LinkFrom(string url) + { + return new LinkBuilder(url); + } + + // See TestResponseGenerator in RoutingWebSite for the code that generates this data. + private class RoutingResult + { + public string[] ExpectedUrls { get; set; } + + public string ActualUrl { get; set; } + + public Dictionary RouteValues { get; set; } + + public string RouteName { get; set; } + + public string Action { get; set; } + + public string Controller { get; set; } + + public string Link { get; set; } + } + + private class LinkBuilder + { + public LinkBuilder(string url) + { + Url = url; + + Values = new Dictionary(); + Values.Add("link", string.Empty); + } + + public string Url { get; set; } + + public Dictionary Values { get; set; } + + public LinkBuilder To(object values) + { + var dictionary = new RouteValueDictionary(values); + foreach (var kvp in dictionary) + { + Values.Add("link_" + kvp.Key, kvp.Value); + } + + return this; + } + + public override string ToString() + { + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); + } + + public static implicit operator string(LinkBuilder builder) + { + return builder.ToString(); + } + } + } +} diff --git a/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs b/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs deleted file mode 100644 index 9d58e35998..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Admin -{ - [Area("Admin")] - [Route("[area]/Users")] - public class UserManagementController : Controller - { - private readonly TestResponseGenerator _generator; - - public UserManagementController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("All")] - public IActionResult ListUsers() - { - return _generator.Generate("Admin/Users/All"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs b/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs deleted file mode 100644 index 76e83adf58..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Areas.Order -{ - [Area("Order")] - [Route("Order/[action]", Name = "[area]_[action]")] - public class OrderController : Controller - { - private readonly TestResponseGenerator _generator; - - public OrderController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public IActionResult GetOrder() - { - return _generator.Generate("/Order/GetOrder"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs deleted file mode 100644 index adaf6e46cc..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite -{ - // This controller is reachable via traditional routing. - [Area("Travel")] - public class FlightController - { - private readonly TestResponseGenerator _generator; - - public FlightController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/Travel/Flight", "/Travel/Flight/Index"); - } - - [HttpPost] - public IActionResult BuyTickets() - { - return _generator.Generate("/Travel/Flight/BuyTickets"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs deleted file mode 100644 index 51b20ea566..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Travel -{ - [Area("Travel")] - public class HomeController : Controller - { - private readonly TestResponseGenerator _generator; - - public HomeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/Travel", "/Travel/Home", "/Travel/Home/Index"); - } - - [HttpGet("ContosoCorp/AboutTravel")] - public IActionResult About() - { - return _generator.Generate(); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs deleted file mode 100644 index c12bcd8c6f..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite -{ - [Area("Travel")] - [Route("ContosoCorp/Trains")] - public class RailController - { - private readonly TestResponseGenerator _generator; - - public RailController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/ContosoCorp/Trains"); - } - - [HttpGet("CheckSchedule")] - public IActionResult Schedule() - { - return _generator.Generate("/ContosoCorp/Trains/Schedule"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs b/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs deleted file mode 100644 index 832fb4d8ed..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - public class BanksController : Controller - { - private readonly TestResponseGenerator _generator; - - public BanksController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("Banks/[action]/{id}")] - [HttpGet("Bank/[action]/{id}")] - public ActionResult Get(int id) - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [AcceptVerbs("PUT", Route = "Bank")] - [HttpPatch("Bank")] - [AcceptVerbs("PUT", Route = "Bank/Update")] - [HttpPatch("Bank/Update")] - public ActionResult UpdateBank() - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [AcceptVerbs("PUT", "POST")] - [Route("Bank/Deposit")] - [Route("Bank/Deposit/{amount}")] - public ActionResult Deposit() - { - return _generator.Generate("/Bank/Deposit", "/Bank/Deposit/5"); - } - - [HttpPost] - [Route("Bank/Withdraw/{id}")] - public ActionResult Withdraw(int id) - { - return _generator.Generate("/Bank/Withdraw/5"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs b/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs deleted file mode 100644 index b48a9c05b4..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - // This controller contains actions mapped with a single controller-level route. - [Route("Blog/[action]/{postId?}")] - public class BlogController - { - private readonly TestResponseGenerator _generator; - - public BlogController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult ShowPosts() - { - return _generator.Generate("/Blog/ShowPosts"); - } - - public IActionResult Edit(int postId) - { - return _generator.Generate("/Blog/Edit/" + postId); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs b/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs deleted file mode 100644 index cc45b934ef..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - // A controller can define a route for all of the actions - // in it and give it a name for link generation purposes. - [Route("api/Company/{id}", Name = "Company")] - public class CompanyController : Controller - { - private readonly TestResponseGenerator _generator; - - public CompanyController(TestResponseGenerator generator) - { - _generator = generator; - } - - // An action with the same template will inherit the name - // from the controller. - [HttpGet] - public ActionResult Get(int id) - { - return _generator.Generate(Url.RouteUrl("Company", new { id = id })); - } - - // Multiple actions can have the same named route as long - // as for a given Name, all the actions have the same template. - // That is, there can't be two link generation entries with same - // name and different templates. - [HttpPut] - public ActionResult Put(int id) - { - return _generator.Generate(Url.RouteUrl("Company", new { id = id })); - } - - // Two actions can have the same template and each of them can have - // a different route name. That is, a given template can have multiple - // names associated with it. - [HttpDelete(Name = "RemoveCompany")] - public ActionResult Delete(int id) - { - return _generator.Generate(Url.RouteUrl("RemoveCompany", new { id = id })); - } - - // An action that defines a non empty template doesn't inherit the name - // from the route on the controller . - [HttpGet("Employees")] - public ActionResult GetEmployees(int id) - { - return _generator.Generate(Url.RouteUrl(new { id = id })); - } - - // An action that defines a non empty template doesn't inherit the name - // from the controller but can perfectly define its own name. - [HttpGet("Departments", Name = "Departments")] - public ActionResult GetDepartments(int id) - { - return _generator.Generate(Url.RouteUrl("Departments", new { id = id })); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs b/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs deleted file mode 100644 index fd946d4752..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - // This controller combines routes on the controller with routes on actions in a REST + navigation property - // style. - [Route("api/Employee")] - public class EmployeeController : Controller - { - private readonly TestResponseGenerator _generator; - - public EmployeeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult List() - { - return _generator.Generate("/api/Employee"); - } - - [AcceptVerbs("PUT", "PATCH")] - public IActionResult UpdateEmployee() - { - return _generator.Generate("/api/Employee"); - } - - [AcceptVerbs("PUT", "PATCH", Route = "Manager")] - public IActionResult UpdateManager() - { - return _generator.Generate("/api/Employee/Manager"); - } - - [HttpMerge("{id}")] - public IActionResult MergeEmployee(int id) - { - return _generator.Generate("/api/Employee/" + id); - } - - [HttpGet("{id}")] - public IActionResult Get(int id) - { - return _generator.Generate("/api/Employee/" + id); - } - - [HttpGet("{id}/Administrator")] - public IActionResult GetAdministrator(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Administrator"); - } - - [HttpGet("~/Manager/{id}")] - public IActionResult GetManager(int id) - { - return _generator.Generate("/Manager/" + id); - } - - [HttpDelete("{id}/Administrator")] - public IActionResult DeleteAdministrator(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Administrator"); - } - - [Route("{id}/Salary")] - public IActionResult Salary(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Salary"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs b/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs deleted file mode 100644 index 25c32c8c3f..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - [Route("Friends")] - public class FriendsController : Controller - { - private readonly TestResponseGenerator _generator; - - public FriendsController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - [HttpGet("{id}")] - public IActionResult Get([FromRoute]string id) - { - return _generator.Generate(id == null ? "/Friends" : $"/Friends/{id}"); - } - - [HttpDelete] - public IActionResult Delete() - { - return _generator.Generate("/Friends"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs b/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs deleted file mode 100644 index 0b40b54c15..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - // This controller is reachable via traditional routing. - public class HomeController : Controller - { - private readonly TestResponseGenerator _generator; - - public HomeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/", "/Home", "/Home/Index"); - } - - public IActionResult About() - { - // There are no urls that reach this action - it's hidden by an attribute route. - return _generator.Generate(); - } - - public IActionResult Contact() - { - return _generator.Generate("/Home/Contact"); - } - - public IActionResult OptionalPath(string path = "default") - { - return _generator.Generate("/Home/OptionalPath/" + path); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs b/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs deleted file mode 100644 index 2b45d3de62..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - [Route("api/v1/Maps", Name = "v1", Order = 1)] - [Route("api/v2/Maps")] - public class MapsController : Controller - { - private readonly TestResponseGenerator _generator; - - public MapsController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public ActionResult Get() - { - // Multiple attribute routes with name and order. - // We will always generate v2 routes except when - // we explicitly use "v1" to generate a v1 route. - return _generator.Generate( - Url.Action(), - Url.RouteUrl("v1"), - Url.RouteUrl(new { })); - } - - [HttpPost("/api/v2/Maps")] - public ActionResult Post() - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [HttpPut("{id}")] - [HttpPatch("PartialUpdate/{id}")] - public ActionResult Update(int id) - { - // We will generate "/api/v2/Maps/PartialUpdate/{id}" - // in both cases, v1 routes will be discarded due to their - // Order and for v2 routes PartialUpdate has higher precedence. - // api/v1/Maps/{id} and api/v2/Maps/{id} will only match on PUT. - // api/v1/Maps/PartialUpdate/{id} and api/v2/Maps/PartialUpdate/{id} will only match on PATCH. - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs b/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs deleted file mode 100644 index 195d810af6..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - [Route("Order/[action]/{orderId?}", Name = "Order_[action]")] - public class OrderController : Controller - { - private readonly TestResponseGenerator _generator; - - public OrderController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public IActionResult Add(int orderId) - { - return _generator.Generate("/Order/Add/1"); - } - - [HttpPost] - public IActionResult Add() - { - return _generator.Generate("/Order/Add"); - } - - [HttpPut] - public IActionResult Edit(int orderId) - { - return _generator.Generate("/Order/Edit/1"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs b/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs deleted file mode 100644 index a6b7e679a5..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - // This controller contains only actions with individual attribute routes. - public class StoreController : Controller - { - private readonly TestResponseGenerator _generator; - - public StoreController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("Store/Shop/Products")] - public IActionResult ListProducts() - { - return _generator.Generate("/Store/Shop/Products"); - } - - // Intentionally designed to conflict with HomeController#About. - [HttpGet("Home/About")] - public IActionResult About() - { - return _generator.Generate("/Home/About"); - } - - [Route("Store/Shop/Orders")] - public IActionResult Orders() - { - return _generator.Generate("/Store/Shop/Orders"); - } - - [HttpGet("Store/Shop/Orders")] - public IActionResult GetOrders() - { - return _generator.Generate("/Store/Shop/Orders"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs b/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs deleted file mode 100644 index a5459b8e79..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace DispatchingWebSite.Controllers -{ - [Route("/Teams", Order = 1)] - public class TeamController : Controller - { - private readonly TestResponseGenerator _generator; - - public TeamController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("/Team/{teamId}", Order = 2)] - public ActionResult GetTeam(int teamId) - { - return _generator.Generate("/Team/" + teamId); - } - - [HttpGet("/Team/{teamId}")] - public ActionResult GetOrganization(int teamId) - { - return _generator.Generate("/Team/" + teamId); - } - - [HttpGet("")] - public ActionResult GetTeams() - { - return _generator.Generate("/Teams"); - } - - [HttpGet("", Order = 0)] - public ActionResult GetOrganizations() - { - return _generator.Generate("/Teams"); - } - - [HttpGet("/Club/{clubId?}")] - public ActionResult GetClub() - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("/Organization/{clubId?}", Order = 1)] - public ActionResult GetClub(int clubId) - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("AllTeams")] - public ActionResult GetAllTeams() - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("AllOrganizations", Order = 0)] - public ActionResult GetAllTeams(int notRelevant) - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("/TeamName/{*Name=DefaultName}/")] - public ActionResult GetTeam(string name) - { - return _generator.Generate("/TeamName/" + name); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj b/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj deleted file mode 100644 index df863f0a9c..0000000000 --- a/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - $(StandardTestWebsiteTfms) - - - - - - - - - - - diff --git a/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs b/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs deleted file mode 100644 index 19477e4889..0000000000 --- a/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace DispatchingWebSite -{ - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class HttpMergeAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider - { - private static readonly IEnumerable _supportedMethods = new[] { "MERGE" }; - - public HttpMergeAttribute(string template) - { - Template = template; - } - - public IEnumerable HttpMethods - { - get { return _supportedMethods; } - } - - /// - public string Template { get; private set; } - - /// - public int? Order { get; set; } - - /// - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs b/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs deleted file mode 100644 index 41f9455f66..0000000000 --- a/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace DispatchingWebSite -{ - // Generates a response based on the expected URL and action context - public class TestResponseGenerator - { - private readonly ActionContext _actionContext; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory) - { - _urlHelperFactory = urlHelperFactory; - - _actionContext = contextAccessor.ActionContext; - if (_actionContext == null) - { - throw new InvalidOperationException("ActionContext should not be null here."); - } - } - - public JsonResult Generate(params string[] expectedUrls) - { - var link = (string)null; - var query = _actionContext.HttpContext.Request.Query; - if (query.ContainsKey("link")) - { - var values = query - .Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller") - .ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]); - - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext); - link = urlHelper.Action(query["link_action"], query["link_controller"], values); - } - - var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo; - - return new JsonResult(new - { - expectedUrls = expectedUrls, - actualUrl = _actionContext.HttpContext.Request.Path.Value, - routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name, - routeValues = new Dictionary(_actionContext.RouteData.Values), - - action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName, - controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName, - - link, - }); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/readme.md b/test/WebSites/DispatchingWebSite/readme.md deleted file mode 100644 index 357e09a324..0000000000 --- a/test/WebSites/DispatchingWebSite/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -DispatchingWebSite -=== - -This web site illustrates how to use conventional and attribute dispatching. diff --git a/test/WebSites/RoutingWebSite/Program.cs b/test/WebSites/RoutingWebSite/Program.cs new file mode 100644 index 0000000000..83b2991269 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Program.cs @@ -0,0 +1,26 @@ +// 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.IO; +using Microsoft.AspNetCore.Hosting; + +namespace RoutingWebSite +{ + public class Program + { + public static void Main(string[] args) + { + var host = CreateWebHostBuilder(args) + .Build(); + + host.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseKestrel() + .UseIISIntegration(); + } +} diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 2a9f4482dd..c7b905bffb 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -42,21 +42,6 @@ namespace RoutingWebSite "{controller}/{action}/{path?}"); }); } - - public static void Main(string[] args) - { - var host = CreateWebHostBuilder(args) - .Build(); - - host.Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .UseKestrel() - .UseIISIntegration(); } } diff --git a/test/WebSites/DispatchingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs similarity index 67% rename from test/WebSites/DispatchingWebSite/Startup.cs rename to test/WebSites/RoutingWebSite/StartupWithDispatching.cs index f576ad29d3..75737e859f 100644 --- a/test/WebSites/DispatchingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs @@ -14,9 +14,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -namespace DispatchingWebSite +namespace RoutingWebSite { - public class Startup + public class StartupWithDispatching { // Set up application services public void ConfigureServices(IServiceCollection services) @@ -33,46 +33,26 @@ namespace DispatchingWebSite { app.UseDispatcher(); - //app.UseMvc(routes => + app.UseEndpoint(); + + //app.UseMvcWithEndpoint(routes => //{ - // routes.MapAreaRoute( + // routes.MapAreaEndpoint( // "flightRoute", // "adminRoute", // "{area:exists}/{controller}/{action}", // new { controller = "Home", action = "Index" }, // new { area = "Travel" }); - // routes.MapRoute( + // routes.MapEndpoint( // "ActionAsMethod", // "{controller}/{action}", // defaults: new { controller = "Home", action = "Index" }); - // routes.MapRoute( + // routes.MapEndpoint( // "RouteWithOptionalSegment", // "{controller}/{action}/{path?}"); //}); - - app.UseEndpoint(); } - - public static void Main(string[] args) - { - var host = CreateWebHostBuilder(args) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConsole(); - }) - .Build(); - - host.Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .UseKestrel() - .UseIISIntegration(); } -} - +} \ No newline at end of file