Skip to content

OpenAPI: merge multiple body content types into one operation #61401

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ascott18
Copy link
Contributor

@ascott18 ascott18 commented Apr 8, 2025

OpenAPI: merge multiple body content types for the same path/method

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Description

This change merges multiple body content types into one operation definition in the OpenAPI document when there are multiple endpoints with the same path and HTTP method but different Consumes attributes.

The previous behavior would clobber existing operations in this scenario such that the last defined operation wins.

Fixes #58329

@ascott18 ascott18 requested review from captainsafia and a team as code owners April 8, 2025 22:00
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Apr 8, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Apr 8, 2025
Copy link
Member

@captainsafia captainsafia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Comment left inline.

@@ -295,6 +294,24 @@ private async Task<Dictionary<OperationType, OpenApiOperation>> GetOperationsAsy
{
await endpointOperationTransformer.TransformAsync(operation, operationContext, cancellationToken);
}

var operationType = description.GetOperationType();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic looks great overall! However, I believe we want to apply this before operation transformers are applied so that transformers registered by the user get the correct view.

I'm reviewing this on mobile so if this is already the case and I missed it let me know.

Also, we might want to add a test case to verify that transformers see the correct data.

@ascott18
Copy link
Contributor Author

ascott18 commented Apr 9, 2025

@captainsafia Thank you for the review. I made some adjustments to better support operation transformers.

Your comments made me realize that with my original changes, the transformer would run multiple times for a given operation, once per ApiDescription. I've adjusted it to perform grouping and merging before the transformers runs for the operation. I added a property AllDescriptions to the transformer context so that the transformer implementation can receive all descriptions that were merged together. The existing Description property on the context is unchanged so that this isn't a breaking change - it holds the first ApiDescription in the group.

This should eliminate any possible breakage caused by this change - I'd imagine there are users whose operation transformers are not expecting to be run multiple times on one operation.

I had to make a slight adjustment to XmlController so that Get1 is now a distinct route from Get, since these two actions are testing different parts of the XML comment logic. Since the logic is now that first wins instead of last wins, there was a change to the snapshots.

My gut is that this change from last-wins to first-wins isn't a major problem in terms of breaking changes, but if it is a problem, I can change the merging logic to preserve the old behavior where the last declared endpoint is the primary source of information for a give path + method combination, instead of the first declared endpoint.

@mikekistler
Copy link
Contributor

I thought that having multiple endpoints with the same route and verb triggered analyzer warnings. Is that not the case or are these being suppressed for this use case?

Also, I want to note ... in the related issue there is this statement:

A single route can indeed support both formdata and json bodies.
That may be true, and there is a way to express this in OpenAPI v3.x, but OpenAPI v2 forbids this:
Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.

I'm curious how this change will affect the generated OpenAPI if the app specifies that it should be OpenAPI v2.

@ascott18
Copy link
Contributor Author

ascott18 commented Apr 9, 2025

I'll investigate the impact on v2 - thanks for identifying that.

The analyzer doesn't trigger in this case because ConsumesAttribute is not one of the listed attributes that doesn't affect route matching (rightfully so, because it does affect route matching by its implementation of IActionConstraint.)

private static readonly WellKnownType[] KnownMethodAttributeTypes = new[]

@ascott18
Copy link
Contributor Author

It looks like for v2, the following happens:

  • If one of the bodies is form data, then it always wins, regardless of declared/discovered order of endpoints. No in: body parameters are emitted.
  • If both of the bodies are from body but have different types (i.e. XML and JSON, with different CLR body types), the first declared endpoint wins, and the second one is ignored.

So, it at least seems that Microsoft.OpenApi already handles this as elegantly as it can within the constraints of what OpenAPI v2 allows. It doesn't throw exceptions or produce invalid output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

.NET 9 OpenAPI doesn't support [Consumes] multiple content types correctly.
3 participants