Swashbuckle crashes by just adding another action method in controller - c#

I have just added another post method in a controller, and the Swagger Swashbuckle crashed.
How to solve this ?
[HttpPost]
public IActionResult CreateCars(List<Car> cars)
{
_carService.CreateCars(cars);
return NoContent();
}
System.NotSupportedException: HTTP method "POST" & path "api/Cars" overloaded by actions - IrkcnuApi.Controllers.CarsController.Create (WebApi),MyWebAPI.Controllers.CarsController.CreateCars (MyWebApi). Actions require unique method/path combination for OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

You already have a method in your controller that is attributed with a HttpPost attribute.
Since you do not explicitly specify a route, these operations clash.
You'll solve this by specifying a route for these POST operations, for instance:
[HttpPost("createMultiple")]
public IActionResult CreateCars(List<Car> cars) {}
[HttpPost()]
public IActionResult CreateCar(Car car) {}
The above suggestion is offcourse not that 'RESTfull', since you have verbs in your URLs.
I'd suggest to modify your code so that you only have one 'Create' method, since the above 2 operations are actually implicitely the same (I guess). Calling the CreateCars operation with a collection of Cars that only contains one item is in a sense actually identical to calling the CreateCar operation.

Use the following code to resolve the issue,
services.AddSwaggerGen(options =>
{
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});

In my code i use Swagger Swashbuckle 5.5.1 and Microsoft.AspNetCore.Mvc.Versioning 4.1.1
For me [ApiExplorerSettings(GroupName = "vx.0")] where x is version of multiple action in the same controller or other controller, works fine.
I also use MapToApiVersion attribute togheter but the attribute ApiExplorerSettings avoid conflict.
See https://www.myget.org/feed/domaindrivendev/package/nuget/Swashbuckle.AspNetCore.Swagger
at "Decorate Individual Actions"
In my test i have 2 controllers. First controller maps version 1.0 and 2.0. Second controller map only version 3.0
First controller:
[Authorize]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("viewqlikapi")]
[Route("ViewQlikApi/v{version:apiVersion}")]
[ApiController]
public class QlikController : ControllerBase, IQlikController
And two action that have the same route
// Dati Pratica Audit
[HttpGet]
[ApiExplorerSettings(GroupName = "v1.0")]
[Route("datipraticaaudit")]
public RisultatoElementiPagina<ViewQlikDatiPraticaAudit GetElementiPaginaDatiPraticaAudit(int numeroElementi, int indicePagina)....
[HttpGet]
[MapToApiVersion("2.0")]
[ApiExplorerSettings(GroupName = "v2.0")]
[Route("datipraticaaudit")]
public RisultatoElementiPagina<ViewQlikDatiPraticaAudit> GetElementiPaginaDatiPraticaAuditV2(int numeroElementi, int indicePagina, int other)...
and the second controller..
[Authorize]
[ApiVersion("3.0")]
[Route("ViewQlikApi/v{version:apiVersion}")]
[ApiController]
public class QlikV2Controller : ControllerBase
and the action
[HttpGet]
[MapToApiVersion("3.0")]
[ApiExplorerSettings(GroupName = "v3.0")]
[Route("auditoperativoaccesso")]
public RisultatoElementiPagina<ViewQlikAuditOperativoAccesso> GetElementiPaginaAuditOperativoAccesso(int numeroElementi, int indicePagina, int o)

Related

.Net Api - Parent route matched for invalid nested route

