Skip to content
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

Error "InvalidOperationException: Sequence contains more than one matching element" in Swagger when using multiple [FromForm] parameters in Minimal API #53831

Closed
1 task done
marcominerva opened this issue Feb 6, 2024 · 16 comments
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi
Milestone

Comments

@marcominerva
Copy link
Contributor

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I have the following Minimal API endpoint:

app.MapPost("/api/people-minimalapi", ([FromForm] Person person, [FromForm] Address address)
    => TypedResults.NoContent())
.WithOpenApi();

public record class Person(string FirstName, string LastName);

public record class Address(string Street, string City, string State, string ZipCode);

When I launch the application, I get the following Exception related to Swagger:

InvalidOperationException: Sequence contains more than one matching element
System.Linq.ThrowHelper.ThrowMoreThanOneMatchException()
System.Linq.Enumerable.TryGetSingle<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate, out bool found)
System.Linq.Enumerable.SingleOrDefault<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOpenApiOperationFromMetadata(ApiDescription apiDescription, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable<ApiDescription> apiDescriptions, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable<ApiDescription> apiDescriptions, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(string documentName, string host, string basePath)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(string documentName, string host, string basePath)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

If instead I use a Controller like this:

[ApiController]
[Route("api/[controller]")]
public class PeopleController : ControllerBase
{
    [HttpPost]
    public IActionResult Save([FromForm] Person person, [FromForm] Address address) => NoContent();
}

Everything works as expected.

Expected Behavior

Swagger should not throw the exception.

Steps To Reproduce

Minimal repro here: https://github.com/marcominerva/FromFormBindingIssue

Exceptions (if any)

InvalidOperationException: Sequence contains more than one matching element
System.Linq.ThrowHelper.ThrowMoreThanOneMatchException()
System.Linq.Enumerable.TryGetSingle(IEnumerable source, Func<TSource, bool> predicate, out bool found)
System.Linq.Enumerable.SingleOrDefault(IEnumerable source, Func<TSource, bool> predicate)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOpenApiOperationFromMetadata(ApiDescription apiDescription, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable apiDescriptions, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable apiDescriptions, SchemaRepository schemaRepository)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(string documentName, string host, string basePath)
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(string documentName, string host, string basePath)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

8.0.101

Anything else?

No response

@captainsafia captainsafia added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-web-frameworks labels Feb 6, 2024
@captainsafia
Copy link
Member

@marcominerva Thanks for reporting this issue!

I believe the issue is coming from the Microsoft.AspNetCore.OpenAPI <-> Swashbuckle integration layer. In particular here:

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/8f363f7359cb1cb8fa5de5195ec6d97aefaa16b3/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L266

I don't have immediate thoughts on the best way to workaround this, at the moment.

@marcominerva
Copy link
Contributor Author

Thank you @captainsafia for the reply!

So, now I have another question. The last update in the https://github.com/domaindrivendev/Swashbuckle.AspNetCore repository happened on January 2023. Reading this issue: domaindrivendev/Swashbuckle.AspNetCore#2759, what should we expect for the future? I mean, if the situation remain the same, will Swagger integration be removed from the .NET Web API template? And there will be a recommended alternative?

@Havunen
Copy link

Havunen commented Feb 13, 2024

Have you tested if DotSwashbuckle produces the same error? https://github.com/Havunen/DotSwashbuckle

@vdevc
Copy link

vdevc commented Mar 7, 2024

The exact same behaviour happens, in minimal api endpoints, if using header (like, e.g. for versioning) and so having endpoints like this one

app.MapPost("/api/people-minimalapi", ([FromHeader(Name = "x-api-version"] string apiVersion)
    => TypedResults.NoContent())
.WithOpenApi();

Have you tested if DotSwashbuckle produces the same error? https://github.com/Havunen/DotSwashbuckle

Yes. The error is present also with DotSwashbuckle @Havunen :)
You can find a repro here.

@captainsafia
Copy link
Member

@Havunen You should be able to resolve this in DotSwashbuckle by updating the line of code referenced here to accept either a single parameter from body or multiple parameters from the form.

One thing to note is that I think you'd have to gather the schemas for all the FromForm attributes when constructing the form body.

The exact same behaviour happens, in minimal api endpoints, if using header (like, e.g. for versioning) and so having endpoints like this one

This is surprising to me because I don't know of any code in minimal APIs/ApiExplorer/Swashbuckle that would be causing this off the top of my head but I can look deeper.

@vdevc
Copy link

vdevc commented Mar 8, 2024

This is surprising to me because I don't know of any code in minimal APIs/ApiExplorer/Swashbuckle that would be causing this off the top of my head but I can look deeper.

Actually the exception seems to be the same

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.

System.InvalidOperationException: Sequence contains more than one matching element
   at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException()
   at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
   at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOpenApiOperationFromMetadata(ApiDescription apiDescription, SchemaRepository schemaRepository)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(String documentName, String host, String basePath)
   at DotSwashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(String documentName, String host, String basePath)
   at DotSwashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

@Havunen
Copy link

Havunen commented Mar 8, 2024

@Havunen You should be able to resolve this in DotSwashbuckle by updating the line of code referenced #53831 (comment) to accept either a single parameter from body or multiple parameters from the form.

that code line does not exist in DotSwashbuckle

Actually the exception seems to be the same

Is this latest version of DotSwashbuckle 3.0.9 ? I cannot reproduce the exception, however there seems to be something wrong with the generated schema so I will check that

@captainsafia
Copy link
Member

that code line does not exist in DotSwashbuckle

You're right. It looks like it was removed in this commit.

@marcominerva Given the divergence in the codebases, can you confirm if you repro this issue with DotSwashbuckle? I don't believe what @vdevc reported with FromHeader is related to your FromForm issue.

@vdevc
Copy link

vdevc commented Mar 9, 2024

Is this latest version of DotSwashbuckle 3.0.9 ? I cannot reproduce the exception, however there seems to be something wrong with the generated schema so I will check that

My check was done on 3.0.8 yesterday. I don't see the 3.0.9 version on nuget yet 🙃

I don't believe what @vdevc reported with FromHeader is related to your FromForm issue.

@captainsafia same doubts here. I am suspecting about the presence of multiple Produces extensions methods. However I haven't been able to reproduce the problem without using the FromHeader parameter.

@Havunen
Copy link

Havunen commented Mar 9, 2024

Is this latest version of DotSwashbuckle 3.0.9 ? I cannot reproduce the exception, however there seems to be something wrong with the generated schema so I will check that

My check was done on 3.0.8 yesterday. I don't see the 3.0.9 version on nuget yet 🙃

I don't believe what @vdevc reported with FromHeader is related to your FromForm issue.

@captainsafia same doubts here. I am suspecting about the presence of multiple Produces extensions methods. However I haven't been able to reproduce the problem without using the FromHeader parameter.

ah yes, I meant 3.0.8.

Can you edit the Basic examaple project here to make it fail: https://github.com/Havunen/DotSwashbuckle

I have tried different approaches here but I am not able to reproduce the exception

https://github.com/Havunen/DotSwashbuckle/blob/master/test/WebSites/Basic/Startup.cs#L88-L98

@vdevc
Copy link

vdevc commented Mar 11, 2024

Can you edit the Basic examaple project here to make it fail: https://github.com/Havunen/DotSwashbuckle

I edited the test/Website/MinimalApi8/MinimalApi8.csproj instead as it is exactly like in my scenario.
You can see the commit in my fork here for your reference.

The error occur on line 279 of SwaggerGenerator.cs

image

Member apiDescription.ParameterDescriptions contains 3 items one of which is duplicated

image

The packages

<PackageReference Include="Asp.Versioning.Http" Version="8.0.0" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" />

comes from the excellent project from @commonsensesoftware available here.

@captainsafia
Copy link
Member

Hi all,

We just posted an announcement regarding our plans for OpenAPI support in ASP.NET Core moving forward in this post. We're bringing OpenAPI document generation as a first class feature in ASP.NET Core.

@marcominerva I've included the scenario with multiple FromForm parameters into our test bed and verified that the appropriate schema is generated for both Minimal APIs and controllers.

@vdevc It looks like your issues is specifically related to the integration with Asp.Versioning. I'm eager to get this working with the built-in integration. Do you mind filing a separate issue so that we can track this work appropriately?

@captainsafia
Copy link
Member

I've opened #55321 to resolve this issue in our built-in OpenAPI support.

The TL;DR is that we are now using allOf to properly coalesce multiple parameters that are sources from the form body into the same request. I've also double checked that the correct schema is produced between MVC's model binding semantics and minimal APIs model binding semantics.

@vdevc
Copy link

vdevc commented Apr 27, 2024

I see that #54623 is not addressed in the pull request. Are you planning with a different solution?

@captainsafia
Copy link
Member

I see that #54623 is not addressed in the pull request. Are you planning with a different solution?

Yep, that issue is distinct from this one although the exception is the same. We'll need to look at it separately especially when Asp.Versioning is thrown in the mix since it modifies the way ApiExplorer metadata is generated.

@captainsafia
Copy link
Member

Fixed in preview5 of Microsoft.AspNetCore.OpenApi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi
Projects
None yet
Development

No branches or pull requests

4 participants