1313 using System . Collections . Generic ;
1414 using System . Diagnostics . Contracts ;
1515 using System . Linq ;
16+ using System . Threading ;
1617 using static ErrorCodes ;
1718
1819 /// <summary>
2122 [ CLSCompliant ( false ) ]
2223 public class ApiVersionActionSelector : IActionSelector
2324 {
24- static readonly IReadOnlyList < ActionDescriptor > NoMatches = new ActionDescriptor [ 0 ] ;
25- readonly IActionSelectorDecisionTreeProvider decisionTreeProvider ;
25+ static readonly IReadOnlyList < ActionDescriptor > NoMatches = Array . Empty < ActionDescriptor > ( ) ;
26+ readonly IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ;
2627 readonly ActionConstraintCache actionConstraintCache ;
2728 readonly IOptions < ApiVersioningOptions > options ;
29+ Cache cache ;
2830
2931 /// <summary>
3032 /// Initializes a new instance of the <see cref="ApiVersionActionSelector"/> class.
3133 /// </summary>
32- /// <param name="decisionTreeProvider ">The <see cref="IActionSelectorDecisionTreeProvider "/> used to select candidate routes.</param>
34+ /// <param name="actionDescriptorCollectionProvider ">The <see cref="IActionDescriptorCollectionProvider "/> used to select candidate routes.</param>
3335 /// <param name="actionConstraintCache">The <see cref="ActionConstraintCache"/> that providers a set of <see cref="IActionConstraint"/> instances.</param>
3436 /// <param name="options">The <see cref="ApiVersioningOptions">options</see> associated with the action selector.</param>
3537 /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
3638 public ApiVersionActionSelector (
37- IActionSelectorDecisionTreeProvider decisionTreeProvider ,
39+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ,
3840 ActionConstraintCache actionConstraintCache ,
3941 IOptions < ApiVersioningOptions > options ,
4042 ILoggerFactory loggerFactory )
4143 {
42- Arg . NotNull ( decisionTreeProvider , nameof ( decisionTreeProvider ) ) ;
44+ Arg . NotNull ( actionDescriptorCollectionProvider , nameof ( actionDescriptorCollectionProvider ) ) ;
4345 Arg . NotNull ( actionConstraintCache , nameof ( actionConstraintCache ) ) ;
4446 Arg . NotNull ( options , nameof ( options ) ) ;
4547 Arg . NotNull ( loggerFactory , nameof ( loggerFactory ) ) ;
4648
47- this . decisionTreeProvider = decisionTreeProvider ;
49+ this . actionDescriptorCollectionProvider = actionDescriptorCollectionProvider ;
4850 this . actionConstraintCache = actionConstraintCache ;
4951 this . options = options ;
5052 Logger = loggerFactory . CreateLogger ( GetType ( ) ) ;
5153 }
5254
55+ Cache Current
56+ {
57+ get
58+ {
59+ var actions = actionDescriptorCollectionProvider . ActionDescriptors ;
60+ var value = Volatile . Read ( ref cache ) ;
61+
62+ if ( value != null && value . Version == actions . Version )
63+ {
64+ return value ;
65+ }
66+
67+ value = new Cache ( actions ) ;
68+ Volatile . Write ( ref cache , value ) ;
69+
70+ return value ;
71+ }
72+ }
73+
5374 /// <summary>
5475 /// Gets the configuration options associated with the action selector.
5576 /// </summary>
@@ -78,8 +99,28 @@ public virtual IReadOnlyList<ActionDescriptor> SelectCandidates( RouteContext co
7899 {
79100 Arg . NotNull ( context , nameof ( context ) ) ;
80101
81- var tree = decisionTreeProvider . DecisionTree ;
82- return tree . Select ( context . RouteData . Values ) ;
102+ var cache = Current ;
103+ var keys = cache . RouteKeys ;
104+ var values = new string [ keys . Length ] ;
105+
106+ for ( var i = 0 ; i < keys . Length ; i ++ )
107+ {
108+ context . RouteData . Values . TryGetValue ( keys [ i ] , out object value ) ;
109+
110+ if ( value != null )
111+ {
112+ values [ i ] = value as string ?? Convert . ToString ( value ) ;
113+ }
114+ }
115+
116+ if ( cache . OrdinalEntries . TryGetValue ( values , out var matchingRouteValues ) ||
117+ cache . OrdinalIgnoreCaseEntries . TryGetValue ( values , out matchingRouteValues ) )
118+ {
119+ return matchingRouteValues ;
120+ }
121+
122+ Logger . NoActionsMatched ( context . RouteData . Values ) ;
123+ return NoMatches ;
83124 }
84125
85126 /// <summary>
@@ -271,6 +312,9 @@ IReadOnlyList<ActionDescriptor> EvaluateActionConstraints( RouteContext context,
271312
272313 IReadOnlyList < ActionSelectorCandidate > EvaluateActionConstraintsCore ( RouteContext context , IReadOnlyList < ActionSelectorCandidate > candidates , int ? startingOrder )
273314 {
315+ Contract . Requires ( context != null ) ;
316+ Contract . Requires ( candidates != null ) ;
317+
274318 var order = default ( int ? ) ;
275319
276320 for ( var i = 0 ; i < candidates . Count ; i ++ )
@@ -422,5 +466,155 @@ public IEnumerator<ActionDescriptorMatch> GetEnumerator()
422466
423467 IEnumerator IEnumerable . GetEnumerator ( ) => GetEnumerator ( ) ;
424468 }
469+
470+ sealed class Cache
471+ {
472+ public Cache ( ActionDescriptorCollection actions )
473+ {
474+ Contract . Requires ( actions != null ) ;
475+
476+ Version = actions . Version ;
477+ OrdinalEntries = new Dictionary < string [ ] , List < ActionDescriptor > > ( StringArrayComparer . Ordinal ) ;
478+ OrdinalIgnoreCaseEntries = new Dictionary < string [ ] , List < ActionDescriptor > > ( StringArrayComparer . OrdinalIgnoreCase ) ;
479+ RouteKeys = IdentifyRouteKeysForActionSelection ( actions ) ;
480+ BuildOrderedSetOfKeysForRouteValues ( actions ) ;
481+ }
482+
483+ public int Version { get ; }
484+ public string [ ] RouteKeys { get ; }
485+ public Dictionary < string [ ] , List < ActionDescriptor > > OrdinalEntries { get ; }
486+ public Dictionary < string [ ] , List < ActionDescriptor > > OrdinalIgnoreCaseEntries { get ; }
487+
488+ static string [ ] IdentifyRouteKeysForActionSelection ( ActionDescriptorCollection actions )
489+ {
490+ Contract . Requires ( actions != null ) ;
491+ Contract . Ensures ( Contract . Result < string [ ] > ( ) != null ) ;
492+
493+ var routeKeys = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
494+
495+ for ( var i = 0 ; i < actions . Items . Count ; i ++ )
496+ {
497+ var action = actions . Items [ i ] ;
498+
499+ if ( action . AttributeRouteInfo == null )
500+ {
501+ foreach ( var kvp in action . RouteValues )
502+ {
503+ routeKeys . Add ( kvp . Key ) ;
504+ }
505+ }
506+ }
507+
508+ return routeKeys . ToArray ( ) ;
509+ }
510+
511+ void BuildOrderedSetOfKeysForRouteValues ( ActionDescriptorCollection actions )
512+ {
513+ Contract . Requires ( actions != null ) ;
514+
515+ for ( var i = 0 ; i < actions . Items . Count ; i ++ )
516+ {
517+ var action = actions . Items [ i ] ;
518+
519+ if ( action . AttributeRouteInfo != null )
520+ {
521+ continue ;
522+ }
523+
524+ var routeValues = new string [ RouteKeys . Length ] ;
525+
526+ for ( var j = 0 ; j < RouteKeys . Length ; j ++ )
527+ {
528+
529+ action . RouteValues . TryGetValue ( RouteKeys [ j ] , out routeValues [ j ] ) ;
530+ }
531+
532+ if ( ! OrdinalIgnoreCaseEntries . TryGetValue ( routeValues , out var entries ) )
533+ {
534+ entries = new List < ActionDescriptor > ( ) ;
535+ OrdinalIgnoreCaseEntries . Add ( routeValues , entries ) ;
536+ }
537+
538+ entries . Add ( action ) ;
539+
540+ if ( ! OrdinalEntries . ContainsKey ( routeValues ) )
541+ {
542+ OrdinalEntries . Add ( routeValues , entries ) ;
543+ }
544+ }
545+ }
546+ }
547+
548+ sealed class StringArrayComparer : IEqualityComparer < string [ ] >
549+ {
550+ readonly StringComparer valueComparer ;
551+ public static readonly StringArrayComparer Ordinal = new StringArrayComparer ( StringComparer . Ordinal ) ;
552+ public static readonly StringArrayComparer OrdinalIgnoreCase = new StringArrayComparer ( StringComparer . OrdinalIgnoreCase ) ;
553+
554+ StringArrayComparer ( StringComparer valueComparer ) => this . valueComparer = valueComparer ;
555+
556+ public bool Equals ( string [ ] x , string [ ] y )
557+ {
558+ if ( ReferenceEquals ( x , y ) )
559+ {
560+ return true ;
561+ }
562+
563+ if ( x == null ^ y == null )
564+ {
565+ return false ;
566+ }
567+
568+ if ( x . Length != y . Length )
569+ {
570+ return false ;
571+ }
572+
573+ for ( var i = 0 ; i < x . Length ; i ++ )
574+ {
575+ if ( string . IsNullOrEmpty ( x [ i ] ) && string . IsNullOrEmpty ( y [ i ] ) )
576+ {
577+ continue ;
578+ }
579+
580+ if ( ! valueComparer . Equals ( x [ i ] , y [ i ] ) )
581+ {
582+ return false ;
583+ }
584+ }
585+
586+ return true ;
587+ }
588+
589+ public int GetHashCode ( string [ ] obj )
590+ {
591+ if ( obj == null )
592+ {
593+ return 0 ;
594+ }
595+
596+ var hash = 0 ;
597+ var i = 0 ;
598+
599+ for ( ; i < obj . Length ; i ++ )
600+ {
601+ if ( obj [ i ] != null )
602+ {
603+ hash = valueComparer . GetHashCode ( obj [ i ] ) ;
604+ break ;
605+ }
606+ }
607+
608+ for ( ; i < obj . Length ; i ++ )
609+ {
610+ if ( obj [ i ] != null )
611+ {
612+ hash = ( hash * 397 ) ^ valueComparer . GetHashCode ( obj [ i ] ) ;
613+ }
614+ }
615+
616+ return hash ;
617+ }
618+ }
425619 }
426620}
0 commit comments