I have a .Net 5 API and some nested routes as follows:
[Route("api/v{version:apiVersion}/orders")]
[ApiVersion("1.0")]
[ApiController]
public class OrdersController: ControllerBase
{
[HttpGet("{userId:required}")]
public async Task<ActionResult> Get(string userId,
CancellationToken cancellationToken = default)
{
// return the orders corresponding to the userId
}
}
[Route("api/v{version:apiVersion}/orders/details")]
[ApiVersion("1.0")]
[ApiController]
public class OrdersDetailsController: ControllerBase
{
[HttpGet("{orderId:required}")]
public async Task<ActionResult> Get(string orderId,
CancellationToken cancellationToken = default)
{
// return the order details
}
}
Below is the list of responses I get when making requests to the API:
GET /orders/some_dummy_user_id returns the orders for userId="some_dummy_user_id", which is OK
GET /orders/details/some_dummy_order_id returns the details of orderId="some_dummy_order_id", which is OK
GET /orders/details/ tries to return the orders corresponding to userId="details" which is Not OK
The question is: is it possible to make the GET /orders/details/ request match the OrderDetailsController route and therefore return a 404 because of the missing orderId URL parameter?
try this
[Route("api/v{version:apiVersion}/orders")]
[ApiVersion("1.0")]
[ApiController]
public class OrdersController: ControllerBase
{
[HttpGet("{userId:required}")]
public async Task<ActionResult> Get(string userId,
CancellationToken cancellationToken = default)
{
if(userId.ToLower=="details") throw new HttpException(404, "not found");
// return the orders corresponding to the userId
}
}
It seems that unfortunately it's not possible to achieve the required scenario using only the .Net routing features. The possible solutions I see in this situation are:
As mentioned in Serge's answer, introduce a manual check and throw a 404 Exception if the userId matches the sub-route
Update the routes schema in order to prevent this scenario
As I'd like to achieve this using only the .Net routing features I've proceeded with the 2nd solution (especially as the impact on the overall routes schema wasn't major).

ASP.NET Core routing return 404

I am creating a web api using ASP.NET Core 3.1 and am trying to route URL to controllers. So far I have a basic controller like this:
[Route("abc")]
[ApiController]
public class ABCController : ControllerBase
{
// GET: abc/1234
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetABCService(long id)
{
...
}
}
Which correctly route me to the page when I type in http://myurl/abc/1234. The next thing I controller I wanted to wire is like this:
[Route("xxx")]
[ApiController]
public class XXXController : ControllerBase
{
// GET: abc/1234/XXX
[HttpGet("{id}")]
public async Task<ActionResult<string>> GetXXXService(long id)
{
...
}
}
Somehow it keeps giving me 404 when I type in http://myurl/abc/1234/xxx. I made the first one works by setting my endpoint like this :
app.UseEndpoints(endpoints =>{
endpoints.MapControllerRoute(
"abc",
"abc/{id}",
new {controller = "ABCController", action = "GetABCService"});
//My current endpoint mapping for the second controller:
endpoints.MapControllerRoute(
"xxx",
"abc/{id}/xxx",
new {controller = "XXXController", action = "GetXXXStatus" });
}
I could not figure out why I would get 404 with http://myurl/abc/1234/xxx. Any insight?
You want to say XXXController to route 'abc' first by [Route("abc")]
[Route("abc")]
[ApiController]
public class XXXController : ControllerBase
{
[HttpGet("{id}/xxx")]
public ActionResult<string> GetXXXService(long id)
{
return "ActionResult";
}
}
When you use attribute routing, e.g. with [Route] or [HttpGet(…)], then convention-based routing is ignored. So the route templates you define with MapControllerRoute are not taken into account when generating the routes for your API controller. In addition, using the [ApiController] attribute actually enables certain API-related conventions. And one of those convention is that you may only use attribute routing for your API controllers.
So if you only have API controllers in your project, then you can leave out the MapControllerRoute calls. Instead, you will have to make sure that your attribute routing is correct.
In your case, if you want the route abc/1234/XXX to work, then you will have to use a route of abc/{id}/XXX.

Why is index method not called when action name is not specified

I have a simple ASP.NET Core API with the following controller:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
public IActionResult Index()
{
return Ok("index");
}
public IActionResult Get()
{
return Ok("get");
}
}
When I hit URL:
"http://localhost:6001/api/home"
With a GET request, I get the following error:
"The request matched multiple endpoints."
Normally, if I don't specify an action name, shouldn't it call the Index() method?
In ASP.NET Core API, action methods are matched by a combination of HTTP verb used as attribute on the method (which is HttpGet by default if you don't add any) and the route parameter used with it. In that sense both your action methods looks similar to the routing system. It sees two HttpGet method with no route parameter. That's why both method matches the request -
"http://localhost:6001/api/home"
making the routing system confused which to select. That's exactly what the error message is saying.
You need to make your non-default action method more specific, so that the routing mechanism can differentiate between the two. Something like -
[HttpGet("data")]
public IActionResult Get()
{
return Ok("get");
}
Now your Index method will be matched as default action method with the above URL.
Both of your endpoints are identical. Use routes to differentiate them. For example:
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index()
{
return Ok("index");
}
[HttpGet, Route("data")]
public IActionResult Get()
{
return Ok("get");
}
}
This would open up:
http://localhost:6001/api/home/
and
http://localhost:6001/api/home/data
respectively using GET
You could also change the verb, for example use HttpGet for one and HttpPost for another.

How does ASP.NET Core Web API build URLs?

I have the following code:
[Produces("application/json")]
[Route("api/[controller]")]
public class MarketReportInstancesTableController : BaseController
{
internal readonly MyIRIntegrationDbContext Context;
public MarketReportInstancesTableController(ILogger<MarketReportInstancesTableController> logger,
MyIRIntegrationDbContext context) : base(logger)
{
Context = context;
}
[HttpGet (Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request)
{
.... methd body in here
}
And I try to access with a URL like:
http://somehost/pca/api/MarketReportInstancesTable/pagedata
Which DOES NOT work, but
http://somehost/pca/api/MarketReportInstancesTable/
DOES WORK.
My question would be, why does the route do that? I want to have many paths in the same WebAPI controller.
Am I approaching it wrong?
You have no route template in the route. You only have a route name
Route names can be used to generate a URL based on a specific route. Route names have no impact on the URL matching behavior of routing and are only used for URL generation. Route names must be unique application-wide.
emphasis mine
//GET api/MarketReportInstancesTable/pagedata
[HttpGet ("pagedata", Name ="PageData")]
public IActionResult PageData([FromQuery] IDataTablesRequest request) {
//.... methd body in here
}
Using [HttpGet] without a route template is the same as [HttpGet("")] which will map to the root of the controller with route prefix.
This explains why your root call works.
Reference Routing in ASP.NET Core
Reference Routing to Controller Actions

WebAPI does not instantiate objects when using Attribute Routing

I have controller with Attribute routing similar to this.
[RoutePrefix("api/v1/user")]
public class UserController : ApiController
{
[Route("")]
[HttpGet]
public HttpResponseMessage Get([FromUri] UserModel user)
{
return Request.CreateResponse(HttpStatusCode.OK, user)
}
}
If I send in no parameters to this controller, "user" is null. But if I remove the attribute routing from the controller/method, the "user" object is instantiated with default values.
I read from previous posts that JSON.net will instantiate these objects by default but what I cannot find is why attribute routing removes this functionality.

Categories