-
Notifications
You must be signed in to change notification settings - Fork 678
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: "Not supported by Swagger 2.0: Multiple operations with path 'api/Products' and method 'GET'" #142
Comments
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 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? |
Hmm... and one method for getting product details like this: is a very common scenario. So yes: changing the implementation is not an option. |
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 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:
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 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 |
@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? |
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. |
The workaround specified in the documentation won't work. httpConfiguration cuz apiDescription is of type IEnumerable and not an ICollection or IList |
Also, for some reason, the rendered swagger lists about 30 controllers, and ignores the rest. |
@decoder318 The work around does work. First() is an extension method for IEnumerable. Add 'using System.Linq;' to make it available. |
@jbongaarts can confirm this works! Have been trying to look for a solution for ages. |
@jiajian this works but not as well. In fact it suppresses the methods that conflict leaving only one in the documentation. |
action added route("api/getstudent") |
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:
Obviously the hashes don't get POSTed and the APIs work as expected. |
@KarunaGovind Can you post the C# for this endpoint? Or do you edit the JSON manually? Thanks. |
@philals I didn't do this in C# but going by your above example, maybe something like this:
|
Thanks @KarunaGovind |
Add {action} to your route and it will work as supposed.
|
Hi, |
Hey |
Could you be more specific please ? |
@candoumbe No. I did not try on aspnetcore 1.0. |
hanssonfredrik 's solution with the Action saved my life ! thanks |
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): That will solve the problem w/o necessity to merge actions. |
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. |
@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. |
How to add the custom headers based on controllers. |
In ASP.NET Core apps, configure ResolveConflictingActions in your Setup class,. Add this to the ConfigureServices() method:
I verified that it works for dotnet Core 2.1. And it requires no other changes. Ugly but simple. |
Just because I had this issue and no-one seemed to have mentioned it here, you can also use LINQ fluent selectors other than I ended up using:
This selects the endpoint which contains a certain parameter, and in case there's multiple of those, takes the first of that sub-list. |
I did something similar and got it to work.
|
Tried this in an asp.net core 2.2 application and although neither swashbuckle nor asp.complain, the action with the route with anchor (e.g. #blah) never gets hit. The only way to reach that action is to introduce a path segment |
Is this really still the same in 20 20 you need to be able to pass different parameters to multiple get statements within the one controller. |
EDIT: Clarification I had two POST RPC methods in one controller (Core 3.1), and solved the issue by adding the action parameter to the Controller Route attribute:
|
The issue you have is because the two actions have the same verb. |
I can't believe that this is still an issue in 2020. Obviously one of the main reasons to use Swashbuckle is for Api document gen however, you shouldn't have to use bad programming practices (code duplication) in order to show that one method in a controller is shared between multiple Api versions. Case in point: I have a login controller in v1.0 of my Api. Due to a change in another controller I have to roll v1.1 of my Api. Because the login controller has not changed I should be able to decorate the controller with two [ApiVersion] attributes to reflect this and leave the method as is because the logic has not changed. In .Net core, doing this will expose our '/login' path to version 1.0 and 1.1 independently without any other changes. The issues occur however when you get to version 2.0 of the Api. In this instance we need to make a change to the login method for v2.0 only whilst still supporting the old login methods used by other consumers. To do this, we decorate our controller with the new ApiVersion to reflect that it supports 3 versions of the Api. We then add MapToApiVersion("2.0") to the new method and equally on the pre-exisiting method for the previous versions we add two attributes MapToApiVersion("1.0") and MapToApiVersion("1.1"). Because the methods are allocated to specific versions they are allowed to have the same route however, at this point Swashbuckle fails to generate complaing that multiple operations have the same path. The only way to get around this is to create dummy public methods that call into the same private method behind the scenes in order to make Swashbuckle think the method names and path to not clash which is code duplication purely to satisfy a poor decision in the Swagger spec. Example of the above can be found here: https://github.com/jezzipin/core-api-with-swagger/tree/master/CoreApiWithSwagger |
Same for me @jezzipin, can't believe I lost more than a day looking for a solution, reading ugly workarounds everywhere, and not finding a concrete & complete example of how to resolve this. |
If you have 2 POST methods inside 1 Controller and you for e.g. work with XML that have much similar named subclasses with different properties. There is a bug when you have the subclasse named the same - the examples illustrates that the two B subclasses are different but it seems that swagger.aspdotnet can't distinguish them. A1 { B { int Id {get; set;} } } B is different for A1 and A2, therefor when you use that inside one controller it seems that swagger can't understand which B you are referring to. |
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!
The text was updated successfully, but these errors were encountered: