3
3
namespace Asp . Versioning . ApiExplorer ;
4
4
5
5
using Microsoft . AspNetCore . Http ;
6
+ using Microsoft . AspNetCore . Mvc . Abstractions ;
7
+ using Microsoft . AspNetCore . Mvc . Infrastructure ;
6
8
using Microsoft . AspNetCore . Routing ;
7
9
using Microsoft . Extensions . Options ;
8
10
using Microsoft . Extensions . Primitives ;
@@ -22,15 +24,18 @@ public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvid
22
24
/// Initializes a new instance of the <see cref="DefaultApiVersionDescriptionProvider"/> class.
23
25
/// </summary>
24
26
/// <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>
25
29
/// <param name="sunsetPolicyManager">The <see cref="ISunsetPolicyManager">manager</see> used to resolve sunset policies.</param>
26
30
/// <param name="apiExplorerOptions">The <see cref="IOptions{TOptions}">container</see> of configured
27
31
/// <see cref="ApiExplorerOptions">API explorer options</see>.</param>
28
32
public DefaultApiVersionDescriptionProvider (
29
33
EndpointDataSource endpointDataSource ,
34
+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ,
30
35
ISunsetPolicyManager sunsetPolicyManager ,
31
36
IOptions < ApiExplorerOptions > apiExplorerOptions )
32
37
{
33
- collection = new ( this , endpointDataSource ) ;
38
+ collection = new ( this , endpointDataSource , actionDescriptorCollectionProvider ) ;
34
39
SunsetPolicyManager = sunsetPolicyManager ;
35
40
options = apiExplorerOptions ;
36
41
}
@@ -51,43 +56,38 @@ public DefaultApiVersionDescriptionProvider(
51
56
public IReadOnlyList < ApiVersionDescription > ApiVersionDescriptions => collection . Items ;
52
57
53
58
/// <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 .
55
60
/// </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>
57
63
/// <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 )
59
65
{
60
- if ( endpointDataSource == null )
66
+ if ( metadata == null )
61
67
{
62
- throw new ArgumentNullException ( nameof ( endpointDataSource ) ) ;
68
+ throw new ArgumentNullException ( nameof ( metadata ) ) ;
63
69
}
64
70
65
- var endpoints = endpointDataSource . Endpoints ;
66
- var descriptions = new List < ApiVersionDescription > ( capacity : endpoints . Count ) ;
71
+ var descriptions = new List < ApiVersionDescription > ( capacity : metadata . Count ) ;
67
72
var supported = new HashSet < ApiVersion > ( ) ;
68
73
var deprecated = new HashSet < ApiVersion > ( ) ;
69
74
70
- BucketizeApiVersions ( endpoints , supported , deprecated ) ;
75
+ BucketizeApiVersions ( metadata , supported , deprecated ) ;
71
76
AppendDescriptions ( descriptions , supported , deprecated : false ) ;
72
77
AppendDescriptions ( descriptions , deprecated , deprecated : true ) ;
73
78
74
79
return descriptions . OrderBy ( d => d . ApiVersion ) . ToArray ( ) ;
75
80
}
76
81
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 )
78
83
{
79
84
var declared = new HashSet < ApiVersion > ( ) ;
80
85
var advertisedSupported = new HashSet < ApiVersion > ( ) ;
81
86
var advertisedDeprecated = new HashSet < ApiVersion > ( ) ;
82
87
83
- for ( var i = 0 ; i < endpoints . Count ; i ++ )
88
+ for ( var i = 0 ; i < metadata . Count ; i ++ )
84
89
{
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 ) ;
91
91
var versions = model . DeclaredApiVersions ;
92
92
93
93
for ( var j = 0 ; j < versions . Count ; j ++ )
@@ -138,47 +138,214 @@ private void AppendDescriptions( ICollection<ApiVersionDescription> descriptions
138
138
private sealed class ApiVersionDescriptionCollection
139
139
{
140
140
private readonly object syncRoot = new ( ) ;
141
- private readonly EndpointDataSource endpointDataSource ;
142
141
private readonly DefaultApiVersionDescriptionProvider apiVersionDescriptionProvider ;
142
+ private readonly EndpointApiVersionMetadataCollection endpoints ;
143
+ private readonly ActionApiVersionMetadataCollection actions ;
143
144
private IReadOnlyList < ApiVersionDescription > ? items ;
145
+ private long version ;
144
146
145
147
public ApiVersionDescriptionCollection (
146
148
DefaultApiVersionDescriptionProvider apiVersionDescriptionProvider ,
147
- EndpointDataSource endpointDataSource )
149
+ EndpointDataSource endpointDataSource ,
150
+ IActionDescriptorCollectionProvider actionDescriptorCollectionProvider )
148
151
{
149
152
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 ) ;
152
155
}
153
156
154
157
public IReadOnlyList < ApiVersionDescription > Items
155
158
{
156
159
get
157
160
{
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
+ }
160
206
}
161
207
}
162
208
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
164
229
{
165
- if ( items == null )
230
+ get
166
231
{
232
+ if ( items is not null && version == currentVersion )
233
+ {
234
+ return items ;
235
+ }
236
+
167
237
lock ( syncRoot )
168
238
{
239
+ if ( items is not null && version == currentVersion )
240
+ {
241
+ return items ;
242
+ }
243
+
244
+ var endpoints = endpointDataSource . Endpoints ;
245
+
169
246
if ( items == null )
170
247
{
171
- UpdateItems ( ) ;
248
+ items = new ( capacity : endpoints . Count ) ;
172
249
}
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 ;
173
265
}
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 ;
174
340
}
175
341
}
176
342
177
- private void UpdateItems ( )
343
+ public void Deconstruct ( out IReadOnlyList < ApiVersionMetadata > items , out int version )
178
344
{
179
345
lock ( syncRoot )
180
346
{
181
- items = apiVersionDescriptionProvider . EnumerateApiVersions ( endpointDataSource ) ;
347
+ version = this . version ;
348
+ items = Items ;
182
349
}
183
350
}
184
351
}
0 commit comments