Skip to content
This repository was archived by the owner on Nov 25, 2019. It is now read-only.

Commit 235544c

Browse files
author
hongmeig
committed
Fix for adding custom httproute. Before this fix, if one add custom
HttpRoute through the config.Routes.Add method, their GetRouteData method and GetVirtualPath are ignored. Now it will get invoked properly.
1 parent 54777c5 commit 235544c

File tree

7 files changed

+213
-15
lines changed

7 files changed

+213
-15
lines changed

src/System.Web.Http.WebHost/Routing/HostedHttpRouteCollection.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public override IHttpRouteData GetRouteData(HttpRequestMessage request)
8080
httpContextBase = new HttpRequestMessageContextWrapper(VirtualPathRoot, request);
8181
}
8282

83+
if (httpContextBase.GetHttpRequestMessage() == null)
84+
{
85+
httpContextBase.SetHttpRequestMessage(request);
86+
}
87+
8388
RouteData routeData = _routeCollection.GetRouteData(httpContextBase);
8489
if (routeData != null)
8590
{
@@ -103,6 +108,11 @@ public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request,
103108
httpContextBase = new HttpRequestMessageContextWrapper(VirtualPathRoot, request);
104109
}
105110

111+
if (httpContextBase.GetHttpRequestMessage() == null)
112+
{
113+
httpContextBase.SetHttpRequestMessage(request);
114+
}
115+
106116
IHttpRouteData routeData = request.GetRouteData();
107117
if (routeData == null)
108118
{

src/System.Web.Http.WebHost/Routing/HttpContextBaseExtensions.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static HttpRequestMessage GetHttpRequestMessage(this HttpContextBase cont
1515
throw Error.ArgumentNull("context");
1616
}
1717

18-
if (!context.Items.Contains(HttpRequestMessageKey))
18+
if (context.Items == null || !context.Items.Contains(HttpRequestMessageKey))
1919
{
2020
return null;
2121
}
@@ -25,7 +25,27 @@ public static HttpRequestMessage GetHttpRequestMessage(this HttpContextBase cont
2525

2626
public static void SetHttpRequestMessage(this HttpContextBase context, HttpRequestMessage request)
2727
{
28-
context.Items.Add(HttpRequestMessageKey, request);
28+
if (context.Items != null)
29+
{
30+
context.Items.Add(HttpRequestMessageKey, request);
31+
}
32+
}
33+
34+
public static HttpRequestMessage GetOrCreateHttpRequestMessage(this HttpContextBase context)
35+
{
36+
if (context == null)
37+
{
38+
throw Error.ArgumentNull("context");
39+
}
40+
41+
HttpRequestMessage request = context.GetHttpRequestMessage();
42+
if (request == null)
43+
{
44+
request = HttpControllerHandler.ConvertRequest(context);
45+
context.SetHttpRequestMessage(request);
46+
}
47+
48+
return request;
2949
}
3050
}
3151
}

src/System.Web.Http.WebHost/Routing/HttpRouteDataExtensions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
22

3+
using System.Collections.Generic;
34
using System.Web.Http.Routing;
45
using System.Web.Routing;
56

@@ -21,7 +22,12 @@ public static RouteData ToRouteData(this IHttpRouteData httpRouteData)
2122
}
2223

2324
Route route = httpRouteData.Route.ToRoute();
24-
return new RouteData(route, HttpControllerRouteHandler.Instance);
25+
RouteData result = new RouteData(route, HttpControllerRouteHandler.Instance);
26+
foreach (KeyValuePair<string, object> pair in httpRouteData.Values)
27+
{
28+
result.Values.Add(pair.Key, pair.Value);
29+
}
30+
return result;
2531
}
2632
}
2733
}

src/System.Web.Http.WebHost/Routing/HttpWebRoute.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,30 @@ protected override bool ProcessConstraint(HttpContextBase httpContext, object co
4444
IHttpRouteConstraint httpRouteConstraint = constraint as IHttpRouteConstraint;
4545
if (httpRouteConstraint != null)
4646
{
47-
HttpRequestMessage request = httpContext.GetHttpRequestMessage();
48-
if (request == null)
49-
{
50-
request = HttpControllerHandler.ConvertRequest(httpContext);
51-
httpContext.SetHttpRequestMessage(request);
52-
}
53-
47+
HttpRequestMessage request = httpContext.GetOrCreateHttpRequestMessage();
5448
return httpRouteConstraint.Match(request, HttpRoute, parameterName, values, ConvertRouteDirection(routeDirection));
5549
}
5650

5751
return base.ProcessConstraint(httpContext, constraint, parameterName, values, routeDirection);
5852
}
5953

