Skip to content

Commit 2ff5849

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Initial ASP.NET Core 2.0 support
1 parent 68bd434 commit 2ff5849

File tree

11 files changed

+292
-60
lines changed

11 files changed

+292
-60
lines changed

src/Common/Versioning/MediaTypeApiVersionReader.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,17 @@ orderby entry.Quality descending
8686
{
8787
foreach ( var parameter in entry.Parameters )
8888
{
89+
#if WEBAPI
8990
if ( comparer.Equals( parameter.Name, ParameterName ) )
9091
{
9192
return parameter.Value;
9293
}
94+
#else
95+
if ( comparer.Equals( parameter.Name.Value, ParameterName ) )
96+
{
97+
return parameter.Value.Value;
98+
}
99+
#endif
93100
}
94101
}
95102
}
@@ -110,10 +117,17 @@ protected virtual string ReadContentTypeHeader( MediaTypeHeaderValue contentType
110117

111118
foreach ( var parameter in contentType.Parameters )
112119
{
120+
#if WEBAPI
113121
if ( comparer.Equals( parameter.Name, ParameterName ) )
114122
{
115123
return parameter.Value;
116124
}
125+
#else
126+
if ( comparer.Equals( parameter.Name.Value, ParameterName ) )
127+
{
128+
return parameter.Value.Value;
129+
}
130+
#endif
117131
}
118132

119133
return null;

src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiVersionModelMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
22
{
33
using Microsoft.AspNetCore.Mvc.ModelBinding;
4+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
45
using System;
56
using System.Collections.Generic;
67
using System.Diagnostics.Contracts;
7-
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
88

99
sealed class ApiVersionModelMetadata : ModelMetadata
1010
{
@@ -65,7 +65,7 @@ internal ApiVersionModelMetadata( ModelMetadata inner, string description )
6565

6666
public override bool IsRequired => inner.IsRequired;
6767

68-
public override IModelBindingMessageProvider ModelBindingMessageProvider => inner.ModelBindingMessageProvider;
68+
public override ModelBindingMessageProvider ModelBindingMessageProvider => inner.ModelBindingMessageProvider;
6969

7070
public override int Order => inner.Order;
7171

src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<VersionPrefix>1.0.1</VersionPrefix>
5-
<AssemblyVersion>1.0.0.0</AssemblyVersion>
6-
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
7-
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
8-
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.6' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
4+
<VersionPrefix>2.0.0</VersionPrefix>
5+
<AssemblyVersion>2.0.0.0</AssemblyVersion>
6+
<TargetFramework>netstandard2.0</TargetFramework>
7+
<NETStandardImplicitPackageVersion>2.0.0-*</NETStandardImplicitPackageVersion>
8+
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
99
<AssemblyName>Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer</AssemblyName>
1010
<AssemblyTitle>Microsoft ASP.NET Core API Versioning</AssemblyTitle>
1111
<Description>ASP.NET Core MVC API explorer functionality for discovering metadata such as the list of API-versioned controllers and actions, and their URLs and allowed HTTP methods.</Description>
1212
<RootNamespace>Microsoft.AspNetCore.Mvc.ApiExplorer</RootNamespace>
1313
<PackageTags>Microsoft;AspNet;AspNetCore;Versioning;ApiExplorer</PackageTags>
14-
<PackageReleaseNotes></PackageReleaseNotes>
14+
<PackageReleaseNotes>Complementary release for ASP.NET Core 2.0 (Preview 2)</PackageReleaseNotes>
1515
</PropertyGroup>
1616

1717
<ItemGroup>
1818
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Versioning\Microsoft.AspNetCore.Mvc.Versioning.csproj" />
1919
</ItemGroup>
2020

2121
<ItemGroup>
22-
<PackageReference Include="Microsoft.AspNetCore.Mvc.ApiExplorer" Version="1.1.1" />
22+
<PackageReference Include="Microsoft.AspNetCore.Mvc.ApiExplorer" Version="2.0.0-preview2-final" />
2323
</ItemGroup>
2424

2525
<ItemGroup>

src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<VersionPrefix>1.2.1</VersionPrefix>
5-
<AssemblyVersion>1.2.0.0</AssemblyVersion>
6-
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
7-
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
8-
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.6' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
4+
<VersionPrefix>2.0.0</VersionPrefix>
5+
<AssemblyVersion>2.0.0.0</AssemblyVersion>
6+
<TargetFramework>netstandard2.0</TargetFramework>
7+
<NETStandardImplicitPackageVersion>2.0.0-*</NETStandardImplicitPackageVersion>
8+
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
99
<AssemblyName>Microsoft.AspNetCore.Mvc.Versioning</AssemblyName>
1010
<AssemblyTitle>Microsoft ASP.NET Core API Versioning</AssemblyTitle>
1111
<Description>A service API versioning library for Microsoft ASP.NET Core.</Description>
1212
<RootNamespace>Microsoft.AspNetCore.Mvc</RootNamespace>
1313
<PackageTags>Microsoft;AspNet;AspNetCore;Versioning</PackageTags>
14-
<PackageReleaseNotes>• Fix 405 for API version-neutral routes (Issue #159)</PackageReleaseNotes>
14+
<PackageReleaseNotes>Complementary release for ASP.NET Core 2.0 (Preview 2)</PackageReleaseNotes>
1515
</PropertyGroup>
1616

1717
<ItemGroup>
18-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="[1.1.1,2.0.0)" />
19-
</ItemGroup>
20-
21-
<ItemGroup Condition=" '$(TargetFramework)' == 'net451' ">
22-
<Reference Include="System" />
23-
<Reference Include="Microsoft.CSharp" />
18+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.0.0-preview2-final" />
2419
</ItemGroup>
2520

2621
<ItemGroup>

src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersionActionSelector.cs

Lines changed: 202 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
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>
@@ -21,35 +22,55 @@
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

Comments
 (0)