Skip to content

Commit a917cbd

Browse files
author
Yishai Galatzer
committed
Constraints
1 parent cf16d6c commit a917cbd

16 files changed

+238
-54
lines changed

samples/RoutingSample.Web/HttpContextRouteEndpoint.cs renamed to samples/RoutingSample.Web/DelegateRouteEndpoint.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
using System.Threading.Tasks;
2-
using Microsoft.AspNet.Abstractions;
32
using Microsoft.AspNet.Routing;
43

54
namespace RoutingSample.Web
65
{
7-
public class HttpContextRouteEndpoint : IRouter
6+
public class DelegateRouteEndpoint : IRouter
87
{
9-
private readonly RequestDelegate _appFunc;
8+
public delegate Task RoutedDelegate(RouteContext context);
109

11-
public HttpContextRouteEndpoint(RequestDelegate appFunc)
10+
private readonly RoutedDelegate _appFunc;
11+
12+
public DelegateRouteEndpoint(RoutedDelegate appFunc)
1213
{
1314
_appFunc = appFunc;
1415
}
1516

1617
public async Task RouteAsync(RouteContext context)
1718
{
18-
await _appFunc(context.HttpContext);
19+
await _appFunc(context);
1920
context.IsHandled = true;
2021
}
2122

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace RoutingSample.Web
5+
{
6+
public static class DictionaryExtensions
7+
{
8+
public static string Print(this IDictionary<string, object> routeValues)
9+
{
10+
var values = routeValues.Select(kvp => kvp.Key + ":" + kvp.Value.ToString());
11+
12+
return string.Join(" ", values);
13+
}
14+
}
15+
}

samples/RoutingSample.Web/Startup.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.AspNet.Abstractions;
1+
using System.Text.RegularExpressions;
2+
using Microsoft.AspNet.Abstractions;
23
using Microsoft.AspNet.Routing;
34

45
namespace RoutingSample.Web
@@ -9,11 +10,23 @@ public void Configuration(IBuilder builder)
910
{
1011
var routes = builder.UseRouter();
1112

12-
var endpoint1 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("match1"));
13-
var endpoint2 = new HttpContextRouteEndpoint(async (context) => await context.Response.WriteAsync("Hello, World!"));
13+
var endpoint1 = new DelegateRouteEndpoint(async (context) =>
14+
await context.HttpContext.Response.WriteAsync(
15+
"match1, route values -" + context.Values.Print()));
16+
var endpoint2 = new DelegateRouteEndpoint(async (context) =>
17+
await context.HttpContext.Response.WriteAsync("Hello, World!"));
1418

1519
routes.DefaultHandler = endpoint1;
1620
routes.AddPrefixRoute("api/store");
21+
22+
routes.MapRoute("api/constraint/{controller}", null, new { controller = "my.*" });
23+
routes.MapRoute("api/rconstraint/{controller}",
24+
new { foo = "Bar" },
25+
new { controller = new RegexConstraint("^(my.*)$") });
26+
routes.MapRoute("api/r2constraint/{controller}",
27+
new { foo = "Bar2" },
28+
new { controller = new RegexConstraint(new Regex("^(my.*)$")) });
29+
1730
routes.MapRoute("api/{controller}/{*extra}", new { controller = "Store" });
1831

1932
routes.AddPrefixRoute("hello/world", endpoint2);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
using Microsoft.AspNet.Abstractions;
3+
4+
namespace Microsoft.AspNet.Routing
5+
{
6+
public interface IRouteConstraint
7+
{
8+
bool Match([NotNull] HttpContext httpContext,
9+
[NotNull] IRouter route,
10+
[NotNull] string routeKey,
11+
[NotNull] IDictionary<string, object> values,
12+
RouteDirection routeDirection);
13+
}
14+
}

src/Microsoft.AspNet.Routing/IRouteValues.cs

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Microsoft.AspNet.Routing
4+
{
5+
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
6+
internal sealed class NotNullAttribute : Attribute
7+
{
8+
}
9+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Text.RegularExpressions;
5+
using Microsoft.AspNet.Abstractions;
6+
7+
namespace Microsoft.AspNet.Routing
8+
{
9+
public class RegexConstraint : IRouteConstraint
10+
{
11+
public RegexConstraint([NotNull] Regex regex)
12+
{
13+
Constraint = regex;
14+
}
15+
16+
public RegexConstraint([NotNull] string regexPattern)
17+
{
18+
Constraint = new Regex(regexPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
19+
}
20+
21+
public Regex Constraint { get; private set; }
22+
23+
public bool Match([NotNull] HttpContext httpContext,
24+
[NotNull] IRouter route,
25+
[NotNull] string routeKey,
26+
[NotNull] IDictionary<string, object> routeValues,
27+
RouteDirection routeDirection)
28+
{
29+
object routeValue;
30+
31+
if (routeValues.TryGetValue(routeKey, out routeValue)
32+
&& routeValue != null)
33+
{
34+
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
35+
36+
return Constraint.IsMatch(parameterValueString);
37+
}
38+
39+
return false;
40+
}
41+
}
42+
}

src/Microsoft.AspNet.Routing/RouteCollection.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2-
32
using System.Collections.Generic;
43
using System.Threading.Tasks;
54

src/Microsoft.AspNet.Routing/RouteCollectionExtensions.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,39 @@ public static IRouteCollection MapRoute(this IRouteCollection routes, string tem
2727
throw new ArgumentException("DefaultHandler must be set.");
2828
}
2929

30-
routes.Add(new TemplateRoute(routes.DefaultHandler, template, defaults));
30+
routes.Add(new TemplateRoute(routes.DefaultHandler, template, defaults, null));
31+
return routes;
32+
}
33+
34+
public static IRouteCollection MapRoute(this IRouteCollection routes, string template, object defaults, object constraints)
35+
{
36+
MapRoute(routes, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints));
37+
return routes;
38+
}
39+
40+
public static IRouteCollection MapRoute(this IRouteCollection routes, string template, object defaults,
41+
IDictionary<string, object> constraints)
42+
{
43+
MapRoute(routes, template, new RouteValueDictionary(defaults), constraints);
44+
return routes;
45+
}
46+
47+
public static IRouteCollection MapRoute(this IRouteCollection routes, string template,
48+
IDictionary<string, object> defaults, object constraints)
49+
{
50+
MapRoute(routes, template, defaults, new RouteValueDictionary(constraints));
51+
return routes;
52+
}
53+
54+
public static IRouteCollection MapRoute(this IRouteCollection routes, string template,
55+
IDictionary<string, object> defaults, IDictionary<string, object> constraints)
56+
{
57+
if (routes.DefaultHandler == null)
58+
{
59+
throw new ArgumentException("DefaultHandler must be set.");
60+
}
61+
62+
routes.Add(new TemplateRoute(routes.DefaultHandler, template, defaults, constraints));
3163
return routes;
3264
}
3365
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Microsoft.AspNet.Routing
5+
{
6+
public class RouteConstraintBuilder
7+
{
8+
public static IDictionary<string, IRouteConstraint>
9+
BuildConstraints(IDictionary<string, object> inputConstraints)
10+
{
11+
if (inputConstraints == null || inputConstraints.Count == 0)
12+
{
13+
return null;
14+
}
15+
16+
var constraints = new Dictionary<string, IRouteConstraint>(inputConstraints.Count, StringComparer.OrdinalIgnoreCase);
17+
18+
foreach (var kvp in inputConstraints)
19+
{
20+
var constraint = kvp.Value as IRouteConstraint;
21+
22+
if (constraint == null)
23+
{
24+
var regexPattern = kvp.Value as string;
25+
26+
if (regexPattern == null)
27+
{
28+
throw new InvalidOperationException("Constraint can be a valid regex string or an IRouteConstraint");
29+
}
30+
31+
var constraintsRegEx = "^(" + regexPattern + ")$";
32+
33+
constraint = new RegexConstraint(constraintsRegEx);
34+
}
35+
36+
constraints.Add(kvp.Key, constraint);
37+
}
38+
39+
return constraints;
40+
}
41+
}
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.AspNet.Abstractions;
4+
5+
namespace Microsoft.AspNet.Routing
6+
{
7+
public static class RouteConstraintMatcher
8+
{
9+
public static bool Match([NotNull] IDictionary<string, IRouteConstraint> constraints,
10+
[NotNull] IDictionary<string, object> routeValues,
11+
[NotNull] HttpContext httpContext,
12+
[NotNull] IRouter route,
13+
[NotNull] RouteDirection routeDirection)
14+
{
15+
if (constraints == null)
16+
{
17+
return true;
18+
}
19+
20+
foreach (var kvp in constraints)
21+
{
22+
var constraint = kvp.Value;
23+
if (!constraint.Match(httpContext, route, kvp.Key, routeValues, routeDirection))
24+
{
25+
return false;
26+
}
27+
}
28+
29+
return true;
30+
}
31+
}
32+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Microsoft.AspNet.Routing
2+
{
3+
public enum RouteDirection
4+
{
5+
IncomingRequest,
6+
UrlGeneration,
7+
}
8+
}

src/Microsoft.AspNet.Routing/RouteValues.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/Microsoft.AspNet.Routing/Template/TemplateRoute.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@
22

33
using System;
44
using System.Collections.Generic;
5-
using System.Diagnostics;
65
using System.Threading.Tasks;
7-
using Microsoft.AspNet.Abstractions;
86

97
namespace Microsoft.AspNet.Routing.Template
108
{
119
public class TemplateRoute : IRouter
1210
{
1311
private readonly IDictionary<string, object> _defaults;
12+
private readonly IDictionary<string, IRouteConstraint> _constraints;
1413
private readonly IRouter _target;
15-
private readonly Template _parsedTemplate;
1614
private readonly string _routeTemplate;
1715
private readonly TemplateMatcher _matcher;
1816
private readonly TemplateBinder _binder;
1917

2018
public TemplateRoute(IRouter target, string routeTemplate)
21-
: this(target, routeTemplate, null)
19+
: this(target, routeTemplate, null, null)
2220
{
2321
}
2422

25-
public TemplateRoute(IRouter target, string routeTemplate, IDictionary<string, object> defaults)
23+
public TemplateRoute(IRouter target, string routeTemplate, IDictionary<string, object> defaults,
24+
IDictionary<string, object> constraints)
2625
{
2726
if (target == null)
2827
{
@@ -32,11 +31,12 @@ public TemplateRoute(IRouter target, string routeTemplate, IDictionary<string, o
3231
_target = target;
3332
_routeTemplate = routeTemplate ?? string.Empty;
3433
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
34+
_constraints = RouteConstraintBuilder.BuildConstraints(constraints);
3535

3636
// The parser will throw for invalid routes.
37-
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
37+
var parsedTemplate = TemplateParser.Parse(RouteTemplate);
3838

39-
_matcher = new TemplateMatcher(_parsedTemplate);
39+
_matcher = new TemplateMatcher(parsedTemplate);
4040
_binder = new TemplateBinder(_parsedTemplate, _defaults);
4141
}
4242

@@ -74,7 +74,14 @@ public async virtual Task RouteAsync(RouteContext context)
7474
// Not currently doing anything to clean this up if it's not a match. Consider hardening this.
7575
context.Values = values;
7676

77-
await _target.RouteAsync(context);
77+
if (RouteConstraintMatcher.Match(_constraints,
78+
values,
79+
context.HttpContext,
80+
this,
81+
RouteDirection.IncomingRequest))
82+
{
83+
await _target.RouteAsync(context);
84+
}
7885
}
7986
}
8087

src/Microsoft.AspNet.Routing/project.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"version": "0.1-alpha-*",
33
"dependencies": {
4-
"Microsoft.AspNet.Abstractions": "0.1-alpha-*"
4+
"Microsoft.AspNet.Abstractions": "0.1-alpha-*",
5+
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*"
56
},
67
"configurations": {
78
"net45": {},
@@ -19,6 +20,9 @@
1920
"System.Runtime": "4.0.20.0",
2021
"System.Runtime.Extensions": "4.0.10.0",
2122
"System.Text.RegularExpressions": "4.0.0.0",
23+
"System.Runtime.Hosting": "3.9.0.0",
24+
"System.Runtime.InteropServices": "4.0.10.0",
25+
"System.Text.Encoding": "4.0.10.0",
2226
"System.Threading.Tasks": "4.0.0.0"
2327
}
2428
}

0 commit comments

Comments
 (0)