@@ -8,6 +8,7 @@ namespace Asp.Versioning.ApiExplorer;
8
8
using Microsoft . AspNetCore . Mvc . ModelBinding ;
9
9
using Microsoft . AspNetCore . Routing ;
10
10
using Microsoft . AspNetCore . Routing . Patterns ;
11
+ using Microsoft . AspNetCore . Routing . Template ;
11
12
using static Asp . Versioning . ApiVersionParameterLocation ;
12
13
using static System . Linq . Enumerable ;
13
14
using static System . StringComparison ;
@@ -43,6 +44,11 @@ public ApiVersionParameterDescriptionContext(
43
44
optional = options . AssumeDefaultVersionWhenUnspecified && apiVersion == options . DefaultApiVersion ;
44
45
}
45
46
47
+ // intentionally an internal property so the public contract doesn't change. this will be removed
48
+ // once the ASP.NET Core team fixes the bug
49
+ // BUG: https://github.com/dotnet/aspnetcore/issues/41773
50
+ internal IInlineConstraintResolver ? ConstraintResolver { get ; set ; }
51
+
46
52
/// <summary>
47
53
/// Gets the associated API description.
48
54
/// </summary>
@@ -160,7 +166,8 @@ protected virtual void AddHeader( string name )
160
166
protected virtual void UpdateUrlSegment ( )
161
167
{
162
168
var parameter = FindByRouteConstraintType ( ApiDescription ) ??
163
- FindByRouteConstraintName ( ApiDescription , Options . RouteConstraintName ) ;
169
+ FindByRouteConstraintName ( ApiDescription , Options . RouteConstraintName ) ??
170
+ TryCreateFromRouteTemplate ( ApiDescription , ConstraintResolver ) ;
164
171
165
172
if ( parameter == null )
166
173
{
@@ -309,6 +316,73 @@ routeInfo.Constraints is IEnumerable<IRouteConstraint> constraints &&
309
316
return default ;
310
317
}
311
318
319
+ private static ApiParameterDescription ? TryCreateFromRouteTemplate ( ApiDescription description , IInlineConstraintResolver ? constraintResolver )
320
+ {
321
+ if ( constraintResolver == null )
322
+ {
323
+ return default ;
324
+ }
325
+
326
+ var relativePath = description . RelativePath ;
327
+
328
+ if ( string . IsNullOrEmpty ( relativePath ) )
329
+ {
330
+ return default ;
331
+ }
332
+
333
+ var constraints = new List < IRouteConstraint > ( ) ;
334
+ var template = TemplateParser . Parse ( relativePath ) ;
335
+ var constraintName = default ( string ) ;
336
+
337
+ for ( var i = 0 ; i < template . Parameters . Count ; i ++ )
338
+ {
339
+ var match = false ;
340
+ var parameter = template . Parameters [ i ] ;
341
+
342
+ foreach ( var inlineConstraint in parameter . InlineConstraints )
343
+ {
344
+ var constraint = constraintResolver . ResolveConstraint ( inlineConstraint . Constraint ) ! ;
345
+
346
+ constraints . Add ( constraint ) ;
347
+
348
+ if ( constraint is ApiVersionRouteConstraint )
349
+ {
350
+ match = true ;
351
+ constraintName = inlineConstraint . Constraint ;
352
+ }
353
+ }
354
+
355
+ if ( ! match )
356
+ {
357
+ continue ;
358
+ }
359
+
360
+ constraints . TrimExcess ( ) ;
361
+
362
+ // ASP.NET Core does not discover route parameters without using Reflection in 6.0. unclear if it will be fixed before 7.0
363
+ // BUG: https://github.com/dotnet/aspnetcore/issues/41773
364
+ // REF: https://github.com/dotnet/aspnetcore/blob/release/6.0/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs#L323
365
+ var result = new ApiParameterDescription ( )
366
+ {
367
+ Name = parameter . Name ! ,
368
+ RouteInfo = new ( )
369
+ {
370
+ Constraints = constraints ,
371
+ DefaultValue = parameter . DefaultValue ,
372
+ IsOptional = parameter . IsOptional || parameter . DefaultValue != null ,
373
+ } ,
374
+ Source = BindingSource . Path ,
375
+ } ;
376
+ var token = $ "{ parameter . Name } :{ constraintName } ";
377
+
378
+ description . RelativePath = relativePath . Replace ( token , parameter . Name , Ordinal ) ;
379
+ description . ParameterDescriptions . Insert ( 0 , result ) ;
380
+ return result ;
381
+ }
382
+
383
+ return default ;
384
+ }
385
+
312
386
private ApiParameterDescription NewApiVersionParameter ( string name , BindingSource source )
313
387
{
314
388
var parameter = new ApiParameterDescription ( )
0 commit comments