@@ -79,8 +79,14 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
79
79
Type defaultErrorType )
80
80
{
81
81
var contentTypes = new MediaTypeCollection ( ) ;
82
+ var responseTypeMetadataProviders = _mvcOptions . OutputFormatters . OfType < IApiResponseTypeMetadataProvider > ( ) ;
82
83
83
- var responseTypes = ReadResponseMetadata ( responseMetadataAttributes , type , defaultErrorType , contentTypes ) ;
84
+ var responseTypes = ReadResponseMetadata (
85
+ responseMetadataAttributes ,
86
+ type ,
87
+ defaultErrorType ,
88
+ contentTypes ,
89
+ responseTypeMetadataProviders ) ;
84
90
85
91
// Set the default status only when no status has already been set explicitly
86
92
if ( responseTypes . Count == 0 && type != null )
@@ -102,7 +108,10 @@ private ICollection<ApiResponseType> GetApiResponseTypes(
102
108
contentTypes . Add ( ( string ) null ! ) ;
103
109
}
104
110
105
- CalculateResponseFormats ( responseTypes , contentTypes ) ;
111
+ foreach ( var apiResponse in responseTypes )
112
+ {
113
+ CalculateResponseFormatForType ( apiResponse , contentTypes , responseTypeMetadataProviders , _modelMetadataProvider ) ;
114
+ }
106
115
107
116
return responseTypes ;
108
117
}
@@ -112,7 +121,9 @@ internal static List<ApiResponseType> ReadResponseMetadata(
112
121
IReadOnlyList < IApiResponseMetadataProvider > responseMetadataAttributes ,
113
122
Type ? type ,
114
123
Type defaultErrorType ,
115
- MediaTypeCollection contentTypes )
124
+ MediaTypeCollection contentTypes ,
125
+ IEnumerable < IApiResponseTypeMetadataProvider > ? responseTypeMetadataProviders = null ,
126
+ IModelMetadataProvider ? _modelMetadataProvider = null )
116
127
{
117
128
var results = new Dictionary < int , ApiResponseType > ( ) ;
118
129
@@ -123,7 +134,18 @@ internal static List<ApiResponseType> ReadResponseMetadata(
123
134
{
124
135
foreach ( var metadataAttribute in responseMetadataAttributes )
125
136
{
126
- metadataAttribute . SetContentTypes ( contentTypes ) ;
137
+ // All ProducesXAttributes, except for ProducesResponseTypeAttribute do
138
+ // not allow multiple instances on the same method/class/etc. For those
139
+ // scenarios, the `SetContentTypes` method on the attribute continuously
140
+ // clears out more general content types in favor of more specific ones
141
+ // since we iterate through the attributes in order. For example, if a
142
+ // Produces exists on both a controller and an action within the controller,
143
+ // we favor the definition in the action. This is a semantic that does not
144
+ // apply to ProducesResponseType, which allows multiple instances on an target.
145
+ if ( metadataAttribute is not ProducesResponseTypeAttribute )
146
+ {
147
+ metadataAttribute . SetContentTypes ( contentTypes ) ;
148
+ }
127
149
128
150
var statusCode = metadataAttribute . StatusCode ;
129
151
@@ -157,6 +179,18 @@ internal static List<ApiResponseType> ReadResponseMetadata(
157
179
}
158
180
}
159
181
182
+ // We special case the handling of ProcuesResponseTypeAttributes since
183
+ // multiple ProducesResponseTypeAttributes are permitted on a single
184
+ // action/controller/etc. In that scenario, instead of picking the most-specific
185
+ // set of content types (like we do with the Produces attribute above) we process
186
+ // the content types for each attribute independently.
187
+ if ( metadataAttribute is ProducesResponseTypeAttribute )
188
+ {
189
+ var attributeContentTypes = new MediaTypeCollection ( ) ;
190
+ metadataAttribute . SetContentTypes ( attributeContentTypes ) ;
191
+ CalculateResponseFormatForType ( apiResponseType , attributeContentTypes , responseTypeMetadataProviders , _modelMetadataProvider ) ;
192
+ }
193
+
160
194
if ( apiResponseType . Type != null )
161
195
{
162
196
results [ apiResponseType . StatusCode ] = apiResponseType ;
@@ -167,9 +201,15 @@ internal static List<ApiResponseType> ReadResponseMetadata(
167
201
return results . Values . ToList ( ) ;
168
202
}
169
203
170
- private void CalculateResponseFormats ( ICollection < ApiResponseType > responseTypes , MediaTypeCollection declaredContentTypes )
204
+ private static void CalculateResponseFormatForType ( ApiResponseType apiResponse , MediaTypeCollection declaredContentTypes , IEnumerable < IApiResponseTypeMetadataProvider > ? responseTypeMetadataProviders , IModelMetadataProvider ? _modelMetadataProvider )
171
205
{
172
- var responseTypeMetadataProviders = _mvcOptions . OutputFormatters . OfType < IApiResponseTypeMetadataProvider > ( ) ;
206
+ // If response formats have already been calculate for this type,
207
+ // then exit early. This avoids populating the ApiResponseFormat for
208
+ // types that have already been handled, specifically ProducesResponseTypes.
209
+ if ( apiResponse . ApiResponseFormats . Count > 0 )
210
+ {
211
+ return ;
212
+ }
173
213
174
214
// Given the content-types that were declared for this action, determine the formatters that support the content-type for the given
175
215
// response type.
@@ -179,21 +219,20 @@ private void CalculateResponseFormats(ICollection<ApiResponseType> responseTypes
179
219
// 3. When no formatter supports the specified content-type, use the user specified value as is. This is useful in actions where the user
180
220
// dictates the content-type.
181
221
// e.g. [Produces("application/pdf")] Action() => FileStream("somefile.pdf", "application/pdf");
182
-
183
- foreach ( var apiResponse in responseTypes )
222
+ var responseType = apiResponse . Type ;
223
+ if ( responseType == null || responseType == typeof ( void ) )
184
224
{
185
- var responseType = apiResponse . Type ;
186
- if ( responseType == null || responseType == typeof ( void ) )
187
- {
188
- continue ;
189
- }
225
+ return ;
226
+ }
190
227
191
- apiResponse . ModelMetadata = _modelMetadataProvider . GetMetadataForType ( responseType ) ;
228
+ apiResponse . ModelMetadata = _modelMetadataProvider ? . GetMetadataForType ( responseType ) ;
192
229
193
- foreach ( var contentType in declaredContentTypes )
194
- {
195
- var isSupportedContentType = false ;
230
+ foreach ( var contentType in declaredContentTypes )
231
+ {
232
+ var isSupportedContentType = false ;
196
233
234
+ if ( responseTypeMetadataProviders != null )
235
+ {
197
236
foreach ( var responseTypeMetadataProvider in responseTypeMetadataProviders )
198
237
{
199
238
var formatterSupportedContentTypes = responseTypeMetadataProvider . GetSupportedContentTypes (
@@ -216,15 +255,17 @@ private void CalculateResponseFormats(ICollection<ApiResponseType> responseTypes
216
255
} ) ;
217
256
}
218
257
}
258
+ }
259
+
260
+
219
261
220
- if ( ! isSupportedContentType && contentType != null )
262
+ if ( ! isSupportedContentType && contentType != null )
263
+ {
264
+ // No output formatter was found that supports this content type. Add the user specified content type as-is to the result.
265
+ apiResponse . ApiResponseFormats . Add ( new ApiResponseFormat
221
266
{
222
- // No output formatter was found that supports this content type. Add the user specified content type as-is to the result.
223
- apiResponse . ApiResponseFormats . Add ( new ApiResponseFormat
224
- {
225
- MediaType = contentType ,
226
- } ) ;
227
- }
267
+ MediaType = contentType ,
268
+ } ) ;
228
269
}
229
270
}
230
271
}
0 commit comments