4
4
using System . Collections . Concurrent ;
5
5
using System . Diagnostics ;
6
6
using System . Diagnostics . CodeAnalysis ;
7
+ using System . Globalization ;
7
8
using System . Linq ;
9
+ using Microsoft . AspNetCore . Http ;
8
10
using Microsoft . AspNetCore . Http . Metadata ;
11
+ using Microsoft . AspNetCore . Mvc ;
9
12
using Microsoft . AspNetCore . Mvc . ApiExplorer ;
10
13
using Microsoft . AspNetCore . Mvc . ModelBinding ;
14
+ using Microsoft . AspNetCore . WebUtilities ;
11
15
using Microsoft . Extensions . DependencyInjection ;
12
16
using Microsoft . Extensions . Hosting ;
13
17
using Microsoft . Extensions . Options ;
@@ -32,6 +36,7 @@ internal sealed class OpenApiDocumentService(
32
36
/// operations, API descriptions, and their respective transformer contexts.
33
37
/// </summary>
34
38
private readonly ConcurrentDictionary < string , OpenApiOperationTransformerContext > _operationTransformerContextCache = new ( ) ;
39
+ private static readonly ApiResponseType _defaultApiResponseType = new ApiResponseType { StatusCode = StatusCodes . Status200OK } ;
35
40
36
41
internal bool TryGetCachedOperationTransformerContext ( string descriptionId , [ NotNullWhen ( true ) ] out OpenApiOperationTransformerContext ? context )
37
42
=> _operationTransformerContextCache . TryGetValue ( descriptionId , out context ) ;
@@ -133,6 +138,7 @@ private static OpenApiOperation GetOperation(ApiDescription description, HashSet
133
138
{
134
139
Summary = GetSummary ( description ) ,
135
140
Description = GetDescription ( description ) ,
141
+ Responses = GetResponses ( description ) ,
136
142
Parameters = GetParameters ( description ) ,
137
143
Tags = tags ,
138
144
} ;
@@ -157,6 +163,68 @@ private static OpenApiOperation GetOperation(ApiDescription description, HashSet
157
163
return [ new OpenApiTag { Name = description . ActionDescriptor . RouteValues [ "controller" ] } ] ;
158
164
}
159
165
166
+ private static OpenApiResponses GetResponses ( ApiDescription description )
167
+ {
168
+ // OpenAPI requires that each operation have a response, usually a successful one.
169
+ // if there are no response types defined, we assume a successful 200 OK response
170
+ // with no content by default.
171
+ if ( description . SupportedResponseTypes . Count == 0 )
172
+ {
173
+ return new OpenApiResponses
174
+ {
175
+ [ "200" ] = GetResponse ( description , StatusCodes . Status200OK , _defaultApiResponseType )
176
+ } ;
177
+ }
178
+
179
+ var responses = new OpenApiResponses ( ) ;
180
+ foreach ( var responseType in description . SupportedResponseTypes )
181
+ {
182
+ // The "default" response type is a special case in OpenAPI used to describe
183
+ // the response for all HTTP status codes that are not explicitly defined
184
+ // for a given operation. This is typically used to describe catch-all scenarios
185
+ // like error responses.
186
+ var responseKey = responseType . IsDefaultResponse
187
+ ? OpenApiConstants . DefaultOpenApiResponseKey
188
+ : responseType . StatusCode . ToString ( CultureInfo . InvariantCulture ) ;
189
+ responses . Add ( responseKey , GetResponse ( description , responseType . StatusCode , responseType ) ) ;
190
+ }
191
+ return responses ;
192
+ }
193
+
194
+ private static OpenApiResponse GetResponse ( ApiDescription apiDescription , int statusCode , ApiResponseType apiResponseType )
195
+ {
196
+ var description = ReasonPhrases . GetReasonPhrase ( statusCode ) ;
197
+ var response = new OpenApiResponse
198
+ {
199
+ Description = description ,
200
+ Content = new Dictionary < string , OpenApiMediaType > ( )
201
+ } ;
202
+
203
+ // ApiResponseFormats aggregates information about the supported response content types
204
+ // from different types of Produces metadata. This is handled by ApiExplorer so looking
205
+ // up values in ApiResponseFormats should provide us a complete set of the information
206
+ // encoded in Produces metadata added via attributes or extension methods.
207
+ var apiResponseFormatContentTypes = apiResponseType . ApiResponseFormats
208
+ . Select ( responseFormat => responseFormat . MediaType ) ;
209
+ foreach ( var contentType in apiResponseFormatContentTypes )
210
+ {
211
+ response . Content [ contentType ] = new OpenApiMediaType ( ) ;
212
+ }
213
+
214
+ // MVC's `ProducesAttribute` doesn't implement the produces metadata that the ApiExplorer
215
+ // looks for when generating ApiResponseFormats above so we need to pull the content
216
+ // types defined there separately.
217
+ var explicitContentTypes = apiDescription . ActionDescriptor . EndpointMetadata
218
+ . OfType < ProducesAttribute > ( )
219
+ . SelectMany ( attr => attr . ContentTypes ) ;
220
+ foreach ( var contentType in explicitContentTypes )
221
+ {
222
+ response . Content [ contentType ] = new OpenApiMediaType ( ) ;
223
+ }
224
+
225
+ return response ;
226
+ }
227
+
160
228
private static List < OpenApiParameter > ? GetParameters ( ApiDescription description )
161
229
{
162
230
List < OpenApiParameter > ? parameters = null ;
0 commit comments