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

ApiExplorer not handling multiple routes on same action method correctly #26234

Closed
domaindrivendev opened this issue Sep 23, 2020 · 9 comments · Fixed by #39607
Closed

ApiExplorer not handling multiple routes on same action method correctly #26234

domaindrivendev opened this issue Sep 23, 2020 · 9 comments · Fixed by #39607
Assignees
Labels
affected-few This issue impacts only small number of customers area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels bug This issue describes a behavior which is not expected - a bug. severity-minor This label is used by an internal tool

Comments

@domaindrivendev
Copy link

Describe the bug

Given the following action method bound to multiple routes:

[HttpGet]
[Route("foo")]
[Route("bar/{id}")]
public IActionResult Get(int id = 0)

As expected, the ApiExplorer surfaces two ApiDescription instances, one for each route. However, for the first route "foo", the ApiDescription includes the id path parameter, despite the fact that it's not part of the route template. For example, here's the ApiExplorer data (serialized as json):

[
  {
    relativePath: "Test/foo",
    httpMethod: "GET",
    parameters: [
      {
        name: "id",
        source: {
          displayName: "Path",
          id: "Path",
          isGreedy: false,
          isFromRequest: true,
        },
      }
    ],
  },
  {
    relativePath: "Test/bar/{id}",
    httpMethod: "GET",
    parameters: [
      {
        name: "id",
        source: {
          displayName: "Path",
          id: "Path",
          isGreedy: false,
          isFromRequest: true,
        },
      }
    ],
  },
]

To Reproduce

> git clone [email protected]:domaindrivendev/ApiExplorerIssue.git
> cd ApiExplorerIssue
> dotnet run

Then navigate to "http://localhost:5000/apidescriptions" to see the ApiExplorer data

Further technical details

  • ASP.NET Core version: 3.1
@domaindrivendev
Copy link
Author

Here's the corresponding downstream issue that was originally created in Swashbuckle.AspNetCore:
domaindrivendev/Swashbuckle.AspNetCore#1834

@domaindrivendev
Copy link
Author

In the meantime, if you're really stuck you could wire up an IOperationFilter (see readme) that detects this anomaly and corrects it by removing the parameter from the operation.parameters collection.

@pranavkm pranavkm added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Sep 23, 2020
@frankracis-work
Copy link

I had the filter look at ApiDescription.ParamaterDescriptions, and if In==Path and !IsRequired, removed it from the operation.Parameters list. I don't know that this will work for every case, but it was enough for my routes.

@pranavkm pranavkm added this to the Next sprint planning milestone Sep 25, 2020
@ghost
Copy link

ghost commented Sep 25, 2020

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@SteveSandersonMS SteveSandersonMS added affected-few This issue impacts only small number of customers bug This issue describes a behavior which is not expected - a bug. severity-minor This label is used by an internal tool labels Oct 7, 2020 — with ASP.NET Core Issue Ranking
@ghost
Copy link

ghost commented Oct 9, 2020

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@mkArtakMSFT mkArtakMSFT added area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Oct 20, 2021
@brunolins16
Copy link
Member

@domaindrivendev the short answer for that is, you could workaround it by setting ApiBehaviorOptions.SuppressInferBindingSourcesForParameters to true. Eg.:

builder.Services.Configure<ApiBehaviorOptions>(options => {
    options.SuppressInferBindingSourcesForParameters = true;
});

Now, to give you more details. When your controller is annotated with [ApiController] it will be applied some conventions and one of them is the InferParameterBindingInfoConvention that will basically do what is described here ApiBehaviorOptions.SuppressInferBindingSourcesForParameters Property

image

As you can see, in your case you do have a route where the parameter is included in the route, that means this parameter will be inferred as Binding from Path and that will affect how Swashbuckle deal with the route and setting both as FromPath and Required.

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/45875d5e222f9f7369b6e5f31e1ece112fa81aaa/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiParameterDescriptionExtensions.cs#L23

Once you have the property SuppressInferBindingSourcesForParameters set as true the binding source will not be inferred and you will have the behavior that you are looking for:

{
  "openapi": "3.0.1",
  "info": {
    "title": "OpenAPISample",
    "version": "1.0"
  },
  "paths": {
    "/WeatherForecast/foo": {
      "get": {
        "tags": [
          "WeatherForecast"
        ],
        "operationId": "Foo",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    },
    "/WeatherForecast/bar/{id}": {
      "get": {
        "tags": [
          "WeatherForecast"
        ],
        "operationId": "Bar",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    }
  },
  "components": { }
}

Just remind that it is a global configuration that will affect all API controllers that might cause some issues to your current APIs.

@brunolins16
Copy link
Member

@domaindrivendev In addition to my previous comment, it is a bug and it should be work correct without you need to change the SuppressInferBindingSourcesForParameters but while we do not have a fix, that is the option other than the option you mentioned about IOperationFilter

@halter73
Copy link
Member

Once you have the property SuppressInferBindingSourcesForParameters set as true the binding source will not be inferred and you will have the behavior that you are looking for

Doesn't this change the actual behavior of the controller and not just the ApiExplorer model? Based on the linked docs, I would expect that the id parameter would no longer be inferred to come from the path in the "/WeatherForecast/bar/{id}" case (although the swagger indicates otherwise). Do you have to use param attributes after setting SuppressInferBindingSourcesForParameters to true?

@brunolins16
Copy link
Member

Doesn't this change the actual behavior of the controller and not just the ApiExplorer model? Based on the linked docs, I would expect that the id parameter would no longer be inferred to come from the path in the "/WeatherForecast/bar/{id}" case (although the swagger indicates otherwise). Do you have to use param attributes after setting SuppressInferBindingSourcesForParameters to true?

@halter73, exactly that is why I included a small disclaimer (below) at the end. However, I tested it and the parameter binding still working, without adding the attributes, for me but I did not check exactly why.

image

@ghost ghost locked as resolved and limited conversation to collaborators Feb 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affected-few This issue impacts only small number of customers area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels bug This issue describes a behavior which is not expected - a bug. severity-minor This label is used by an internal tool
Projects
None yet
9 participants