22
33namespace Asp . Versioning . ApiExplorer ;
44
5+ using Asp . Versioning . ApiExplorer . Internal ;
56using Microsoft . Extensions . Options ;
6- using static Asp . Versioning . ApiVersionMapping ;
7- using static System . Globalization . CultureInfo ;
87
98/// <summary>
109/// Represents the default implementation of an object that discovers and describes the API version information within an application.
1110/// </summary>
1211[ CLSCompliant ( false ) ]
1312public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvider
1413{
15- private readonly ApiVersionDescriptionCollection collection ;
14+ private readonly ApiVersionDescriptionCollection < GroupedApiVersionMetadata > collection ;
1615 private readonly IOptions < ApiExplorerOptions > options ;
1716
1817 /// <summary>
@@ -28,7 +27,7 @@ public DefaultApiVersionDescriptionProvider(
2827 ISunsetPolicyManager sunsetPolicyManager ,
2928 IOptions < ApiExplorerOptions > apiExplorerOptions )
3029 {
31- collection = new ( this , providers ?? throw new ArgumentNullException ( nameof ( providers ) ) ) ;
30+ collection = new ( Describe , providers ?? throw new ArgumentNullException ( nameof ( providers ) ) ) ;
3231 SunsetPolicyManager = sunsetPolicyManager ;
3332 options = apiExplorerOptions ;
3433 }
@@ -58,133 +57,53 @@ protected virtual IReadOnlyList<ApiVersionDescription> Describe( IReadOnlyList<A
5857 {
5958 ArgumentNullException . ThrowIfNull ( metadata ) ;
6059
61- var descriptions = new List < ApiVersionDescription > ( capacity : metadata . Count ) ;
62- var supported = new HashSet < ApiVersion > ( ) ;
63- var deprecated = new HashSet < ApiVersion > ( ) ;
64-
65- BucketizeApiVersions ( metadata , supported , deprecated ) ;
66- AppendDescriptions ( descriptions , supported , deprecated : false ) ;
67- AppendDescriptions ( descriptions , deprecated , deprecated : true ) ;
68-
69- return descriptions . OrderBy ( d => d . ApiVersion ) . ToArray ( ) ;
70- }
71-
72- private void BucketizeApiVersions ( IReadOnlyList < ApiVersionMetadata > metadata , HashSet < ApiVersion > supported , HashSet < ApiVersion > deprecated )
73- {
74- var declared = new HashSet < ApiVersion > ( ) ;
75- var advertisedSupported = new HashSet < ApiVersion > ( ) ;
76- var advertisedDeprecated = new HashSet < ApiVersion > ( ) ;
77-
78- for ( var i = 0 ; i < metadata . Count ; i ++ )
60+ // TODO: consider refactoring and removing GroupedApiVersionDescriptionProvider as both implementations are now
61+ // effectively the same. this cast is safe as an internal implementation detail. if this method is
62+ // overridden, then this code doesn't even run
63+ //
64+ // REF: https://github.com/dotnet/aspnet-api-versioning/issues/1066
65+ if ( metadata is GroupedApiVersionMetadata [ ] groupedMetadata )
7966 {
80- var model = metadata [ i ] . Map ( Explicit | Implicit ) ;
81- var versions = model . DeclaredApiVersions ;
82-
83- for ( var j = 0 ; j < versions . Count ; j ++ )
84- {
85- declared . Add ( versions [ j ] ) ;
86- }
87-
88- versions = model . SupportedApiVersions ;
89-
90- for ( var j = 0 ; j < versions . Count ; j ++ )
91- {
92- var version = versions [ j ] ;
93- supported . Add ( version ) ;
94- advertisedSupported . Add ( version ) ;
95- }
96-
97- versions = model . DeprecatedApiVersions ;
98-
99- for ( var j = 0 ; j < versions . Count ; j ++ )
100- {
101- var version = versions [ j ] ;
102- deprecated . Add ( version ) ;
103- advertisedDeprecated . Add ( version ) ;
104- }
67+ return DescriptionProvider . Describe ( groupedMetadata , SunsetPolicyManager , Options ) ;
10568 }
10669
107- advertisedSupported . ExceptWith ( declared ) ;
108- advertisedDeprecated . ExceptWith ( declared ) ;
109- supported . ExceptWith ( advertisedSupported ) ;
110- deprecated . ExceptWith ( supported . Concat ( advertisedDeprecated ) ) ;
111-
112- if ( supported . Count == 0 && deprecated . Count == 0 )
113- {
114- supported . Add ( Options . DefaultApiVersion ) ;
115- }
70+ return Array . Empty < ApiVersionDescription > ( ) ;
11671 }
11772
118- private void AppendDescriptions ( List < ApiVersionDescription > descriptions , IEnumerable < ApiVersion > versions , bool deprecated )
73+ private sealed class GroupedApiVersionMetadata :
74+ ApiVersionMetadata ,
75+ IEquatable < GroupedApiVersionMetadata > ,
76+ IGroupedApiVersionMetadata ,
77+ IGroupedApiVersionMetadataFactory < GroupedApiVersionMetadata >
11978 {
120- foreach ( var version in versions )
121- {
122- var groupName = version . ToString ( Options . GroupNameFormat , CurrentCulture ) ;
123- var sunsetPolicy = SunsetPolicyManager . TryGetPolicy ( version , out var policy ) ? policy : default ;
124- descriptions . Add ( new ( version , groupName , deprecated , sunsetPolicy ) ) ;
125- }
126- }
79+ private GroupedApiVersionMetadata ( string ? groupName , ApiVersionMetadata metadata )
80+ : base ( metadata ) => GroupName = groupName ;
12781
128- private sealed class ApiVersionDescriptionCollection (
129- DefaultApiVersionDescriptionProvider provider ,
130- IEnumerable < IApiVersionMetadataCollationProvider > collators )
131- {
132- private readonly object syncRoot = new ( ) ;
133- private readonly DefaultApiVersionDescriptionProvider provider = provider ;
134- private readonly IApiVersionMetadataCollationProvider [ ] collators = collators . ToArray ( ) ;
135- private IReadOnlyList < ApiVersionDescription > ? items ;
136- private int version ;
137-
138- public IReadOnlyList < ApiVersionDescription > Items
139- {
140- get
141- {
142- if ( items is not null && version == ComputeVersion ( ) )
143- {
144- return items ;
145- }
82+ public string ? GroupName { get ; }
14683
147- lock ( syncRoot )
148- {
149- var currentVersion = ComputeVersion ( ) ;
84+ static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory < GroupedApiVersionMetadata > . New (
85+ string ? groupName ,
86+ ApiVersionMetadata metadata ) => new ( groupName , metadata ) ;
15087
151- if ( items is not null && version == currentVersion )
152- {
153- return items ;
154- }
88+ public bool Equals ( GroupedApiVersionMetadata ? other ) =>
89+ other is not null && other . GetHashCode ( ) == GetHashCode ( ) ;
15590
156- var context = new ApiVersionMetadataCollationContext ( ) ;
91+ public override bool Equals ( object ? obj ) =>
92+ obj is not null &&
93+ GetType ( ) . Equals ( obj . GetType ( ) ) &&
94+ GetHashCode ( ) == obj . GetHashCode ( ) ;
15795
158- for ( var i = 0 ; i < collators . Length ; i ++ )
159- {
160- collators [ i ] . Execute ( context ) ;
161- }
162-
163- items = provider . Describe ( context . Results ) ;
164- version = currentVersion ;
165- }
166-
167- return items ;
168- }
169- }
170-
171- private int ComputeVersion ( ) =>
172- collators . Length switch
173- {
174- 0 => 0 ,
175- 1 => collators [ 0 ] . Version ,
176- _ => ComputeVersion ( collators ) ,
177- } ;
178-
179- private static int ComputeVersion ( IApiVersionMetadataCollationProvider [ ] providers )
96+ public override int GetHashCode ( )
18097 {
18198 var hash = default ( HashCode ) ;
18299
183- for ( var i = 0 ; i < providers . Length ; i ++ )
100+ if ( ! string . IsNullOrEmpty ( GroupName ) )
184101 {
185- hash . Add ( providers [ i ] . Version ) ;
102+ hash . Add ( GroupName , StringComparer . Ordinal ) ;
186103 }
187104
105+ hash . Add ( base . GetHashCode ( ) ) ;
106+
188107 return hash . ToHashCode ( ) ;
189108 }
190109 }
0 commit comments