22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Linq ;
56using System . Collections . Generic ;
67using System . Diagnostics ;
8+ using System . Text ;
79
810namespace Microsoft . AspNetCore . Components . Routing
911{
@@ -25,23 +27,52 @@ public RouteEntry(RouteTemplate template, Type handler, string[] unusedRoutePara
2527
2628 internal void Match ( RouteContext context )
2729 {
28- if ( Template . Segments . Length != context . Segments . Length )
30+ // If there are no optional segments on the route and the length of the route
31+ // and the template do not match, then there is no chance of this matching and
32+ // we can bail early.
33+ if ( Template . OptionalSegmentsCount == 0 && Template . Segments . Length != context . Segments . Length )
2934 {
3035 return ;
3136 }
3237
3338 // Parameters will be lazily initialized.
3439 Dictionary < string , object > parameters = null ;
40+ var numMatchingSegments = 0 ;
3541 for ( var i = 0 ; i < Template . Segments . Length ; i ++ )
3642 {
3743 var segment = Template . Segments [ i ] ;
38- var pathSegment = context . Segments [ i ] ;
44+
45+ // If the template contains more segments than the path, then
46+ // we may need to break out of this for-loop. This can happen
47+ // in one of two cases:
48+ //
49+ // (1) If we are comparing a literal route with a literal template
50+ // and the route is shorter than the template.
51+ // (2) If we are comparing a template where the last value is an optional
52+ // parameter that the route does not provide.
53+ if ( i >= context . Segments . Length )
54+ {
55+ // If we are under condition (1) above then we can stop evaluating
56+ // matches on the rest of this template.
57+ if ( ! segment . IsParameter && ! segment . IsOptional )
58+ {
59+ break ;
60+ }
61+ }
62+
63+ string pathSegment = null ;
64+ if ( i < context . Segments . Length )
65+ {
66+ pathSegment = context . Segments [ i ] ;
67+ }
68+
3969 if ( ! segment . Match ( pathSegment , out var matchedParameterValue ) )
4070 {
4171 return ;
4272 }
4373 else
4474 {
75+ numMatchingSegments ++ ;
4576 if ( segment . IsParameter )
4677 {
4778 parameters ??= new Dictionary < string , object > ( StringComparer . Ordinal ) ;
@@ -62,8 +93,32 @@ internal void Match(RouteContext context)
6293 }
6394 }
6495
65- context . Parameters = parameters ;
66- context . Handler = Handler ;
96+ // We track the number of segments in the template that matched
97+ // against this particular route then only select the route that
98+ // matches the most number of segments on the route that was passed.
99+ // This check is an exactness check that favors the more precise of
100+ // two templates in the event that the following route table exists.
101+ // Route 1: /{anythingGoes}
102+ // Route 2: /users/{id:int}
103+ // And the provided route is `/users/1`. We want to choose Route 2
104+ // over Route 1.
105+ // Furthermore, literal routes are preferred over parameterized routes.
106+ // If the two routes below are registered in the route table.
107+ // Route 1: /users/1
108+ // Route 2: /users/{id:int}
109+ // And the provided route is `/users/1`. We want to choose Route 1 over
110+ // Route 2.
111+ var allRouteSegmentsMatch = numMatchingSegments >= context . Segments . Length ;
112+ // Checking that all route segments have been matches does not suffice if we are
113+ // comparing literal templates with literal routes. For example, the template
114+ // `/this/is/a/template` and the route `/this/`. In that case, we want to ensure
115+ // that all non-optional segments have matched as well.
116+ var allNonOptionalSegmentsMatch = numMatchingSegments >= ( Template . Segments . Length - Template . OptionalSegmentsCount ) ;
117+ if ( allRouteSegmentsMatch && allNonOptionalSegmentsMatch )
118+ {
119+ context . Parameters = parameters ;
120+ context . Handler = Handler ;
121+ }
67122 }
68123 }
69124}
0 commit comments