Only one GET method in controller and yet getting "Not supported by Swagger 2.0: Multiple operations with path" - asp.net-web-api

I know there are many questions with the same title but this one is different.
I was getting this error on my product controller, so to investigate the problem I created a demo controller in the ASP.NET Web API 2.
DemoController.cs
namespace WMWebAPIControllers.Controllers
{
[RoutePrefix("api/Demo")]
public class DemoController : ControllerBase
{
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetProducts(int CatalogType, string ProductNo)
{
return Ok();
}
}}
The strange thing is demo controller have only one method. There is no method with which swagger would find ambuigity.
I don't understand the problem.
Below is the swagger error.
500 : {"message":"An error has occurred.","exceptionMessage":"Not supported by Swagger 2.0: Multiple operations with path 'api/Demo' and method 'GET'. See the config setting - \"ResolveConflictingActions\" for a potential workaround","exceptionType":"System.NotSupportedException"

The problem was I had one public method without route attribute. So swagger found ambiguity with the GetProducts() method which was configured for the empty route.
Marked that public method as [NonAction] attribute and issue solved.
It can also be marked private/protected to solve this problem but in my case, it was an interface method.
Hope this will help someone.

Related

Spring Boot - Is it possible to disable an end-point

Assuming I have a controller like:
public class MyController {
public String endpoint1() {...}
public String endpoint2() {...}
}
I want to disable endpoint1 for whatever reason in Spring. Simply, just disable it so that it cannot be accessed. So, I am not looking for how and what response to return in that case or how to secure that endpoint. Just looking to simply disable the endpoint, something like #Disabled annotation on it or so.
SOLUTION UPDATE:
Thanks all who contributed. I decided to go with #AdolinK suggestion . However, that solution will only disable access to the controller resulting into 404 Not Found. However, if you use OpenApi, your controller and all of its models such as request/response body will still show in swagger.
So, in addition to Adolin's suggestion and also added #Hidden OpenApi annotation to my controllers like:
In application.properties, set:
cars.controller.enabled=false
Then in your controller, use it. To hide controller from the OpenApi/Swagger as well, you can use #Hiden tag:
#Hidden
#ConditionalOnExpression("${cars.controller.enabled}")
#RestController
#RequestMapping("/cars")
public class Carontroller {
...
}
After this, every end point handled by this controller will return 404 Not Found and OpenApi/Swagger will not show the controllers nor any of its related schema objects such as CarRequestModel, CarResponseModel etc.
You can use #ConditionalOnExpression annotation.
public class MyController {
#ConditionalOnExpression("${my.controller.enabled:false}")
public String endpoint1() {...}
public String endpoint2() {...}
}
In application.properties, you indicates that controller is enabled by default
my.controller.enabled=true
ConditionalOnExpression sets false your property, and doesn't allow access to end-point
Why not remove the mapping annotation over that method?
Try this simple approach: You can define a property is.enable.enpoint1 to turn on/off your endpoint in a flexible way.
If you turn off the endpoint, then return a 404 or error page, which depends on your situation.
#Value("${is.enable.enpoint1}")
private String isEnableEnpoint1;
public String endpoint1() {
if (!"true".equals(isEnableEnpoint1)) {
return "404";
}
// code
}

Automatically document #PathVariable annotated parameters within #ModelAttribute annotated methods

In our REST-API we need to be multi-tenant capable. For achiving this all rest controllers subclass a common REST controller which defines a request mapping prefix and exposes a model attribute as follows
#RequestMapping(path = "/{tenantKey}/api")
public class ApiController {
#ModelAttribute
public Tenant getTenant(#PathVariable("tenantKey") String tenantKey) {
return repository.findByTenantKey(tenantKey);
}
}
Derived controllers make use of the model attributes in their request mapping methods:
#RestController
public class FooController extends ApiController {
#RequestMapping(value = "/foo", method = GET)
public List<Foo> getFoo(#ApiIgnore #ModelAttribute Tenant tenant) {
return service.getFoos(tenant);
}
}
This endpoint gets well documented in the swagger-ui. I get an endpoint documented with a GET mapping for path /{tenantKey}/api/foo.
My issue is, that the {tenantKey} path variable is not documented in swagger-ui as parameter. The parameters section in swagger is not rendered at all. If I add a String parameter to controller method, annotating it with #PathVariable("tenantKey) everything is fine, but I don't want a tenantKey parameter in my controller method, since the resolved tenant is already available as model attribute.
So my question is: Is there a way do get the #PathVariable from the #ModelAttriute annotated method in ApiController documented within swagger-ui in this setup?
Project-Setup is
Spring-Boot (1.4.2)
springfox-swagger2 (2.6.1)
springfox-swagger-ui (2.6.1)
This is certainly possible. Model attributes on methods are not supported currently. Instead, you could take the following approach.
Mark the getTenant method with an #ApiIgnore (not sure if it gets treated as a request mapping.)
In your docket you can add tenantKey global path variable (to all end points). Since this is a multi-tenant app it's assuming this applies to all endpoints.

Web API 2: Added a new controller, it won't register?

Having a heck of a time here... (Web API 2.1, .NET 4.5.1)
I had one controller that worked perfectly:
[RoutePrefix("v1/members")]
public class MembersController : ApiController
{
[Route("{id}")]
public Member Get(string id)
{
DataGateway g = new DataGateway();
return g.GetMember(id);
}
}
Works as intended and desired like:
/v1/members/12345
But I added a new controller today and it doesn't seem to be registered or recognized at all. It doesn't get added to the Help pages and it returns a 404 Not Found when trying to access it:
[RoutePrefix("v1/test")]
public class Test : ApiController
{
[Route("{id}")]
public string Get(int id)
{
return "value";
}
}
Like I stated, the new controller doesn't show up in the Help pages and returns a 404:
/v1/test/12345
What am I doing wrong?
EDITED TO ADD:
I installed tracing and it doesn't even seem to be hitting that. The first controller works fine and shows a trace, the new Test controller doesn't show any trace info.
EDIT 2:
Updated example code to better match my actual code, which was the problem all along.
The problem was here:
[RoutePrefix("v1/test")]
public class Test : ApiController
Changed to:
[RoutePrefix("v1/test")]
public class TestController : ApiController
Seems the almighty important word "Controller" needs to be in the class name for the magic to happen.

ASP.NET WebAPI Supported Media Types per Method

Given a method in a controller:
public class CustomerController : ApiController
{
[HttpGet]
public CustomerDto GetById([FromUri] int id)
{
.
.
return customerDto
}
}
Is there a way to specify supported Media Types with an attribute? For instance, CustomerDto is a complex class and will only serialize with JSON (application/json) not XML (application/xml) but could also accept PDF (application/pdf). Is there something like this:
[HttpGet(Accepts.JSON, Accepts.PDF)]
or
[HttpGet][AcceptJSON][AcceptXML]
or
[HttpGet][Accept("application/json")][Accept("application/pdf")]
If the incoming request wasn't supported a Unsupported Exception / Status could be returned.
Note - I don't want to remove say XML serialization all together as could be done globally. Instead, I would like to define what is accepted per route.
Using - ASP.NET WebAPI RC 1 (need to upgrade) + Self Hosting
Sounds like a custom ActionFilterAttribute might do the trick.
Create a new class that inherits from System.Web.Http.Filters.ActionFilterAttribute, override the OnActionExecuting method. Inside this method, you could check the request's headers, look for what you don't want to support and return an appropriate response.
The constructor for your custom ActionFilterAttribute could take the details of which "accept" types you want to process and which ones you want to reject.
For an example of a custom ActionFilterAttribute, check out this post.

WebApi action parameters validation by ValidationAttribute

Does WebAPI can handle ValidationAttribute on action parameter?
For instance:
public class SampleController : ApiController
{
public string Get([RegularExpression("sampleExpression")]string id)
{
return "result";
}
}
In this sample WebAPI doesn't invoke any methods of RegularExpressionAttribute (or any other data annotation attribute) to validate input parameter. But in case if we passing an object as parameter, for instance a class then WebAPI can validate properties.
Is it a bug? Or I'm doing something wrong?
UPD: It's an open issue:
http://aspnetwebstack.codeplex.com/workitem/24?PendingVoteId=24
Does anyone can suggest a workaround?
This is a genuine question, I'm curious why not just do something like :
public class SampleController : ApiController
{
public string Get(string id)
{
RegularExpressionHelper.ValidateWith("sampleExpression",id);
return "result";
}
}
To me this seems to be equivalently concise. It is obvious when the validation occurs. I can safely assume that if the validation fails then an exception will be thrown. I can easily add additional validation and be confident of the order in which they occur.
Regarding the attribute method, I don't know if the validation is used as part of the route matching, I have no idea what happens if it fails. I don't know what filters run before or after the validation. I'm sure if I knew MVC better I would know the answer to these questions, but I don't see the advantage of using an attribute that makes my code's behaviour dependent on some framework controlled infrastructure.
Am I missing some significant benefit?
I had the same doubt. My workaround consists in creating a class just for encapsulating the parameter, so I can decorate it with the validation attribute I want. I could use the workaround proposed by Darrel in his answer, but I have a filter that checks if ModelState.IsValid before entering the action, so I need to validate before the action gets executed.
[ModelBinder]
public class Item
{
[RegularExpression("sampleExpression")]
public string Id { get; set; }
}
The class must be annotated with [ModelBinder], otherwise the parameter binding mechanism will try to extract the id field from the body of the request. Read this article for more info.
Also, note that Id is now in PascalCase instead of camelCase. Read this article to understand how the conversion is made.
The action signature is:
public string Get(Item item)

Resources