Skip to content

Commit 3aa125a

Browse files
committed
Add HttpRouteTableBuilder for SelfHost.
1 parent 3aa2a2a commit 3aa125a

File tree

2 files changed

+154
-2
lines changed

2 files changed

+154
-2
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Text.RegularExpressions;
5+
using System.Web.Http;
6+
using System.Web.Http.Routing;
7+
using WebApiContrib.Routing;
8+
9+
namespace WebApiContrib.SelfHost.Routing
10+
{
11+
/// <summary>
12+
/// Route table builder that uses the <see cref="HttpRouteAttribute"/> instances found on the controllers
13+
/// in the web application.
14+
/// </summary>
15+
public class HttpRouteTableBuilder
16+
{
17+
/// <summary>
18+
/// Builds a route table based on the Http route
19+
/// attributes found on the controllers in the current web application.
20+
/// </summary>
21+
/// <param name="routes">Route collection to append the routes to</param>
22+
public static void BuildTable(HttpRouteCollection routes)
23+
{
24+
var assembly = Assembly.GetExecutingAssembly();
25+
BuildTable(routes, assembly);
26+
}
27+
28+
/// <summary>
29+
/// Builds a route table based on the Http route
30+
/// attributes found on the controllers in the current web application.
31+
/// </summary>
32+
/// <param name="routes">Route collection to append the routes to</param>
33+
/// <param name="assembly">Assembly to scan for routes</param>
34+
public static void BuildTable(HttpRouteCollection routes, Assembly assembly)
35+
{
36+
var controllerTypes = assembly.GetExportedTypes().Where(type => typeof(ApiController).IsAssignableFrom(type));
37+
38+
// Find all the controller types and extract the HTTP route attributes from
39+
// the public methods that can be found on the controller.
40+
foreach (var controllerType in controllerTypes)
41+
{
42+
BuildControllerRoutes(routes, controllerType);
43+
44+
var methods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
45+
46+
foreach (var method in methods)
47+
{
48+
BuildControllerMethodRoutes(routes, controllerType, method);
49+
}
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Builds the contents for the route table based on the attributes
55+
/// found on a specific controller type.
56+
/// </summary>
57+
/// <param name="routes"></param>
58+
/// <param name="controllerType"></param>
59+
private static void BuildControllerRoutes(HttpRouteCollection routes, Type controllerType)
60+
{
61+
var attributes = (HttpRouteAttribute[]) controllerType.GetCustomAttributes(typeof(HttpRouteAttribute), true);
62+
63+
string controller = controllerType.Name;
64+
65+
// Translate the somewhat weird controller name into one the routing system
66+
// understands, by removing the Controller part from the name.
67+
if (controller.EndsWith("Controller", StringComparison.Ordinal))
68+
{
69+
controller = controller.Substring(0, controller.IndexOf("Controller"));
70+
}
71+
72+
foreach (var attribute in attributes)
73+
{
74+
var routeValuesDictionary = new HttpRouteValueDictionary();
75+
routeValuesDictionary.Add("controller", controller);
76+
77+
// Create the route and attach the default route handler to it.
78+
var route = new HttpRoute(attribute.UriTemplate, routeValuesDictionary, new HttpRouteValueDictionary(), new HttpRouteValueDictionary());
79+
80+
routes.Add(Guid.NewGuid().ToString(), route);
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Builds the contents for the route table based on the attributes
86+
/// found on the method of a specific controller.
87+
/// </summary>
88+
/// <param name="routes"></param>
89+
/// <param name="controllerType"></param>
90+
/// <param name="method"></param>
91+
private static void BuildControllerMethodRoutes(HttpRouteCollection routes, Type controllerType, MethodInfo method)
92+
{
93+
// Grab the http route attributes from the current method.
94+
var attributes = (HttpRouteAttribute[]) method.GetCustomAttributes(typeof(HttpRouteAttribute), true);
95+
96+
if (attributes.Length != 0)
97+
{
98+
// Automatically grab the controller name and action name
99+
// from the method and controller type.
100+
string action = method.Name;
101+
string controller = controllerType.Name;
102+
103+
// Translate the somewhat weird controller name into one the routing system
104+
// understands, by removing the Controller part from the name.
105+
if (controller.EndsWith("Controller", StringComparison.Ordinal))
106+
{
107+
controller = controller.Substring(0, controller.IndexOf("Controller"));
108+
}
109+
110+
// Generate a route for every HTTP route attribute found on the method
111+
foreach (var attribute in attributes)
112+
{
113+
var routeValueDictionary = new HttpRouteValueDictionary();
114+
115+
routeValueDictionary.Add("controller", controller);
116+
routeValueDictionary.Add("action", action);
117+
118+
ResolveOptionalRouteParameters(attribute.UriTemplate, method, routeValueDictionary);
119+
120+
// Create the route and attach the default route handler to it.
121+
var route = new HttpRoute(attribute.UriTemplate, routeValueDictionary, new HttpRouteValueDictionary(), new HttpRouteValueDictionary());
122+
123+
routes.Add(Guid.NewGuid().ToString(), route);
124+
}
125+
}
126+
}
127+
128+
/// <summary>
129+
/// Resolves any route parameters that have been marked as optional
130+
/// </summary>
131+
/// <param name="uriTemplate"></param>
132+
/// <param name="method"></param>
133+
/// <param name="routeValueDictionary"></param>
134+
private static void ResolveOptionalRouteParameters(string uriTemplate, MethodInfo method, HttpRouteValueDictionary routeValueDictionary)
135+
{
136+
Regex pattern = new Regex(@"{(\S+)}");
137+
var methodParameters = method.GetParameters();
138+
139+
foreach (Match match in pattern.Matches(uriTemplate))
140+
{
141+
string parameterName = match.Groups[1].Value;
142+
var parameter = methodParameters.FirstOrDefault(param => param.Name == parameterName);
143+
144+
// Mark the route parameter as optional when there's a method parameter for it
145+
// and that method parameter is marked with [OptionalRouteParameter]
146+
if (parameter != null && parameter.GetCustomAttributes(typeof(OptionalRouteParameterAttribute), true).Length != 0)
147+
{
148+
routeValueDictionary.Add(parameterName, RouteParameter.Optional);
149+
}
150+
}
151+
}
152+
}
153+
}

src/WebApiContrib.SelfHost/WebApiContrib.SelfHost.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
<HintPath>..\..\packages\Microsoft.Net.Http.2.0.20504.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
5252
</Reference>
5353
<Reference Include="System.ServiceModel" />
54-
<Reference Include="System.Web" />
5554
<Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
5655
<Private>True</Private>
5756
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20504.0\lib\net40\System.Web.Http.dll</HintPath>
@@ -60,7 +59,6 @@
6059
<Private>True</Private>
6160
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.20504.0\lib\net40\System.Web.Http.SelfHost.dll</HintPath>
6261
</Reference>
63-
<Reference Include="System.Web.Routing" />
6462
<Reference Include="System.Xml.Linq" />
6563
<Reference Include="System.Data.DataSetExtensions" />
6664
<Reference Include="Microsoft.CSharp" />
@@ -69,6 +67,7 @@
6967
</ItemGroup>
7068
<ItemGroup>
7169
<Compile Include="Properties\AssemblyInfo.cs" />
70+
<Compile Include="Routing\HttpRouteTableBuilder.cs" />
7271
<Compile Include="SslHttpSelfHostConfiguration.cs" />
7372
</ItemGroup>
7473
<ItemGroup>

0 commit comments

Comments
 (0)