-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setting the Description in a ProducesResponseTypeAttribute works correctly for Minimal API #60539
base: main
Are you sure you want to change the base?
Setting the Description in a ProducesResponseTypeAttribute works correctly for Minimal API #60539
Conversation
Thanks for your PR, @sander1095. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
@@ -375,6 +375,9 @@ private static void AddSupportedResponseTypes( | |||
apiResponseType.ApiResponseFormats.Add(defaultResponseFormat); | |||
} | |||
|
|||
// We set the Description to the first non-null value we find that matches the status code. | |||
apiResponseType.Description ??= responseMetadataTypes.FirstOrDefault(x => x.StatusCode == apiResponseType.StatusCode && x.Description is not null)?.Description; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason the existing logic in the ApiResponseTypeProvider doesn't handle this? That feels like the right place to do this instead of having to filter on the types returned by it to set the description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @captainsafia ! Great question! We need to discuss how we want to approach this.
Short answer: The ApiRespnseTypeProvider
does set the description, but the EndpointMetadataProvider
throws it away. I found out about this specific behavior thanks to your comment. Below is the explanation and some suggestions on ways we could perhaps make the code a bit more clear:
I dove a bit deeper into this, and this is what happens:
The code above your comment does this (AddSupportedResponseTypes()
in EndpointMetadataApiDescriptionProvider.cs
)
var responseProviderMetadataTypes = ApiResponseTypeProvider.ReadResponseMetadata(
responseProviderMetadata, responseType, defaultErrorType, contentTypes, out var errorSetByDefault);
var producesResponseMetadataTypes = ApiResponseTypeProvider.ReadResponseMetadata(producesResponseMetadata, responseType);
responseProviderMetadataTypes
contains the Description set inApiResponseTypeProvider
, as you correctly said!producesResponseMetadataTypes
does not contain the Description.
Next, the following happens:
// We favor types added via the extension methods (which implements IProducesResponseTypeMetadata)
// over those that are added via attributes.
var responseMetadataTypes = producesResponseMetadataTypes.Values.Concat(responseProviderMetadataTypes.Values);
if (responseMetadataTypes.Any())
{
foreach (var apiResponseType in responseMetadataTypes)
{
// Code you commented on happens here
if (!supportedResponseTypes.Any(existingResponseType => existingResponseType.StatusCode == apiResponseType.StatusCode))
{
supportedResponseTypes.Add(apiResponseType);
}
}
}
SupportedResponseTypes
is eventually returned and used.
As you can see, both lists are combined with producesResponseMetadataTypes
being iterated over first, and thus that's the first apiResponseType
that's added to the supportedResponseTypes
, causing it to not contain a description.
At some point we loop over the responseProviderMetadataTypes
which DO contain the description, but that doesn't get added to the list of supportedResponseTypes
because that type already exists thanks to the metadata.
My code forces the Description to be set when we loop over the first iteration of the metadata, which fixes the issue. But this is a bit confusing, now that I understand the code a bit better.
What would you like as a solution? The core problem is that metadata is iterated over first, which doesn't contain the description. It then fails to enrich the response types from ApiDescriptionProvider
which supplies the description.
IProducesResponseTypeMetadata
should also have a Description.- On line 330 we call
endpointMetadata.GetOrderedMetadata<IProducesResponseTypeMetadata>();
. This has aDescription
property, but isnull
. I'm not sure if this is intended, or if I forgot to set the Description for metadata in a previous PR. (Please advise if this is desirable/possible). - If it is desired that
IProducesResponseTypeMetadata
has aDescription
, I will need to make that happen AND update line 247 inApiDescriptionProvider
to set the description.
- On line 330 we call
- Setting description once
- I can keep the code the same, so it uses the first non-null description
- I could change my code to use the LAST description it encounters in the list, in case multiple descriptions are set
- Keep code the same, add comments to explain it
- Move the code that sets the description out of the loop and populate the Descriptions after the loop, based on the data from
responseProviderMetadataTypes
. This makes the code more explicit. - Other thoughts are welcome!
This also explains why this bug happens in Minimal API's and not in Controllers. I believe EndpointMetadataApiDescriptionProvider
is only used for Minimal API, whereas ApiDescriptionProvider
is used for both Controllers and Minimal API. Because the EndpointMetadataApiDescriptionProvider
forgets to set enrich response types it has already added to the list, the description goes missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sander1095 Thanks for digging into this and sharing detailed notes. Based on this analysis, I expect this option:
I could change my code to use the LAST description it encounters in the list, in case multiple descriptions are set
To be the prefered one since it aligns most closely with the way metadata gets respected in other scenarios. We'll have to sanity check this implementation with multiple ProducesResponseType
attributes on the same endpoint to make sure the additions only apply to the target type and status code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@captainsafia Done! See the latest commits
Setting the Description in a ProducesResponseTypeAttribute works correctly for Minimal API
Using
ProducesResponseType
with theDescription
property works with Minimal APIDescription
This PR fixes the issue where the description is not set in a Minimal API application:
This did work for Controllers AND for Minimal API's when the method body was empty. The tests sadly did not cover scenarios where the method body did have content.
This PR adds test cases in several places to check that the Description is added correctly to the OpenAPI document. It also fixes the underlying issues.
Please perform a careful review of the tests in the ApiExlorer project! I made some changes and am not 100% sure that the tests are 100% valid and represent real scenarios.
Fixes #60518