@@ -13,6 +13,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer;
13
13
14
14
internal sealed class ApiResponseTypeProvider
15
15
{
16
+ internal readonly record struct ResponseKey (
17
+ int StatusCode ,
18
+ Type ? DeclaredType ,
19
+ string ? ContentType ) ;
20
+
16
21
private readonly IModelMetadataProvider _modelMetadataProvider ;
17
22
private readonly IActionResultTypeMapper _mapper ;
18
23
private readonly MvcOptions _mvcOptions ;
@@ -87,9 +92,7 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
87
92
responseTypeMetadataProviders ,
88
93
_modelMetadataProvider ) ;
89
94
90
- // Read response metadata from providers and
91
- // overwrite responseTypes from the metadata based
92
- // on the status code
95
+ // Read response metadata from providers
93
96
var responseTypesFromProvider = ReadResponseMetadata (
94
97
responseMetadataAttributes ,
95
98
type ,
@@ -98,6 +101,7 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
98
101
out var _ ,
99
102
responseTypeMetadataProviders ) ;
100
103
104
+ // Merge the response types
101
105
foreach ( var responseType in responseTypesFromProvider )
102
106
{
103
107
responseTypes [ responseType . Key ] = responseType . Value ;
@@ -106,7 +110,11 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
106
110
// Set the default status only when no status has already been set explicitly
107
111
if ( responseTypes . Count == 0 && type != null )
108
112
{
109
- responseTypes . Add ( StatusCodes . Status200OK , new ApiResponseType
113
+ var key = new ResponseKey (
114
+ StatusCodes . Status200OK ,
115
+ type ,
116
+ null ) ;
117
+ responseTypes . Add ( key , new ApiResponseType
110
118
{
111
119
StatusCode = StatusCodes . Status200OK ,
112
120
Type = type ,
@@ -128,11 +136,16 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
128
136
CalculateResponseFormatForType ( apiResponse , contentTypes , responseTypeMetadataProviders , _modelMetadataProvider ) ;
129
137
}
130
138
131
- return responseTypes . Values ;
139
+ // Order the response types by status code, type name, and content type for consistent output
140
+ return responseTypes . Values
141
+ . OrderBy ( r => r . StatusCode )
142
+ . ThenBy ( r => r . Type ? . Name )
143
+ . ThenBy ( r => r . ApiResponseFormats . FirstOrDefault ( ) ? . MediaType )
144
+ . ToList ( ) ;
132
145
}
133
146
134
147
// Shared with EndpointMetadataApiDescriptionProvider
135
- internal static Dictionary < int , ApiResponseType > ReadResponseMetadata (
148
+ internal static Dictionary < ResponseKey , ApiResponseType > ReadResponseMetadata (
136
149
IReadOnlyList < IApiResponseMetadataProvider > responseMetadataAttributes ,
137
150
Type ? type ,
138
151
Type ? defaultErrorType ,
@@ -142,7 +155,7 @@ internal static Dictionary<int, ApiResponseType> ReadResponseMetadata(
142
155
IModelMetadataProvider ? modelMetadataProvider = null )
143
156
{
144
157
errorSetByDefault = false ;
145
- var results = new Dictionary < int , ApiResponseType > ( ) ;
158
+ var results = new Dictionary < ResponseKey , ApiResponseType > ( ) ;
146
159
147
160
// Get the content type that the action explicitly set to support.
148
161
// Walk through all 'filter' attributes in order, and allow each one to see or override
@@ -213,21 +226,27 @@ internal static Dictionary<int, ApiResponseType> ReadResponseMetadata(
213
226
214
227
if ( apiResponseType . Type != null )
215
228
{
216
- results [ apiResponseType . StatusCode ] = apiResponseType ;
229
+ var mediaType = apiResponseType . ApiResponseFormats . FirstOrDefault ( ) ? . MediaType ;
230
+ var key = new ResponseKey (
231
+ apiResponseType . StatusCode ,
232
+ apiResponseType . Type ,
233
+ mediaType ) ;
234
+
235
+ results [ key ] = apiResponseType ;
217
236
}
218
237
}
219
238
}
220
239
221
240
return results ;
222
241
}
223
242
224
- internal static Dictionary < int , ApiResponseType > ReadResponseMetadata (
243
+ internal static Dictionary < ResponseKey , ApiResponseType > ReadResponseMetadata (
225
244
IReadOnlyList < IProducesResponseTypeMetadata > responseMetadata ,
226
245
Type ? type ,
227
246
IEnumerable < IApiResponseTypeMetadataProvider > ? responseTypeMetadataProviders = null ,
228
247
IModelMetadataProvider ? modelMetadataProvider = null )
229
248
{
230
- var results = new Dictionary < int , ApiResponseType > ( ) ;
249
+ var results = new Dictionary < ResponseKey , ApiResponseType > ( ) ;
231
250
232
251
foreach ( var metadata in responseMetadata )
233
252
{
@@ -269,7 +288,12 @@ internal static Dictionary<int, ApiResponseType> ReadResponseMetadata(
269
288
270
289
if ( apiResponseType . Type != null )
271
290
{
272
- results [ apiResponseType . StatusCode ] = apiResponseType ;
291
+ var mediaType = apiResponseType . ApiResponseFormats . FirstOrDefault ( ) ? . MediaType ;
292
+ var key = new ResponseKey (
293
+ apiResponseType . StatusCode ,
294
+ apiResponseType . Type ,
295
+ mediaType ) ;
296
+ results [ key ] = apiResponseType ;
273
297
}
274
298
}
275
299
0 commit comments