How to disable REST convention from interfering with my ApiController - c#

This is my api configuration class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
}
}
This is my api controller class:
public class RoleController : ApiController
{
// Some action that works fine...
// Another action that works fine...
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
I am calling my actions using POST and they are working fine.
But, when I try to call the Delete action using POST I get the following error:
405 Method Not Allowed
The requested resource does not support http method 'POST'.
Clearly, this is because ApiController enforces REST convention
which expects DELETE verb for Delete action.
Now, how do I disable this REST convention constraints
and write my actions in a classic manner?

You can use the HttpPostAttribute to enforce the Action to accept only POST:
public class RoleController : ApiController
{
[HttpPost]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}

You may want to keep the REST conventions while allowing certain clients (like HTML forms) to properly use you actions.
So, you can use a combination of HttpPostAttribute and HttpDeleteAttribute or AcceptVerbsAttribute (which allows multiple verbs) to allow multiple verbs:
public class RoleController : ApiController
{
[HttpPost, HttpDelete]
// OR
[AcceptVerbs("DELETE", "POST")
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}

If you don't want magic verbs and magic action names you can use route attributes.
Delete config.Routes.MapHttpRoute and set:
config.MapHttpAttributeRoutes();
Now you have to set the routes manually:
[RoutePrefix("~/Role")]
public class RoleController : ApiController
{
[HttpPost]
[Route("~/Delete")]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}

In your case I'd stop using any kind of REST conventions.
Instead of having a Delete method on the Role controller you can have a DeleteRole method and allow POST on it. This way nothing will interfere with what you want to do. Nothing forces you to build a REST api if that's not what you need.
There are several things you could do to still build a nice api.
For example, you could return an IHttpActionResult
your method could look like this:
public class RoleController : ApiController
{
[HttpPost]
public IHttpActionResult DeleteRole([FromBody]int RoleID)
{
if ( RoleID <= 0 )
{
return BadRequest();
}
var deleteResult = RoleBL.Delete(RoleID);
return Ok(deleteResult);
}
}
You still return the same object but it's wrapped inside an object with a proper http code so your code which deals with the result, won't change much.

Related

How are WebAPI routes defined when the controller has no attributes?

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.

How to override web api route on the same method with annotations

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);
}
}

How to put multiple GET methods in Web API2 controller?

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
{
}

Calling Methods from one API is done but facing Not found error on second API

I have two Wep APIs. I have done CRUD operation using one eg. Customer. But when I built another Similar Web API and called a method It shows:
{,…} Message: "No HTTP resource was found that matches the request URI
http://localhost:23995/Product/Insert'."
MessageDetail: "No route providing a controller name was found to
match request URI '[[same link as above here]]'"
Here is my JS Calling Method:
$scope.Insert = function () {
$http({
method: 'post',
url: 'http://localhost:23995/Product/Insert',
data: JSON.stringify($scope.Product)
}).then(function (response) {
alert("chec");
});
}
In Product Controller
// Insert
[HttpPost]
[Route("{controller}/Insert")]
public string Insert([FromBody] Product newProd) {
newProd.Insert();
return newProd.DbResponse;
}
In supplier Controller
// Insert
[HttpPost]
[Route("{controller}/Insert")]
public string Insert([FromBody] Product newProd) {
newProd.Insert();
return newProd.DbResponse;
}
Assuming you already have attribute routing enabled.
Attribute Routing in ASP.NET Web API 2
To enable attribute routing, call MapHttpAttributeRoutes during
configuration. This extension method is defined in the
System.Web.Http.HttpConfigurationExtensions class.
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
and assuming that given the route you are getting the error on
http://localhost:23995/Product/Insert
Your product controller should look something like this.
[RoutePrefix("product")]
public class ProductController : ApiController {
// ... other code removed for brevity
// Insert
// eg: POST /product/insert
[HttpPost]
[Route("insert")]
public string Insert([FromBody] Product newProd) {...}
}
and your supplier controller would look very similar
[RoutePrefix("supplier")]
public class SupplierController : ApiController {
// ... other code removed for brevity
// Insert
// eg: POST /supplier/insert
[HttpPost]
[Route("insert")]
public string Insert([FromBody] Product newProd) {...}
}
you calling JavaScript should then be able to properly call the desired methods
Do you have a controller named "ProductController" with a method named Insert?
Looks like that's all that is missing for you.

ASP.NET Web API 2 Setting Controller Routes

I have been scratching my head for a whole day since I cannot figure out what is wrong in my code.
First, I have a working controller as this one:
namespace MyProject.Controllers {
[RoutePrefix("api/Account")]
public class AccountController : ApiController {
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(AccountModel model) {
...
return Ok();
}
}
}
However, when I add a new controller and introduce a new route, problem arises.
The new controller is as follows:
namespace MyProject.Controllers {
[RoutePrefix("api/Admin")]
public class AdminController : ApiController {
[AllowAnonymous]
[Route("Encrypt")]
public IHttpActionResult Encrypt(string clientSecret) {
...
return Ok();
}
}
}
Then I make the request via Postman like this:
Unfortunately, an error is returned:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost/api/admin/encrypt'."
}
I am using the default route mapping configuration defined in WebApiConfig.cs:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Can anyone shed some light on it?
UPDATE
If I send the POST request in this way, I can get the Ok() response successfully.
I have a question now. Why can I only pass the parameter via the URL but not with a form object?
To call with the parameter, include parameter in route attribute and add POST verb as follows:
[Route("Encrypt/{clientSecret}")]
[HttpPost]
public IHttpActionResult Encrypt(string clientSecret) {
...
return Ok();
}
Then call as follows:
http://localhost/api/admin/encrypt/123456
However convention for post methods is without the parameter as follows:
[Route("Encrypt")]
[HttpPost]
public IHttpActionResult Encrypt() {
...
return Ok();
}
}
Then call as follows:
http://localhost/api/admin/encrypt
If you do not want to add the POST verb, you can prefix the methods with the POST verb as follows:
public IHttpActionResult PostEncrypt(string clientSecret)
public IHttpActionResult PostEncrypt()
I suggest the slight change in your route definition:
api/{controller}/{action}/{id}
The reason, imo, is that Web api actions default to the standard accept verbs.
Then you can name your api action methods anyhow, only decorate them with the relevant accept attribute.

Categories