Error: "Not supported by Swagger 2.0: Multiple operations with path 'api/Products' and method 'GET'" #142

Closed
ChristianWeyer opened this Issue Dec 10, 2014 · 25 comments

Comments

Projects
None yet
@ChristianWeyer

Hm... I see in the README that Swagger 2.0 does not include the query string component when mapping a URL to an action.
As a result, Swashbuckle will raise an exception if it encounters multiple actions with the same path (sans query string) and HTTP method.

Phew. Is there any generic solution for that? We (and others, I figure) do have a lot of APIs with query strings and a standard route like
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}");

Any ideas?
Thanks!

@domaindrivendev

This comment has been minimized.

Show comment
Hide comment
@domaindrivendev

domaindrivendev Dec 10, 2014

Owner

Unfortunately this is a constraint imposed by the Swagger specification https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md.

I had some late involvement with the Swagger 2.0 working group and pushed hard to have this constraint removed but to no avail. As you pointed out, this does cause an issue for certain WebApi implementations. I'll describe the workaround below but first I want to play devil's advocate and look at it from an API perspective, agnostic of implementation frameworks or even specific language constructs.

In it's essence, the constraint really just says the following paths have to be described as one operation.

GET api/products
GET api/products?producType={productType}

Behind the scenes these may be implemented as separate C# or separate Java methods, but in the context of describing a REST API, would you describe them separately? Swagger 2.0 says "No ... it's one operation with an optional productType parameter".

Technically, I believe they are two different resources (hence why I opposed the constraint) but I do see some sense in the Swagger 2.0 approach. For example, I don't think I've ever seen any API docs broken down this way - it's invariably just by path with additional information about query parameters included.

Anyway, philosophy aside - breaking the Swagger 2.0 spec is not an option and so I can only provide some workarounds.

The most straight forward would be to consolidate your multiple, "overloaded", actions with a single action with optional parameters. You could even delegate internally to the private overloaded versions.

If a change to the implementation isn't an option - Swashbuckle 5.0 provides a config setting ResolveConflictingActions. This takes a function of the form - Func<<IEnumerable<ApiDescription>, ApiDescription> which you can provide to consolidate the actions into one ApiDescription, and therefore one Operation, at the documentation level only.

This "merge" process will get trickier if the response type and errors codes also differ but at that point you'd have to ask questions about the API design.

Hope this helps - let me know if it makes sense?

Owner

domaindrivendev commented Dec 10, 2014

Unfortunately this is a constraint imposed by the Swagger specification https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md.

I had some late involvement with the Swagger 2.0 working group and pushed hard to have this constraint removed but to no avail. As you pointed out, this does cause an issue for certain WebApi implementations. I'll describe the workaround below but first I want to play devil's advocate and look at it from an API perspective, agnostic of implementation frameworks or even specific language constructs.

In it's essence, the constraint really just says the following paths have to be described as one operation.

GET api/products
GET api/products?producType={productType}

Behind the scenes these may be implemented as separate C# or separate Java methods, but in the context of describing a REST API, would you describe them separately? Swagger 2.0 says "No ... it's one operation with an optional productType parameter".

Technically, I believe they are two different resources (hence why I opposed the constraint) but I do see some sense in the Swagger 2.0 approach. For example, I don't think I've ever seen any API docs broken down this way - it's invariably just by path with additional information about query parameters included.

Anyway, philosophy aside - breaking the Swagger 2.0 spec is not an option and so I can only provide some workarounds.

The most straight forward would be to consolidate your multiple, "overloaded", actions with a single action with optional parameters. You could even delegate internally to the private overloaded versions.

If a change to the implementation isn't an option - Swashbuckle 5.0 provides a config setting ResolveConflictingActions. This takes a function of the form - Func<<IEnumerable<ApiDescription>, ApiDescription> which you can provide to consolidate the actions into one ApiDescription, and therefore one Operation, at the documentation level only.

This "merge" process will get trickier if the response type and errors codes also differ but at that point you'd have to ask questions about the API design.

Hope this helps - let me know if it makes sense?

@ChristianWeyer

This comment has been minimized.

Show comment
Hide comment
@ChristianWeyer

ChristianWeyer Dec 10, 2014

Hmm...
I think having a method for list of products like this:
public IEnumerable GetProducts()

and one method for getting product details like this:
public ProductDto GetProduct(int id)

is a very common scenario.
Different return types, at least.

So yes: changing the implementation is not an option.
Hm...

Hmm...
I think having a method for list of products like this:
public IEnumerable GetProducts()

and one method for getting product details like this:
public ProductDto GetProduct(int id)

