diff --git a/Mvc.sln b/Mvc.sln index ccb7a68633..914ebd1fbe 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22416.0 +VisualStudioVersion = 14.0.22410.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -116,6 +116,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoggingWebSite", "test\WebS EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorPageMiddlewareWebSite", "test\WebSites\ErrorPageMiddlewareWebSite\ErrorPageMiddlewareWebSite.kproj", "{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CustomRouteWebSite", "test\WebSites\CustomRouteWebSite\CustomRouteWebSite.kproj", "{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -640,6 +642,18 @@ Global {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Mixed Platforms.Build.0 = Release|Any CPU {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.ActiveCfg = Release|Any CPU {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.Build.0 = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|x86.ActiveCfg = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|x86.Build.0 = Debug|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Any CPU.Build.0 = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|x86.ActiveCfg = Release|Any CPU + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -696,5 +710,6 @@ Global {0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs b/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs index 04db8c8970..438d916a08 100644 --- a/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs @@ -37,12 +37,14 @@ namespace Microsoft.AspNet.Builder ServiceProvider = app.ApplicationServices }; - routes.Routes.Add(AttributeRouting.CreateAttributeMegaRoute( + configureRoutes(routes); + + // Adding the attribute route comes after running the user-code because + // we want to respect any changes to the DefaultHandler. + routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute( routes.DefaultHandler, app.ApplicationServices)); - configureRoutes(routes); - return app.UseRouter(routes.Build()); } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/CustomRouteTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/CustomRouteTest.cs new file mode 100644 index 0000000000..95a0b35d5c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/CustomRouteTest.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class CustomRouteTest + { + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(CustomRouteWebSite)); + private readonly Action _app = new CustomRouteWebSite.Startup().Configure; + + [Theory] + [InlineData("Javier", "Hola from Spain.")] + [InlineData("Doug", "Hello from Canada.")] + [InlineData("Ryan", "Hello from the USA.")] + public async Task RouteToLocale_ConventionalRoute_BasedOnUser(string user, string expected) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/CustomRoute_Products/Index"); + request.Headers.Add("User", user); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, content); + } + + [Theory] + [InlineData("Javier", "Hello from es-ES.")] + [InlineData("Doug", "Hello from en-CA.")] + [InlineData("Ryan", "Hello from en-US.")] + public async Task RouteWithAttributeRoute_IncludesLocale_BasedOnUser(string user, string expected) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/CustomRoute_Orders/5"); + request.Headers.Add("User", user); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, content); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 80f87a620b..81eeb63311 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -12,6 +12,7 @@ "BasicWebSite": "1.0.0", "CompositeViewEngineWebSite": "1.0.0", "ConnegWebSite": "1.0.0", + "CustomRouteWebSite": "1.0.0", "ErrorPageMiddlewareWebSite": "1.0.0", "FilesWebSite": "1.0.0", "FiltersWebSite": "1.0.0", diff --git a/test/WebSites/CustomRouteWebSite/Controllers/Canada/CustomRoute_ProductsController.cs b/test/WebSites/CustomRouteWebSite/Controllers/Canada/CustomRoute_ProductsController.cs new file mode 100644 index 0000000000..228ef7ef30 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/Controllers/Canada/CustomRoute_ProductsController.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace CustomRouteWebSite.Controllers.Canada +{ + [Locale("en-CA")] + public class CustomRoute_ProductsController : Controller + { + public string Index() + { + return "Hello from Canada."; + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/Controllers/CustomRoute_OrdersControlller.cs b/test/WebSites/CustomRouteWebSite/Controllers/CustomRoute_OrdersControlller.cs new file mode 100644 index 0000000000..1f3187acfa --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/Controllers/CustomRoute_OrdersControlller.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace CustomRouteWebSite.Controllers +{ + public class CustomRoute_OrdersControlller : Controller + { + [HttpGet("CustomRoute_Orders/{id}")] + public string Index(int id) + { + return "Hello from " + ActionContext.RouteData.Values["locale"] + "."; + } + } +} diff --git a/test/WebSites/CustomRouteWebSite/Controllers/Spain/CustomRoute_ProductsController.cs b/test/WebSites/CustomRouteWebSite/Controllers/Spain/CustomRoute_ProductsController.cs new file mode 100644 index 0000000000..b5e3960e68 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/Controllers/Spain/CustomRoute_ProductsController.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace CustomRouteWebSite.Controllers.Spain +{ + [Locale("es-ES")] + public class CustomRoute_ProductsController : Controller + { + public string Index() + { + return "Hola from Spain."; + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/Controllers/US/CustomRoute_ProductsController.cs b/test/WebSites/CustomRouteWebSite/Controllers/US/CustomRoute_ProductsController.cs new file mode 100644 index 0000000000..ae7c36dbf1 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/Controllers/US/CustomRoute_ProductsController.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace CustomRouteWebSite.Controllers.US +{ + [Locale("en-US")] + public class CustomRoute_ProductsController : Controller + { + public string Index() + { + return "Hello from the USA."; + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/CustomRouteWebSite.kproj b/test/WebSites/CustomRouteWebSite/CustomRouteWebSite.kproj new file mode 100644 index 0000000000..fa333aa175 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/CustomRouteWebSite.kproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 364ec3c6-c9db-45e0-a0f2-1ee61e4b429b + CustomRouteWebSite + ..\..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 4095 + + + \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/LocaleAttribute.cs b/test/WebSites/CustomRouteWebSite/LocaleAttribute.cs new file mode 100644 index 0000000000..495f456479 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/LocaleAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Mvc; + +namespace CustomRouteWebSite +{ + public class LocaleAttribute : RouteConstraintAttribute + { + public LocaleAttribute(string locale) + : base("locale", routeValue: locale, blockNonAttributedActions: true) + { + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs b/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs new file mode 100644 index 0000000000..61d2f85db2 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading.Tasks; +using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Http; + +namespace CustomRouteWebSite +{ + public class LocalizedRoute : IRouter + { + private readonly IRouter _next; + + private readonly Dictionary _users = new Dictionary(StringComparer.Ordinal) + { + { "Javier", "es-ES" }, + { "Doug", "en-CA" }, + }; + + public LocalizedRoute(IRouter next) + { + _next = next; + } + + public string GetVirtualPath(VirtualPathContext context) + { + // We just want to act as a pass-through for link generation + return _next.GetVirtualPath(context); + } + + public async Task RouteAsync(RouteContext context) + { + // Saving and restoring the original route data ensures that any values we + // add won't 'leak' if action selection doesn't match. + var oldRouteData = context.RouteData; + + // For diagnostics and link-generation purposes, routing should include + // a list of IRoute instances that lead to the ultimate destination. + // It's the responsibility of each IRouter to add the 'next' before + // calling it. + var newRouteData = new RouteData(oldRouteData); + newRouteData.Routers.Add(_next); + + var locale = GetLocale(context.HttpContext) ?? "en-US"; + newRouteData.Values.Add("locale", locale); + + try + { + context.RouteData = newRouteData; + await _next.RouteAsync(context); + } + finally + { + if (!context.IsHandled) + { + context.RouteData = oldRouteData; + } + } + } + + private string GetLocale(HttpContext context) + { + string locale; + _users.TryGetValue(context.Request.Headers.Get("User"), out locale); + return locale; + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/Startup.cs b/test/WebSites/CustomRouteWebSite/Startup.cs new file mode 100644 index 0000000000..2d39cc65b9 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/Startup.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; + +namespace CustomRouteWebSite +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + var configuration = app.GetTestConfiguration(); + + app.UseServices(services => + { + services.AddMvc(configuration); + }); + + app.UseMvc(routes => + { + routes.DefaultHandler = new LocalizedRoute(routes.DefaultHandler); + routes.MapRoute("default", "{controller}/{action}"); + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/CustomRouteWebSite/project.json b/test/WebSites/CustomRouteWebSite/project.json new file mode 100644 index 0000000000..01696e38f0 --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/project.json @@ -0,0 +1,19 @@ +{ + "commands": { + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", + "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + }, + "webroot": "wwwroot" +} diff --git a/test/WebSites/CustomRouteWebSite/wwwroot/readme.md b/test/WebSites/CustomRouteWebSite/wwwroot/readme.md new file mode 100644 index 0000000000..04955ec21b --- /dev/null +++ b/test/WebSites/CustomRouteWebSite/wwwroot/readme.md @@ -0,0 +1,4 @@ +CustomRouteWebSite +=== + +This web site illustrates how a custom route injects route data based on the user. \ No newline at end of file