I came across this issue by accident. I have this route
config.Routes.MapHttpRoute(
name: "RecycleCenters",
routeTemplate: "api/cars/{id}",
defaults: new { controller = "rc", id = RouteParameter.Optional }
);
and I have a controller like this
public class CarsController : ApiController
{
public IEnumerable<Car> Get() { ... }
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post(Car car) { ... }
public HttpResponseMessage Put(int id, Car car) { ... }
public HttpResponseMessage Delete(int id) { ... }
}
Basically what's happening is that my routing is allowing to make a POST request to an endpoint like this /api/cars/id, when it shouldn't because to create a new car the request should be to this endpoint /api/cars
I've found an answer for my issue in this link but I want to know if the newer version of Web API has something already built in to prevent this scenario, if so then how to use it?
Thanks
If you use attribute routing you avoid this problem completely. Instead of defining your routes with config.Routes.MapHttpRoute you use config.MapHttpAttributeRoutes() and then place attributes on your controller methods:
public class CarsController : ApiController
{
[HttpGet]
[Route("api/cars")]
public IEnumerable<Car> Get() { ... }
[HttpGet]
[Route("api/cars/{id}")]
public HttpResponseMessage Get(int id) { ... }
[HttpPost]
[Route("api/cars")]
public HttpResponseMessage Post(Car car) { ... }
[HttpPut]
[Route("api/cars/{id}")]
public HttpResponseMessage Put(int id, Car car) { ... }
[HttpDelete]
[Route("api/cars/{id}")]
public HttpResponseMessage Delete(int id) { ... }
}
You can also add a RoutePrefix attribute at the controller level to avoid duplicating some of the information in the route on each controller. You can also avoid placing the Http verb attributes if you name your methods as you did, with the method name as the verb, but I prefer to use the attribute for each method for consistency.
This is a good article that talks about attribute routing versus convention routing. It talks specifically about MVC, but it applies to web api as well.
Hope that helps.
Related
I am debugging someone elses code that uses WebAPI. The WebApiConfig looks like this:
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/{controller}/{id}",
defaults: new {id = RouteParameter.Optional});
The service method that gets called looks like this:
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
}
The above code works, and the controller is correctly called when a request is made. But how does the routing work when attributes are not present in the controller?
edit 1:
Let's say I create a new controller called PlantController:
public class PlantController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
}
When I call the web service like this:
api/v2/plant/test
The PlantController is not called. However, when I call the device service, it works:
api/v2/device/test
ID is optionnal. So if you call this king of route : api/v2/device it will try to call a controller action with this signature :
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
// do stuff here...
}
}
This rule is true for all HTTP words ( Post, Put etc... )
One use of the http verb attributes is a quasi overload based on the http verb.
Image this function.
public class DeviceController : ApiController
{
[HttpGet]
public IHttpActionResult Get(string id)
{
// do stuff here...
}
public IHttpActionResult Get()
{
// do stuff here...
}
}
In this example the Get(string id) overload of Get will be called when the http request type is a GET, all non GET request types will be routed to the other Get() method.
I have this configuration in the HttpConfiguration
config.Routes.MapHttpRoute("Default", "api/{controller}");
config.Routes.MapHttpRoute("Another", "api/{controller}/{action}");
config.Routes.MapHttpRoute("WithKey", "api/{controller}/{action}/{key}");
For that reason I cannot access my controller like this
http://<host>/api/products (works)
http://<host>/api/products/1 (doesn't work)
So I added the annotation Route in the get method but it doesn't work
[RoutePrefix("products")]
public class ProductsController : ApiController
{
[HttpGet]
public IQueryable<IProduct> GetProducts()
{
return db.GetProducts();
}
//[Route("products/{productID}")] Tried. Doesn't work
//[Route("{productID:int}")] Tried. Doesn't work
[HttpGet]
public IProduct GetProduct(int productID)
{
return db.GetProduct(productID);
}
}
The only way to make it work is typing the address like this http://<host>/api/products?productID=1, but I'd really want to access with this url http://<host>/api/products/1.
I can add new routes in the http configuration but cannot modify the existing ones. And I don't want to affect existing controllers.
How can I solve this, please?
First ensure that attribute routing is enabled before convention-based routes.
config.MapHttpAttributeRoutes();
//...convention-based routes.
config.Routes.MapHttpRoute("Default", "api/{controller}");
//...other code removed for brevity
Next you want to update the attribute routes.
[RoutePrefix("api/products")]
public class ProductsController : ApiController {
//GET api/products
[HttpGet]
[Route("")]
public IQueryable<IProduct> GetProducts() {
return db.GetProducts();
}
//GET api/products/1
[HttpGet]
[Route("{productID:int}")]
public IProduct GetProduct(int productID) {
return db.GetProduct(productID);
}
}
I am doing a Web API 2 application and I have controller named NCT_ProcessSettings and already I have two GET methods as below.
1. public IEnumerable<Process_Settings> Get()
2. public HttpResponseMessage Get(int id)
Now I want to have third one as below (Same as first one but inside I will write different logic).
3. public IEnumerable<Process_Settings> Get() //Compiler will confuse which to pick?
I tried as below.
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings()
{
return entityObject.NCT_Process_Settings.Where(c => c.project_id == 0).ToList();
}
Below is my angularcode to call api.
var url = '/api/NCT_ProcessSettings/GetGlobalSettings';
May I have some idea how to fix this? Any help would be appreciated?
Enable attribute routing in WebApiConfig.cs before convention-based routes.
config.MapHttpAttributeRoutes();
Next update controller to use routing attributes. (note the route prefix)
[RoutePrefix("api/NCT_ProcessSettings")]
public class NCT_ProcessSettingsController : ApiController {
//GET api/NCT_ProcessSettings
[HttpGet]
[Route("")]
public IEnumerable<Process_Settings> Get() { ... }
//GET api/NCT_ProcessSettings/5
[HttpGet]
[Route("{id:int}")]
public HttpResponseMessage Get(int id) { ... }
//GET api/NCT_ProcessSettings/GetGlobalSettings
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings() { ... }
}
Read up more documentation here Attribute Routing in ASP.NET Web API 2
Used Action Name attribute
[ActionName("Get")]
public IEnumerable<Process_Settings> Get1()//used any name here
{
}
I have five actions in my web api controller as follows.
http://localhost:1234/products - to map getallproduct action
http://localhost:1234/products/1 - to map getproductnyid action
http://localhost:1234/products - saveproduct action (post)
http://localhost:1234/products/1/getcolor - getproductcolorbyid action
http://localhost:1234/products/1/getcost - getproductcostbyid action
I need only one custom routing url for this.
I have tried following routing but it appends action name in url(http://localhost:24007/product/GetProductByID/Id) which i don't want.
config.Routes.MapHttpRoute(
name: "ProductRoute",
routeTemplate: "product/{action}/{productId}",
defaults: new { controller = "Product", productId= RouteParameter.Optional }
);
You have to use Attribute Routing if you want this kind of flexibility:
[RoutePrefix("products")]
public class ProductsController : ApiController {
[HttpGet]
[Route("")]
public IHttpActionResult GetAllProduct()
{
//...
}
[HttpGet]
[Route("{productId}")]
public IHttpActionResult GetProductById(int id)
{
//...
}
[HttpPost]
[Route("")]
public IHttpActionResult SaveProduct(Product product)
{
//...
}
[HttpGet]
[Route("{productId}/getcolor")]
public IHttpActionResult GetProductColorById(int id)
{
//...
}
[HttpGet]
[Route("{productId}/getcost")]
public IHttpActionResult GetProductCostById(int id)
{
//...
}
}
And remember to register them in your HttpConfiguration object:
config.MapHttpAttributeRoutes();
As an aside: if you are designing a RESTful API (which it seems to me) I strongly suggest you to avoid using RPC-like actions in your URIs (e.g. never use URI segments like getcolor, getcost), but use names that conforms with REST constraints:
http://localhost:1234/products/1/color
http://localhost:1234/products/1/cost
This can be achieved by changing your RouteAttributes:
[Route("{productId}/color")]
//...
[Route("{productId}/cost")]
I am having a problem when trying to have two different GET methods, one for retrieving a number of resources while one is for retrieving a specific resource.
Startup.cs
config.Routes.MapHttpRoute("DefaultAPI",
"api/{controller}/{action}",
new { id = RouteParameter.Optional });
Controller.cs
[RoutePrefix("api/Files")]
public class FileController : ApiController
{
// /api/Files/
[Authorize]
[Route("")]
public IHttpActionResult GetAll()
{
}
// /api/Files/Id/
[Authorize]
[Route("Id")]
public async Task<HttpResponseMessage> Get([FromBody] string id)
{
}
// /api/Files/Upload
[Authorize]
[HttpPost]
[Route("Upload")]
public async Task<HttpResponseMessage> Post()
{
}
// /api/Files/Delete
[Authorize]
[Route("Delete")]
public IHttpActionResult Delete([FromBody] string id)
{
}
This is new to me and I know I am also making a mistake with using both IHttpActionResult as well as HttpResponseMessage but I figured I would change that later on after I figure out the routing.
Error:
When Startup.cs has
"api/{controller}/{action}"
, it returns a 404 Not found,
when it is "api/{controller}/{id}", the error is:
Multiple actions were found that match the request: \r\nGetAll
If you are using attribute-based routing, you need to add the following code before you declare any WebAPI routes in Startup.cs:
config.MapHttpAttributeRoutes();