@@ -403,12 +403,6 @@ private async Task<OpenApiResponse> GetResponseAsync(
403403 continue ;
404404 }
405405
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 ;
412406 var openApiParameter = new OpenApiParameter
413407 {
414408 Name = parameter . Name ,
@@ -420,7 +414,7 @@ private async Task<OpenApiResponse> GetResponseAsync(
420414 _ => throw new InvalidOperationException ( $ "Unsupported parameter source: { parameter . Source . Id } ")
421415 } ,
422416 Required = IsRequired ( parameter ) ,
423- Schema = await _componentService . GetOrCreateSchemaAsync ( targetType , scopedServiceProvider , schemaTransformers , parameter , cancellationToken : cancellationToken ) ,
417+ Schema = await _componentService . GetOrCreateSchemaAsync ( GetTargetType ( description , parameter ) , scopedServiceProvider , schemaTransformers , parameter , cancellationToken : cancellationToken ) ,
424418 Description = GetParameterDescriptionFromAttribute ( parameter )
425419 } ;
426420
@@ -671,4 +665,41 @@ private async Task<OpenApiRequestBody> GetJsonRequestBody(
671665
672666 return requestBody ;
673667 }
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+ }
674705}
0 commit comments