33namespace Asp . Versioning . ApiExplorer ;
44
55using Microsoft . AspNetCore . Http ;
6+ using Microsoft . AspNetCore . Mvc . Abstractions ;
7+ using Microsoft . AspNetCore . Mvc . Infrastructure ;
68using Microsoft . AspNetCore . Routing ;
79using Microsoft . Extensions . Options ;
810using Microsoft . Extensions . Primitives ;
@@ -22,15 +24,18 @@ public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvid
2224 /// Initializes a new instance of the <see cref="DefaultApiVersionDescriptionProvider"/> class.
2325 /// </summary>
2426 /// <param name="endpointDataSource">The <see cref="EndpointDataSource">data source</see> for <see cref="Endpoint">endpoints</see>.</param>
27+ /// <param name="actionDescriptorCollectionProvider">The <see cref="IActionDescriptorCollectionProvider">provider</see>
28+ /// used to enumerate the actions within an application.</param>
2529 /// <param name="sunsetPolicyManager">The <see cref="ISunsetPolicyManager">manager</see> used to resolve sunset policies.</param>
2630 /// <param name="apiExplorerOptions">The <see cref="IOptions{TOptions}">container</see> of configured
2731 /// <see cref="ApiExplorerOptions">API explorer options</see>.</param>
2832 public DefaultApiVersionDescriptionProvider (
2933 EndpointDataSource endpointDataSource ,
34+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ,
3035 ISunsetPolicyManager sunsetPolicyManager ,
3136 IOptions < ApiExplorerOptions > apiExplorerOptions )
3237 {
33- collection = new ( this , endpointDataSource ) ;
38+ collection = new ( this , endpointDataSource , actionDescriptorCollectionProvider ) ;
3439 SunsetPolicyManager = sunsetPolicyManager ;
3540 options = apiExplorerOptions ;
3641 }
@@ -51,43 +56,38 @@ public DefaultApiVersionDescriptionProvider(
5156 public IReadOnlyList < ApiVersionDescription > ApiVersionDescriptions => collection . Items ;
5257
5358 /// <summary>
54- /// Enumerates all API versions within an application.
59+ /// Provides a list of API version descriptions from a list of application API version metadata .
5560 /// </summary>
56- /// <param name="endpointDataSource">The <see cref="EndpointDataSource">data source</see> used to enumerate the endpoints within an application.</param>
61+ /// <param name="metadata">The <see cref="IReadOnlyList{T}">read-only list</see> of <see cref="ApiVersionMetadata">API version metadata</see>
62+ /// within the application.</param>
5763 /// <returns>A <see cref="IReadOnlyList{T}">read-only list</see> of <see cref="ApiVersionDescription">API version descriptions</see>.</returns>
58- protected virtual IReadOnlyList < ApiVersionDescription > EnumerateApiVersions ( EndpointDataSource endpointDataSource )
64+ protected virtual IReadOnlyList < ApiVersionDescription > Describe ( IReadOnlyList < ApiVersionMetadata > metadata )
5965 {
60- if ( endpointDataSource == null )
66+ if ( metadata == null )
6167 {
62- throw new ArgumentNullException ( nameof ( endpointDataSource ) ) ;
68+ throw new ArgumentNullException ( nameof ( metadata ) ) ;
6369 }
6470
65- var endpoints = endpointDataSource . Endpoints ;
66- var descriptions = new List < ApiVersionDescription > ( capacity : endpoints . Count ) ;
71+ var descriptions = new List < ApiVersionDescription > ( capacity : metadata . Count ) ;
6772 var supported = new HashSet < ApiVersion > ( ) ;
6873 var deprecated = new HashSet < ApiVersion > ( ) ;
6974
70- BucketizeApiVersions ( endpoints , supported , deprecated ) ;
75+ BucketizeApiVersions ( metadata , supported , deprecated ) ;
7176 AppendDescriptions ( descriptions , supported , deprecated : false ) ;
7277 AppendDescriptions ( descriptions , deprecated , deprecated : true ) ;
7378
7479 return descriptions . OrderBy ( d => d . ApiVersion ) . ToArray ( ) ;
7580 }
7681
77- private void BucketizeApiVersions ( IReadOnlyList < Endpoint > endpoints , ISet < ApiVersion > supported , ISet < ApiVersion > deprecated )
82+ private void BucketizeApiVersions ( IReadOnlyList < ApiVersionMetadata > metadata , ISet < ApiVersion > supported , ISet < ApiVersion > deprecated )
7883 {
7984 var declared = new HashSet < ApiVersion > ( ) ;
8085 var advertisedSupported = new HashSet < ApiVersion > ( ) ;
8186 var advertisedDeprecated = new HashSet < ApiVersion > ( ) ;
8287
83- for ( var i = 0 ; i < endpoints . Count ; i ++ )
88+ for ( var i = 0 ; i < metadata . Count ; i ++ )
8489 {
85- if ( endpoints [ i ] . Metadata . GetMetadata < ApiVersionMetadata > ( ) is not ApiVersionMetadata metadata )
86- {
87- continue ;
88- }
89-
90- var model = metadata . Map ( Explicit | Implicit ) ;
90+ var model = metadata [ i ] . Map ( Explicit | Implicit ) ;
9191 var versions = model . DeclaredApiVersions ;
9292
9393 for ( var j = 0 ; j < versions . Count ; j ++ )
@@ -138,47 +138,214 @@ private void AppendDescriptions( ICollection<ApiVersionDescription> descriptions
138138 private sealed class ApiVersionDescriptionCollection
139139 {
140140 private readonly object syncRoot = new ( ) ;
141- private readonly EndpointDataSource endpointDataSource ;
142141 private readonly DefaultApiVersionDescriptionProvider apiVersionDescriptionProvider ;
142+ private readonly EndpointApiVersionMetadataCollection endpoints ;
143+ private readonly ActionApiVersionMetadataCollection actions ;
143144 private IReadOnlyList < ApiVersionDescription > ? items ;
145+ private long version ;
144146
145147 public ApiVersionDescriptionCollection (
146148 DefaultApiVersionDescriptionProvider apiVersionDescriptionProvider ,
147- EndpointDataSource endpointDataSource )
149+ EndpointDataSource endpointDataSource ,
150+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider )
148151 {
149152 this . apiVersionDescriptionProvider = apiVersionDescriptionProvider ;
150- this . endpointDataSource = endpointDataSource ?? throw new ArgumentNullException ( nameof ( endpointDataSource ) ) ;
151- ChangeToken . OnChange ( endpointDataSource . GetChangeToken , UpdateItems ) ;
153+ endpoints = new ( endpointDataSource ) ;
154+ actions = new ( actionDescriptorCollectionProvider ) ;
152155 }
153156
154157 public IReadOnlyList < ApiVersionDescription > Items
155158 {
156159 get
157160 {
158- Initialize ( ) ;
159- return items ! ;
161+ if ( items is not null && version == CurrentVersion )
162+ {
163+ return items ;
164+ }
165+
166+ lock ( syncRoot )
167+ {
168+ var ( items1 , version1 ) = endpoints ;
169+ var ( items2 , version2 ) = actions ;
170+ var currentVersion = ComputeVersion ( version1 , version2 ) ;
171+
172+ if ( items is not null && version == currentVersion )
173+ {
174+ return items ;
175+ }
176+
177+ var capacity = items1 . Count + items2 . Count ;
178+ var metadata = new List < ApiVersionMetadata > ( capacity ) ;
179+
180+ for ( var i = 0 ; i < items1 . Count ; i ++ )
181+ {
182+ metadata . Add ( items1 [ i ] ) ;
183+ }
184+
185+ for ( var i = 0 ; i < items2 . Count ; i ++ )
186+ {
187+ metadata . Add ( items2 [ i ] ) ;
188+ }
189+
190+ items = apiVersionDescriptionProvider . Describe ( metadata ) ;
191+ version = currentVersion ;
192+ }
193+
194+ return items ;
195+ }
196+ }
197+
198+ private long CurrentVersion
199+ {
200+ get
201+ {
202+ lock ( syncRoot )
203+ {
204+ return ComputeVersion ( endpoints . Version , actions . Version ) ;
205+ }
160206 }
161207 }
162208
163- private void Initialize ( )
209+ private static long ComputeVersion ( int version1 , int version2 ) => ( ( ( long ) version1 ) << 32 ) | ( long ) version2 ;
210+ }
211+
212+ private sealed class EndpointApiVersionMetadataCollection
213+ {
214+ private readonly object syncRoot = new ( ) ;
215+ private readonly EndpointDataSource endpointDataSource ;
216+ private List < ApiVersionMetadata > ? items ;
217+ private int version ;
218+ private int currentVersion ;
219+
220+ public EndpointApiVersionMetadataCollection ( EndpointDataSource endpointDataSource )
221+ {
222+ this . endpointDataSource = endpointDataSource ?? throw new ArgumentNullException ( nameof ( endpointDataSource ) ) ;
223+ ChangeToken . OnChange ( endpointDataSource . GetChangeToken , IncrementVersion ) ;
224+ }
225+
226+ public int Version => version ;
227+
228+ public IReadOnlyList < ApiVersionMetadata > Items
164229 {
165- if ( items == null )
230+ get
166231 {
232+ if ( items is not null && version == currentVersion )
233+ {
234+ return items ;
235+ }
236+
167237 lock ( syncRoot )
168238 {
239+ if ( items is not null && version == currentVersion )
240+ {
241+ return items ;
242+ }
243+
244+ var endpoints = endpointDataSource . Endpoints ;
245+
169246 if ( items == null )
170247 {
171- UpdateItems ( ) ;
248+ items = new ( capacity : endpoints . Count ) ;
172249 }
250+ else
251+ {
252+ items . Clear ( ) ;
253+ items . Capacity = endpoints . Count ;
254+ }
255+
256+ for ( var i = 0 ; i < endpoints . Count ; i ++ )
257+ {
258+ if ( endpoints [ i ] . Metadata . GetMetadata < ApiVersionMetadata > ( ) is ApiVersionMetadata item )
259+ {
260+ items . Add ( item ) ;
261+ }
262+ }
263+
264+ version = currentVersion ;
173265 }
266+
267+ return items ;
268+ }
269+ }
270+
271+ public void Deconstruct ( out IReadOnlyList < ApiVersionMetadata > items , out int version )
272+ {
273+ lock ( syncRoot )
274+ {
275+ version = this . version ;
276+ items = Items ;
277+ }
278+ }
279+
280+ private void IncrementVersion ( )
281+ {
282+ lock ( syncRoot )
283+ {
284+ currentVersion ++ ;
285+ }
286+ }
287+ }
288+
289+ private sealed class ActionApiVersionMetadataCollection
290+ {
291+ private readonly object syncRoot = new ( ) ;
292+ private readonly IActionDescriptorCollectionProvider provider ;
293+ private List < ApiVersionMetadata > ? items ;
294+ private int version ;
295+
296+ public ActionApiVersionMetadataCollection ( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ) =>
297+ provider = actionDescriptorCollectionProvider ?? throw new ArgumentNullException ( nameof ( actionDescriptorCollectionProvider ) ) ;
298+
299+ public int Version => version ;
300+
301+ public IReadOnlyList < ApiVersionMetadata > Items
302+ {
303+ get
304+ {
305+ var collection = provider . ActionDescriptors ;
306+
307+ if ( items is not null && collection . Version == version )
308+ {
309+ return items ;
310+ }
311+
312+ lock ( syncRoot )
313+ {
314+ if ( items is not null && collection . Version == version )
315+ {
316+ return items ;
317+ }
318+
319+ var actions = collection . Items ;
320+
321+ if ( items == null )
322+ {
323+ items = new ( capacity : actions . Count ) ;
324+ }
325+ else
326+ {
327+ items . Clear ( ) ;
328+ items . Capacity = actions . Count ;
329+ }
330+
331+ for ( var i = 0 ; i < actions . Count ; i ++ )
332+ {
333+ items . Add ( actions [ i ] . GetApiVersionMetadata ( ) ) ;
334+ }
335+
336+ version = collection . Version ;
337+ }
338+
339+ return items ;
174340 }
175341 }
176342
177- private void UpdateItems ( )
343+ public void Deconstruct ( out IReadOnlyList < ApiVersionMetadata > items , out int version )
178344 {
179345 lock ( syncRoot )
180346 {
181- items = apiVersionDescriptionProvider . EnumerateApiVersions ( endpointDataSource ) ;
347+ version = this . version ;
348+ items = Items ;
182349 }
183350 }
184351 }
0 commit comments