I'm trying to add a new Web API in a legacy web with VB.NET and .NET Framework 4.8. It has multiple Web API running correctly.
For example - CalendarController.vb
:
<RoutePrefix("api/{Controller}")>
Public Class CalendarController
Inherits ApiController
<HttpGet>
<Route("{id:int}")>
Public Function GetCalendarEvent(id As Integer) As IHttpActionResult
...
Return Ok(...)
End Function
End Class
StockController.vb
:
<RoutePrefix("api/{Controller}")>
Public Class StockController
Inherits ApiController
<HttpGet>
<Route("{friendlyURL}")>
Public Function GetProductStock(friendlyURL As String) As IHttpActionResult
...
Return Ok(...)
End Function
End Class
Global.asax.vb
:
Sub Application_Start(sender As Object, e As EventArgs)
With RouteTable.Routes
.MapHttpRoute(name:="Calendar.GetNextCalendarEvents", routeTemplate:="api/{Controller}/Next/{maxEvents}")
.MapHttpRoute(name:="Calendar.GetMonthEntries", routeTemplate:="api/{Controller}/{month}/{year}")
.MapHttpRoute(name:="Calendar.GetCalendarEvent", routeTemplate:="api/{Controller}/{id}")
.MapHttpRoute(name:="Map.GetMarkerById", routeTemplate:="api/{Controller}/{markerId:int}")
.MapHttpRoute(name:="Map.GetAllMarkers", routeTemplate:="api/{Controller}")
.MapHttpRoute(name:="Stock.GetProductStock", routeTemplate:="api/{Controller}/{friendlyURL}")
'.MapHttpRoute(name:="Stock.GetProductStock", routeTemplate:="api/{Controller}/{friendlyURL}", defaults:=Nothing, constraints:=New With {.friendlyURL = "^[a-zA-Z]+[a-zA-Z0-9,_ -]*$"})
End With
End Sub
Testing both APIs:
const response = await fetch(`/api/Calendar/3050`);
const event = await response.json();
console.log(event);
//Returns 200 with an object correctly
{ id: 3050, title: '...', ... }
const response = await fetch(`/api/Stock/my-product`);
const product = await response.json();
console.log(product);
//Cannot access the API
GET https://localhost:44330/api/Stock/my-product 404 (Not Found)
{ Message: "No HTTP resource was found that matches the request 'https://localhost:44330/api/Stock/my-product'.",
MessageDetail: "No action was found on the controller 'Stock' that matches the request." }
const response = await fetch(`/api/Stock?friendlyURL=my-product`);
//Returns 200 Http Code and correct stock.
I cannot find out the differences.
EDIT: It runs when parameter is passed as query string
Thank you!
I believe the request isn't getting to correct route registration. In the code below is a comment abiv ethe line I suspect is trying to route the request.
Sub Application_Start(sender As Object, e As EventArgs)
With RouteTable.Routes
.MapHttpRoute(name:="Calendar.GetNextCalendarEvents", routeTemplate:="api/{Controller}/Next/{maxEvents}")
.MapHttpRoute(name:="Calendar.GetMonthEntries", routeTemplate:="api/{Controller}/{month}/{year}")
' I'd guess this registration is trying to route the request
.MapHttpRoute(name:="Calendar.GetCalendarEvent", routeTemplate:="api/{Controller}/{id}")
.MapHttpRoute(name:="Map.GetMarkerById", routeTemplate:="api/{Controller}/{markerId:int}")
.MapHttpRoute(name:="Map.GetAllMarkers", routeTemplate:="api/{Controller}")
.MapHttpRoute(name:="Stock.GetProductStock", routeTemplate:="api/{Controller}/{friendlyURL}")
'.MapHttpRoute(name:="Stock.GetProductStock", routeTemplate:="api/{Controller}/{friendlyURL}", defaults:=Nothing, constraints:=New With {.friendlyURL = "^[a-zA-Z]+[a-zA-Z0-9,_ -]*$"})
End With
End Sub
The way to make it work is to order route registrations from the most specific to most general. I would personally leverage of default values and explicit route values instead of using {Controller}
replacement for specific route templates.
Sub Application_Start(sender As Object, e As EventArgs)
With RouteTable.Routes
.MapHttpRoute(name:="Calendar.GetNextCalendarEvents", routeTemplate:="api/Calendar/Next/{maxEvents}", defaults:=New With {.controller = "Calendar", .action = "GetNextCalendarEvents")
.MapHttpRoute(name:="Calendar.GetMonthEntries", routeTemplate:="api/Calendar/{month}/{year}", defaults:=New With {.controller = "Calendar", .action = "GetMonthEntries"))
.MapHttpRoute(name:="Map.GetAllMarkers", routeTemplate:="api/Map", defaults:=New With {.controller = "Map", .action = "GetAllMarkers"))
.MapHttpRoute(name:="Map.GetMarkerById", routeTemplate:="api/Map/{markerId:int}", defaults:=New With {.controller = "Map", .action = "GetMarkerById"))
.MapHttpRoute(name:="Stock.GetProductStock", routeTemplate:="api/Stock/{friendlyURL}", defaults:=New With {.controller = "Stock", .action = "GetProductStock"))
' Here I see eventual problem when a controller will have two actions with parameter id. It will throw an exception as it wouldn't know which one to choose
.MapHttpRoute(name:="Default", routeTemplate:="api/{Controller}/{id}")
End With
End Sub
I think this will do the trick. I would consider reviewing ASP.NET MVC Routing Overview (VB) and maybe adding default route from the overview.