60-
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
54+
public override RouteData GetRouteData(HttpContextBase httpContext)
6155
{
56+
if (HttpRoute is HostedHttpRoute)
57+
{
58+
return base.GetRouteData(httpContext);
59+
}
60+
else
61+
{
62+
// if user passed us a custom IHttpRoute, then we should invoke their function instead of the base
63+
HttpRequestMessage request = httpContext.GetOrCreateHttpRequestMessage();
64+
IHttpRouteData data = HttpRoute.GetRouteData(httpContext.Request.ApplicationPath, request);
65+
return data == null ? null : data.ToRouteData();
66+
}
67+
}
68+
69+
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
70+
{
6271
// Only perform URL generation if the "httproute" key was specified. This allows these
6372
// routes to be ignored when a regular MVC app tries to generate URLs. Without this special
6473
// key an HTTP route used for Web API would normally take over almost all the routes in a
@@ -70,7 +79,18 @@ public override VirtualPathData GetVirtualPath(RequestContext requestContext, Ro
7079
// Remove the value from the collection so that it doesn't affect the generated URL
7180
RouteValueDictionary newValues = GetRouteDictionaryWithoutHttpRouteKey(values);
7281

73-
return base.GetVirtualPath(requestContext, newValues);
82+
if (HttpRoute is HostedHttpRoute)
83+
{
84+
return base.GetVirtualPath(requestContext, newValues);
85+
}
86+
else
87+
{
88+
// if user passed us a custom IHttpRoute, then we should invoke their function instead of the base
89+
HttpRequestMessage request = requestContext.HttpContext.GetOrCreateHttpRequestMessage();
90+
IHttpVirtualPathData virtualPathData = HttpRoute.GetVirtualPath(request, values);
91+
92+
return virtualPathData == null ? null : new VirtualPathData(this, virtualPathData.VirtualPath);
93+
}
7494
}
7595

7696
private static RouteValueDictionary GetRouteDictionaryWithoutHttpRouteKey(IDictionary<string, object> routeValues)

test/System.Web.Http.WebHost.Test/Routing/HostedHttpRouteCollectionTest.cs

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
22

33
using System.Collections.Generic;
4+
using System.Collections.Specialized;
5+
using System.IO;
46
using System.Net.Http;
57
using System.Threading;
8+
using System.Web.Hosting;
69
using System.Web.Http.Dispatcher;
710
using System.Web.Http.Hosting;
811
using System.Web.Http.Routing;
@@ -235,17 +238,85 @@ public void UnsupportedFunctions()
235238
Assert.Throws<NotSupportedException>(() => _webApiRoutes.Remove(null), "This operation is not supported by 'HostedHttpRouteCollection'.");
236239
}
237240

241+
[Fact]
242+
public void ConvertHttpRouteDataToRouteDataRunsCustomHttpRoute()
243+
{
244+
// Arrange
245+
DomainHttpRoute route = new DomainHttpRoute("myDomain", "api/{controller}/{action}", new { controller = "Values", action = "GetTenant" });
246+
HostedHttpRouteCollection collection = new HostedHttpRouteCollection(new RouteCollection());
247+
collection.Add("domainRoute", route);
248+
HttpRequestMessage request = CreateHttpRequestMessageWithContext();
249+
IHttpRouteData httpRouteData = collection.GetRouteData(request);
250+
251+
// Act
252+
RouteData routeData = httpRouteData.ToRouteData();
253+
254+
// Assert
255+
Assert.NotNull(routeData.Values);
256+
Assert.Equal(3, routeData.Values.Count);
257+
Assert.Equal("controllerName", routeData.Values["controller"]);
258+
Assert.Equal("actionName", routeData.Values["action"]);
259+
Assert.Equal("myDomain", routeData.Values["domain"]);
260+
}
261+
262+
[Fact]
263+
public void CustomHttpRouteGetVitualPathRunsCustomHttpRoute()
264+
{
265+
// Arrange
266+
DomainHttpRoute route = new DomainHttpRoute("myDomain", "api/{controller}/{action}", new { controller = "SomeValue", action = "SomeAction" });
267+
HostedHttpRouteCollection collection = new HostedHttpRouteCollection(new RouteCollection());
268+
collection.Add("domainRoute", route);
269+
HttpRequestMessage request = CreateHttpRequestMessageWithContext();
270+
HttpRouteValueDictionary routeValues = new HttpRouteValueDictionary()
271+
{
272+
{"controller", "controllerName"},
273+
{"action", "actionName"},
274+
{"httproute", true}
275+
};
276+
277+
request.Properties[HttpPropertyKeys.HttpRouteDataKey] = new HttpRouteData(route, routeValues);
278+
279+
// Act
280+
IHttpVirtualPathData httpvPathData = collection.GetVirtualPath(request, "domainRoute", routeValues);
281+
282+
// Assert
283+
Assert.NotNull(httpvPathData);
284+
Assert.Equal("/api/controllerName/actionNameFromDomain", httpvPathData.VirtualPath);
285+
}
286+
287+
private static HttpRequestMessage CreateHttpRequestMessageWithContext()
288+
{
289+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/controllerName/actionName");
290+
request.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();
291+
request.Properties[HttpControllerHandler.HttpContextBaseKey] = CreateHttpContext("~/api");
292+
293+
return request;
294+
}
295+
238296
private static HttpContextBase CreateHttpContext(string relativeUrl, string appPathModifierReturnValue = "")
239297
{
240298
var mockContext = new Mock<HttpContextBase>();
241299

242300
mockContext.SetupGet(c => c.Request.ApplicationPath).Returns(String.Empty);
243301
mockContext.SetupGet(c => c.Request.AppRelativeCurrentExecutionFilePath).Returns(relativeUrl);
244302
mockContext.SetupGet(c => c.Request.PathInfo).Returns("");
303+
mockContext.SetupGet(c => c.Items).Returns(new Dictionary<string, object>());
304+
mockContext.SetupGet(c => c.Request.HttpMethod).Returns("GET");
305+
mockContext.SetupGet(c => c.Request.InputStream).Returns(new MemoryStream());
306+
mockContext.SetupGet(c => c.Request.Headers).Returns(new NameValueCollection());
307+
mockContext.SetupGet(c => c.Request.ApplicationPath).Returns("/");
245308

246-
mockContext.Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>()))
247-
.Returns(appPathModifierReturnValue);
248-
309+
if (appPathModifierReturnValue == string.Empty)
310+
{
311+
mockContext.Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>()))
312+
.Returns((string s) => { return s; });
313+
}
314+
else
315+
{
316+
mockContext.Setup(c => c.Response.ApplyAppPathModifier(It.IsAny<string>()))
317+
.Returns(appPathModifierReturnValue);
318+
}
319+
249320
return mockContext.Object;
250321
}
251322

@@ -269,5 +340,32 @@ public override VirtualPathData GetVirtualPath(RequestContext requestContext, Ro
269340
}
270341
}
271342

343+
public class DomainHttpRoute : HttpRoute
344+
{
345+
public DomainHttpRoute(string domain, string routeTemplate, object defaults, object constraints = null)
346+
: base(routeTemplate, new HttpRouteValueDictionary(defaults), new HttpRouteValueDictionary(constraints))
347+
{
348+
Domain = domain;
349+
}
350+
351+
public string Domain { get; set; }
352+
353+
public override IHttpRouteData GetRouteData(string virtualPathRoot, System.Net.Http.HttpRequestMessage request)
354+
{
355+
// Route data
356+
IHttpRouteData data = base.GetRouteData(virtualPathRoot, request);
357+
data.Values.Add("domain", Domain);
358+
return data;
359+
}
360+
361+
public override IHttpVirtualPathData GetVirtualPath(System.Net.Http.HttpRequestMessage request, IDictionary<string, object> values)
362+
{
363+
// customize the action token
364+
values["action"] = "actionNameFromDomain";
365+
366+
return base.GetVirtualPath(request, values);
367+
}
368+
}
369+
272370
}
273371
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2+
3+
using System.Collections.Generic;
4+
using System.Collections.Specialized;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using Microsoft.TestCommon;
9+
using Moq;
10+
11+
namespace System.Web.Http.WebHost.Routing
12+
{
13+
public class HttpContextBaseExtensionsTest
14+
{
15+
[Fact]
16+
public void GetOrCreateHttpRequestMessageFromHttpContextCopiesHeaders()
17+
{
18+
// Arrange
19+
Mock<HttpContextBase> contextMock = new Mock<HttpContextBase>();
20+
Dictionary<string, object> items = new Dictionary<string, object>();
21+
contextMock.Setup(o => o.Items).Returns(items);
22+
var requestMock = new Mock<HttpRequestBase>();
23+
requestMock.Setup(r => r.HttpMethod).Returns("GET");
24+
requestMock.Setup(r => r.InputStream).Returns(new MemoryStream());
25+
NameValueCollection col = new NameValueCollection();
26+
col.Add("customHeader", "customHeaderValue");
27+
requestMock.Setup(r => r.Headers).Returns(col);
28+
contextMock.Setup(o => o.Request).Returns(requestMock.Object);
29+
30+
// Act
31+
contextMock.Object.GetOrCreateHttpRequestMessage();
32+
33+
// Assert
34+
HttpRequestMessage request = contextMock.Object.GetHttpRequestMessage();
35+
Assert.NotNull(request);
36+
Assert.Equal(HttpMethod.Get, request.Method);
37+
IEnumerable<string> headerValues;
38+
Assert.True(request.Headers.TryGetValues("customHeader", out headerValues));
39+
Assert.Equal(1, headerValues.Count());
40+
Assert.Equal("customHeaderValue", headerValues.First());
41+
}
42+
}
43+
}

test/System.Web.Http.WebHost.Test/System.Web.Http.WebHost.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
</Reference>
7070
</ItemGroup>
7171
<ItemGroup>
72+
<Compile Include="Routing\HttpContextBaseExtensionsTest.cs" />
7273
<Compile Include="Routing\HttpRequestMessageWrapperTest.cs" />
7374
<Compile Include="Routing\HostedHttpRouteCollectionTest.cs" />
7475
<Compile Include="WebHostBufferPolicySelectorTest.cs" />

0 commit comments

Comments
 (0)