@@ -403,16 +403,6 @@ private async Task<OpenApiResponse> GetResponseAsync(
403
403
continue ;
404
404
}
405
405
406
- // MVC's ModelMetadata layer will set ApiParameterDescription.Type to string when the parameter
407
- // is a parsable or convertible type. In this case, we want to use the actual model type
408
- // to generate the schema instead of the string type.
409
- var targetType = parameter . Type == typeof ( string ) && parameter . ModelMetadata . ModelType != parameter . Type
410
- ? parameter . ModelMetadata . ModelType
411
- : parameter . Type ;
412
- // If the type is null, then we're dealing with an inert
413
- // route parameter that does not define a specific parameter type in the
414
- // route handler or in the response. In this case, we default to a string schema.
415
- targetType ??= typeof ( string ) ;
416
406
var openApiParameter = new OpenApiParameter
417
407
{
418
408
Name = parameter . Name ,
@@ -424,7 +414,7 @@ private async Task<OpenApiResponse> GetResponseAsync(
424
414
_ => throw new InvalidOperationException ( $ "Unsupported parameter source: { parameter . Source . Id } ")
425
415
} ,
426
416
Required = IsRequired ( parameter ) ,
427
- Schema = await _componentService . GetOrCreateSchemaAsync ( targetType , scopedServiceProvider , schemaTransformers , parameter , cancellationToken : cancellationToken ) ,
417
+ Schema = await _componentService . GetOrCreateSchemaAsync ( GetTargetType ( description , parameter ) , scopedServiceProvider , schemaTransformers , parameter , cancellationToken : cancellationToken ) ,
428
418
Description = GetParameterDescriptionFromAttribute ( parameter )
429
419
} ;
430
420
@@ -675,4 +665,41 @@ private async Task<OpenApiRequestBody> GetJsonRequestBody(
675
665
676
666
return requestBody ;
677
667
}
668
+
669
+ /// <remarks>
670
+ /// This method is used to determine the target type for a given parameter. The target type
671
+ /// is the actual type that should be used to generate the schema for the parameter. This is
672
+ /// necessary because MVC's ModelMetadata layer will set ApiParameterDescription.Type to string
673
+ /// when the parameter is a parsable or convertible type. In this case, we want to use the actual
674
+ /// model type to generate the schema instead of the string type.
675
+ /// </remarks>
676
+ /// <remarks>
677
+ /// This method will also check if no target type was resolved from the <see cref="ApiParameterDescription"/>
678
+ /// and default to a string schema. This will happen if we are dealing with an inert route parameter
679
+ /// that does not define a specific parameter type in the route handler or in the response.
680
+ /// </remarks>
681
+ private static Type GetTargetType ( ApiDescription description , ApiParameterDescription parameter )
682
+ {
683
+ var bindingMetadata = description . ActionDescriptor . EndpointMetadata
684
+ . OfType < IParameterBindingMetadata > ( )
685
+ . SingleOrDefault ( metadata => metadata . Name == parameter . Name ) ;
686
+ var parameterType = parameter . Type is not null
687
+ ? Nullable . GetUnderlyingType ( parameter . Type ) ?? parameter . Type
688
+ : parameter . Type ;
689
+
690
+ // parameter.Type = typeof(string)
691
+ // parameter.ModelMetadata.Type = typeof(TEnum)
692
+ var requiresModelMetadataFallbackForEnum = parameterType == typeof ( string )
693
+ && parameter . ModelMetadata . ModelType != parameter . Type
694
+ && parameter . ModelMetadata . ModelType . IsEnum ;
695
+ // Enums are exempt because we want to set the OpenApiSchema.Enum field when feasible.
696
+ // parameter.Type = typeof(TEnum), typeof(TypeWithTryParse)
697
+ // parameter.ModelMetadata.Type = typeof(string)
698
+ var hasTryParse = bindingMetadata ? . HasTryParse == true && parameterType is not null && ! parameterType . IsEnum ;
699
+ var targetType = requiresModelMetadataFallbackForEnum || hasTryParse
700
+ ? parameter . ModelMetadata . ModelType
701
+ : parameter . Type ;
702
+ targetType ??= typeof ( string ) ;
703
+ return targetType ;
704
+ }
678
705
}
0 commit comments