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.
Related
I am using Owin self-hosting with WebAPI2.
I have two controller classes and using attribute routing.
One of them has following signature:
[RoutePrefix("api/v1/devices")]
public class DeviceController : ApiController
{
[HttpPost]
[Route("")]
public async Task<HttpResponseMessage> DevicePresence()
{
...
}
[HttpGet]
[Route("updates/{deviceID}")]
public HttpResponseMessage GetDeviceUpdates(string deviceID)
{
...
}
}
This one is working fine and action methods get triggered.
The other Controller has below signature:
[RoutePrefix("device/class")]
public class RemoteController : ApiController
{
[HttpGet]
[Route("remotehost")]
public HttpResponseMessage RemoteHost()
{
...
}
[HttpGet]
[Route("version")]
public HttpResponseMessage GetVersion()
{
...
}
}
When I try to connect to any of these I get 503 (Service Unavailable) response.
My Startup class is initialized as below:
public class Startup
{
public static void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.EnsureInitialized();
app.UseWebApi(config);
}
}
I tried to use the conventional routing as well but still the same problem.
config.Routes.MapHttpRoute(
name: "RemoteApi",
routeTemplate: "device/{controller}/{action}"
);
public class ClassController : ApiController
{
public HttpResponseMessage GetVersion()
{
...
}
}
This is also throwing 503 status code
If I change the Route prefix in the second controller as below then it's working:
[RoutePrefix("api/v1/device/class")]
public class RemoteController : ApiController
{
...
}
As per my requirement, I couldn't change the endpoints.
I am not sure what's wrong here and any help would be much appreciated.
Your RemoteController throws an exception because you do not follow naming conventions of WEB API.
Your RemoteHost() method which is a get method has to have a "Get" prefix, so its name is supposed to actually be GetRemoteHost().
That should solve it.
In case you want to alter the naming conventions, you can modify route definitions in the global.asax file:
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
Hope that helped.
I realized it was not a problem with webapi or routing.
Actually, I was adding a firewall exception for the URL's and somehow it was not getting removed from firewall settings and keeps an entry in DACL.
I removed this from cmd prompt and now everything works fine.
Sorry for bothering...
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 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();
I have two API endpoints defined as such:
[Route("create")]
[HttpPost]
[ResponseType(typeof(User))]
public async Task<IHttpActionResult> CreateUser(User user)
[Route("login")]
[HttpPost]
[ResponseType(typeof (User))]
public async Task<IHttpActionResult> Login(string email, string password)
And the controller defined as
[RoutePrefix("api/users")]
public class UserController : ApiController
When I call it with this information (both in plain chrome, my app and the Postman application)
POST /api/users/login HTTP/1.1
Host: mysite.azurewebsites.net
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
email=somemail&password=somepw
I receive a 404:
{
"Message": "No HTTP resource was found that matches the request URI 'http://mysite.azurewebsites.net/api/users/login'.",
"MessageDetail": "No action was found on the controller 'User' that matches the request."
}
It does work for another route which I can call with /api/users/1:
[Route("{id:int}")]
[HttpGet]
[ResponseType(typeof(User))]
public async Task<IHttpActionResult> GetUser(int? id)
Can't I explicitly define such endpoints? I tried creating a custom route but this made no difference (I placed this before the default route and after calling config.MapHttpAttributeRoutes()).
config.Routes.MapHttpRoute(
name: "Login",
routeTemplate: "api/users/login",
defaults: new { controller = "User", action = "Login" }
);
Note that explicitly defining the route as [Route("~api/users/login")] didn't work either.
I have also noticed that routes in my other controller don't seem to work anymore. More specifically I have these definitions:
[RoutePrefix("api/movies")]
public class MovieController : BaseController
[Route("~api/genres")]
[HttpGet]
[ResponseType(typeof(IEnumerable<Genre>))]
public IHttpActionResult GetGenres()
[Route("~api/genres/{id:int}")]
[HttpGet]
[ResponseType(typeof(IEnumerable<MovieResult>))]
public IHttpActionResult GetMoviesForGenre(int id)
[Route("{id:int}")]
[HttpGet]
[ResponseType(typeof(Movie))]
public IHttpActionResult GetMovieDetails(int id)
Of these options, only a call to /api/movies/16 succeeds, the others return
No type was found that matches the controller named 'genres'.
Is there something elementary I'm overlooking?
I have made the genre routes available again by changing them to genres and genres/{id:int} and adding this route
config.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
but I would assume that this shouldn't be necessary. For some reason the request to /api/movies/genres works while /api/users/login doesn't. I did notice that creating a GET method with URI /api/users/genres DOES work so I believe it must have something to do with that. Why won't it find my POST-methods?
It looks like there are lots of moving pieces here so it's hard to figure out exactly what will fix all the problems. But here are a couple of issues to address...
Web API (unlike MVC) can only have one parameter read from the request body. So to make your Login action work, try to create a LoginInfo class...
public class LoginInfo
{
public string email { get; set; }
public string password { get; set; }
}
And change the Login method to...
public async Task<IHttpActionResult> Login([FromBody]LoginInfo loginInfo)
The issues with the genres appear to be the incorrect usage of ~ in the attribute routing (should be ~/). Try...
[Route("~/api/genres")]
and
[Route("~/api/genres/{id:int}")]