is a very common scenario.
Different return types, at least.

So yes: changing the implementation is not an option.
Hm...

@domaindrivendev

This comment has been minimized.

Show comment
Hide comment
@domaindrivendev

domaindrivendev Dec 10, 2014

Owner

True, it's extremely common to have a "collection" resource and corresponding "item" resource. However, the standard approach here would be to use a "path" parameter for the id:

GET /api/products
GET /api/products/{id}

If you're early in your API design, I would strongly advice this approach. It's a widely accepted standard in REST design. In fact it's reflected by the default route template when you create a new WebApi project:

routes.MapHttpRoute(
    name: "API Default",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Swagger will have no problem with this because the two different operations can be distinguished by path alone. If you want to go against the grain and represent the two different operations as follows:

GET /api/products
GET /api/products?id={id}

Then you will find it very difficult to describe your API with Swagger. Although it does represent best practice, I do think Swagger 2.0 is a little too opinionated in this regard. Swashbuckle provides good workarounds and frankly, can't do much more. If you feel very strongly about this, I would recommend posting an issue here - https://github.com/swagger-api/swagger-spec

Owner

domaindrivendev commented Dec 10, 2014

True, it's extremely common to have a "collection" resource and corresponding "item" resource. However, the standard approach here would be to use a "path" parameter for the id:

GET /api/products
GET /api/products/{id}

If you're early in your API design, I would strongly advice this approach. It's a widely accepted standard in REST design. In fact it's reflected by the default route template when you create a new WebApi project:

routes.MapHttpRoute(
    name: "API Default",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Swagger will have no problem with this because the two different operations can be distinguished by path alone. If you want to go against the grain and represent the two different operations as follows:

GET /api/products
GET /api/products?id={id}

Then you will find it very difficult to describe your API with Swagger. Although it does represent best practice, I do think Swagger 2.0 is a little too opinionated in this regard. Swashbuckle provides good workarounds and frankly, can't do much more. If you feel very strongly about this, I would recommend posting an issue here - https://github.com/swagger-api/swagger-spec

@OnamChilwan

This comment has been minimized.

Show comment
Hide comment
@OnamChilwan

OnamChilwan Mar 4, 2015

@domaindrivendev you mention workarounds via Swashbuckle what are these? The only one I have comes across is using the first action in the list which hides implementation in my opinion and isn't ideal. Is there a way of exposing your example above?

@domaindrivendev you mention workarounds via Swashbuckle what are these? The only one I have comes across is using the first action in the list which hides implementation in my opinion and isn't ideal. Is there a way of exposing your example above?

@domaindrivendev

This comment has been minimized.

Show comment
Hide comment
@domaindrivendev

domaindrivendev Mar 4, 2015

Owner

True, it's not ideal! However, I still maintain that it's due to an overly opinionated constraint in the Swagger specification and with that being the root cause, maybe worth posting an issue there.

The bottom line is this ... because of this constraint, you CANNOT describe the actions as separate Swagger Operations and so, the corresponding ApiDescriptions have to be merged into one for the Swagger document to be generated. This is what the ResolveConflictingActions option is for.

It's also worth noting that you're not limited to just taking the first description, you're free to implement any merge strategy you like. For example, if the actions share the same response type and only differ in parameters, you could return a completely new ApiDescription that includes a union of parameters, marking the non-common ones as optional.

Finally, if they don't share the same response type ... then, at an API level (independent of SB, C# or any server-side frameworks) you simply won't be able to describe your API with Swagger.

Owner

domaindrivendev commented Mar 4, 2015

True, it's not ideal! However, I still maintain that it's due to an overly opinionated constraint in the Swagger specification and with that being the root cause, maybe worth posting an issue there.

The bottom line is this ... because of this constraint, you CANNOT describe the actions as separate Swagger Operations and so, the corresponding ApiDescriptions have to be merged into one for the Swagger document to be generated. This is what the ResolveConflictingActions option is for.

It's also worth noting that you're not limited to just taking the first description, you're free to implement any merge strategy you like. For example, if the actions share the same response type and only differ in parameters, you could return a completely new ApiDescription that includes a union of parameters, marking the non-common ones as optional.

Finally, if they don't share the same response type ... then, at an API level (independent of SB, C# or any server-side frameworks) you simply won't be able to describe your API with Swagger.

@decoder318

This comment has been minimized.

Show comment
Hide comment
@decoder318

decoder318 Jun 9, 2015

@domaindrivendev

The workaround specified in the documentation won't work.

httpConfiguration
.EnableSwagger((c) =>
{
c.SingleApiVersion("v1", "A title for your API"));
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});

cuz apiDescription is of type IEnumerable and not an ICollection or IList
Is there a better way of handling this? Or did you forget updating the documentation?

@domaindrivendev

The workaround specified in the documentation won't work.

httpConfiguration
.EnableSwagger((c) =>
{
c.SingleApiVersion("v1", "A title for your API"));
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});

cuz apiDescription is of type IEnumerable and not an ICollection or IList
Is there a better way of handling this? Or did you forget updating the documentation?

@decoder318

This comment has been minimized.

Show comment
Hide comment
@decoder318

decoder318 Jun 9, 2015

Also, for some reason, the rendered swagger lists about 30 controllers, and ignores the rest.

Also, for some reason, the rendered swagger lists about 30 controllers, and ignores the rest.

@jbongaarts

This comment has been minimized.

Show comment
Hide comment
@jbongaarts

jbongaarts Jun 24, 2015

@decoder318 The work around does work. First() is an extension method for IEnumerable. Add 'using System.Linq;' to make it available.

@decoder318 The work around does work. First() is an extension method for IEnumerable. Add 'using System.Linq;' to make it available.

@JiaJian

This comment has been minimized.

Show comment
Hide comment
@JiaJian

JiaJian Jul 18, 2015

@jbongaarts can confirm this works! Have been trying to look for a solution for ages.

JiaJian commented Jul 18, 2015

@jbongaarts can confirm this works! Have been trying to look for a solution for ages.

@bernardbr

This comment has been minimized.

Show comment
Hide comment
@bernardbr

bernardbr Aug 12, 2015

@JiaJian this works but not as well. In fact it suppresses the methods that conflict leaving only one in the documentation.

@JiaJian this works but not as well. In fact it suppresses the methods that conflict leaving only one in the documentation.

@ivelkov ivelkov referenced this issue in springfox/springfox Oct 20, 2015

Closed

Missing REST API descriptions in JSON #866

@caodaiming

This comment has been minimized.

Show comment
Hide comment
@caodaiming

caodaiming Mar 11, 2016

action added route("api/getstudent")

action added route("api/getstudent")

@KarunaGovind

This comment has been minimized.

Show comment
Hide comment
@KarunaGovind

KarunaGovind Apr 7, 2016

My workaround has been to use hashes to show the same endpoint multiple times. Does the job even though swaggerui may look a little untidy. So my json output ends up being something like:

/user/login#step1: { post: { } }, /user/login#step2: { post: { } },

Obviously the hashes don't get POSTed and the APIs work as expected.

My workaround has been to use hashes to show the same endpoint multiple times. Does the job even though swaggerui may look a little untidy. So my json output ends up being something like:

/user/login#step1: { post: { } }, /user/login#step2: { post: { } },

Obviously the hashes don't get POSTed and the APIs work as expected.

@philals

This comment has been minimized.

Show comment
Hide comment
@philals

philals May 20, 2016

@KarunaGovind Can you post the C# for this endpoint? Or do you edit the JSON manually? Thanks.

philals commented May 20, 2016

@KarunaGovind Can you post the C# for this endpoint? Or do you edit the JSON manually? Thanks.

@KarunaGovind

This comment has been minimized.

Show comment
Hide comment
@KarunaGovind

KarunaGovind May 20, 2016

@philals I didn't do this in C# but going by your above example, maybe something like this:

routes.MapHttpRoute(
    name: "API Default",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "another one with same endpoint",
    routeTemplate: "api/{controller}/{id}#blah2",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "yet another one",
    routeTemplate: "api/{controller}/{id}#blah3",
    defaults: new { id = RouteParameter.Optional }
);

@philals I didn't do this in C# but going by your above example, maybe something like this:

routes.MapHttpRoute(
    name: "API Default",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "another one with same endpoint",
    routeTemplate: "api/{controller}/{id}#blah2",
    defaults: new { id = RouteParameter.Optional }
);

routes.MapHttpRoute(
    name: "yet another one",
    routeTemplate: "api/{controller}/{id}#blah3",
    defaults: new { id = RouteParameter.Optional }
);
@philals

This comment has been minimized.

Show comment
Hide comment
@hanssonfredrik

This comment has been minimized.

Show comment
Hide comment
@hanssonfredrik

hanssonfredrik Jun 29, 2016

Add {action} to your route and it will work as supposed.

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

hanssonfredrik commented Jun 29, 2016

Add {action} to your route and it will work as supposed.

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

@candoumbe

This comment has been minimized.

Show comment
Hide comment
@candoumbe

candoumbe Jul 22, 2016

Hi,
What would be the equivalent workaround for aspnetcore 1.0 ?
Thanks in advance

candoumbe commented Jul 22, 2016

Hi,
What would be the equivalent workaround for aspnetcore 1.0 ?
Thanks in advance

@durgeshanju

This comment has been minimized.

Show comment
Hide comment
@durgeshanju

durgeshanju Jul 27, 2016

Hey
It worked. But it considered only the first method, If there are multiple.
Thanks

Hey
It worked. But it considered only the first method, If there are multiple.
Thanks

@candoumbe

This comment has been minimized.

Show comment
Hide comment
@candoumbe

candoumbe Jul 27, 2016

Could you be more specific please ?
Did you mean "it worked" [on asp.netcore] ?

Could you be more specific please ?
Did you mean "it worked" [on asp.netcore] ?

@durgeshanju

This comment has been minimized.

Show comment
Hide comment
@durgeshanju

durgeshanju Jul 27, 2016

@candoumbe No. I did not try on aspnetcore 1.0.
I am referring to answer posted by @domaindrivendev .

@candoumbe No. I did not try on aspnetcore 1.0.
I am referring to answer posted by @domaindrivendev .

@Benraay

This comment has been minimized.

Show comment
Hide comment
@Benraay

Benraay Sep 28, 2016

hanssonfredrik 's solution with the Action saved my life ! thanks

Benraay commented Sep 28, 2016

hanssonfredrik 's solution with the Action saved my life ! thanks

@xtrmstep

This comment has been minimized.

Show comment
Hide comment
@xtrmstep

xtrmstep Oct 11, 2016

There is one more solution to this, but it's a deviation from Swagger 2.0 spec.

You can implement ISwaggerProvider using the current class SwaggerGenerator. To cut long story short, just copy-paste the class from the original source to ISwaggerProvider implementation and initialize private fields using reflection. The provider can be added by means of the standard way through the configuration. In your implementation you should change only one line of code to allow multiple operations (example):
.GroupBy(apiDesc => apiDesc.RelativePathSansQueryString())
to
.GroupBy(apiDesc => apiDesc.RelativePath)

That will solve the problem w/o necessity to merge actions.

There is one more solution to this, but it's a deviation from Swagger 2.0 spec.

You can implement ISwaggerProvider using the current class SwaggerGenerator. To cut long story short, just copy-paste the class from the original source to ISwaggerProvider implementation and initialize private fields using reflection. The provider can be added by means of the standard way through the configuration. In your implementation you should change only one line of code to allow multiple operations (example):
.GroupBy(apiDesc => apiDesc.RelativePathSansQueryString())
to
.GroupBy(apiDesc => apiDesc.RelativePath)

That will solve the problem w/o necessity to merge actions.

@neuhoffm

This comment has been minimized.

Show comment
Hide comment
@neuhoffm

neuhoffm Nov 26, 2016

The first part of this article solved this issue for me: https://docs.microsoft.com/en-us/azure/app-service-api/app-service-api-dotnet-swashbuckle-customize. No need to change my API, just add a custom operation filter that generates unique ids for each operation for swagger. API remains unchanged otherwise.

The first part of this article solved this issue for me: https://docs.microsoft.com/en-us/azure/app-service-api/app-service-api-dotnet-swashbuckle-customize. No need to change my API, just add a custom operation filter that generates unique ids for each operation for swagger. API remains unchanged otherwise.

@fabriciorissetto

This comment has been minimized.

Show comment
Hide comment
@fabriciorissetto

fabriciorissetto Nov 30, 2016

@neuhoffm thanks for sharing this, but the solution described there doesn't solve the problem of the "multiple operations with the same path". Instead it resolves the problem of the conflicting "operationIds" but the paths are not the same in that example.

fabriciorissetto commented Nov 30, 2016

@neuhoffm thanks for sharing this, but the solution described there doesn't solve the problem of the "multiple operations with the same path". Instead it resolves the problem of the conflicting "operationIds" but the paths are not the same in that example.

@jhonwritter

This comment has been minimized.

Show comment
Hide comment
@jhonwritter

jhonwritter Jun 12, 2017

How to add the custom headers based on controllers.
Lets say ..
Controller Name : Controller1 , Custom header : Header1
Controller Name : Controller2 Custom header : Header2
The headers should only display for all the apis under specific controllers

How to add the custom headers based on controllers.
Lets say ..
Controller Name : Controller1 , Custom header : Header1
Controller Name : Controller2 Custom header : Header2
The headers should only display for all the apis under specific controllers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment