Adding a sample (test) for using custom IRouter implementation with MVC
This is a demonstration of how to inject an IRouter in between traditional routes and MVC's handler. This allows you to accomplish a variety of things that were possible with WebAPIs handlers, but inside the routing system. The example here turns a header representing the user into a locale, which is used to select a controller. You could do other things like reject the route match or change link generation. There is one subtle project change here, to allow the same to be possible for attribute routing, we need to create the attribute route after running the user's routing configuration code.
This commit is contained in:
parent
02f4ca9f05
commit
623b733eaa
17
Mvc.sln
17
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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IApplicationBuilder> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"] + ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>364ec3c6-c9db-45e0-a0f2-1ee61e4b429b</ProjectGuid>
|
||||
<RootNamespace>CustomRouteWebSite</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>4095</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string, string> _users = new Dictionary<string, string>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CustomRouteWebSite
|
||||
===
|
||||
|
||||
This web site illustrates how a custom route injects route data based on the user.
|
||||
Loading…
Reference in New Issue