I've been following along with a tutorial here to get my head around authentication in Web API with OAuth.
I have worked on Web API before, where I named the methods to start with Get, Put, Post etc. in order that they were routed to depending on the http verb. I also know that actions can be decorated with attributes ([HttpGet] etc) to denote the verb that maps to them.
In the tutorial, there is an action on a controller that looks like this:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(UserModel userModel)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
IdentityResult result = await _repo.RegisterUser(userModel);
IHttpActionResult errorResult = GetErrorResult(result);
if(errorResult != null)
return errorResult;
return Ok();
}
This method, as the comment suggests, responds to POST requests. I cannot see how the Web API knows that this action is for POST. Can anyone enlighten me?
If you look at the documentation for Web API Routing and Action Selection:
...
HTTP Methods. The framework only chooses actions that match the HTTP method of the request, determined as follows:
You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
If none of the above, the method supports POST.
...
and the source for ReflectedHttpActionDescriptor.cs (line number 294-300):
...
if (supportedHttpMethods.Count == 0)
{
// Use POST as the default HttpMethod
supportedHttpMethods.Add(HttpMethod.Post);
}
return supportedHttpMethods;
...
You'll find your answer:
POST is the default HTTP Verb for action method in Web API.
Also, if you search a bit more on SO, You'll find the following question:
Is there a default verb applied to a Web API ApiController method?
Although it's a different question, but the problem is basically the same as yours.
Related
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status303SeeOther)]
[HttpPost]
[Route("RegisterUsers")]
public async Task<ActionResult<List<UsersInfo>>> RegisterUsers(List<UsersInfo> Users)
{
// .. how to detect errors here ...
return Users;
}
How do I get errors here specially when API receive wrong format for UserInfo type in the body?
The method implementation never run in the case of wrong userinfo type.
It depends either it's a Web API or ASP MVC.
Assuming you have Web API, The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response.
Consequently, the following code is unnecessary in an action method:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
The default response type for an HTTP 400 response is ValidationProblemDetails.
In case you need to log such automated responses, you can set the InvalidModelStateResponseFactory to a custom function that first performs the logging and then returns an appropriate BadRequestObjectResult
As an example, you can try to do it like this (see original documentation)
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behaviour, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
// Perform logging here.
// ...
// Invoke the default behaviour, which produces a ValidationProblemDetails
// response.
// To produce a custom response, return a different implementation of
// IActionResult instead.
return builtInFactory(context);
};
});
Well, there are a lot of things you are able to implement. In your case I think you need a kind of Validator to your Endpoint parameters. I suggest to implement something like FluentValidation, so the sender will receive all model errors (if any).
Also you can customize the response in that case.
Here you can find more about it
https://docs.fluentvalidation.net/en/latest/
I am trying to write a simple API te receive POST requests with a body. When I try to test my method it keeps resulting in a 400 bad request in Postman. I first thought the issue was with deserializing the JSON body. So to be sure I stripped out that logic from the controller, but the request still returned a 400 status code.
So I removed everything from my method except for the method itself, only returning Ok('Hello World'); and still the response was a 400.
What I have left for now is this:
[Route("api/v1/service/")]
public class ServiceController : Controller
{
public ServiceController()
{
}
[HttpGet("get")]
public IActionResult Get()
{
return Ok("GET works fine");
}
[HttpPost("post")]
public IActionResult Post()
{
return Ok("Hello World"); // <-- Keeps returning 400
}
}
The GET method works fine, but when I sent an empty POST call to /api/v1/service/post in Postman I get a bad request.
I also noticed that when I change the route to something different or random that does not exists it also gets a 400, instead of a 404.
So making a POST call to api/v1/service/this-route-is-not-defined also results in a bad request.
I keep changing small things in my request form adding/removing ContentType or Accept headers and adjusting my StartUp.cs . But every POST call I make to .NET seems to result in a 400 status code.
Edit
This might be related to the routing in Startup.cs:
app.UseHsts();
app.UseMvc(routes =>
{
});
app.UseRouting();
This is the request in POST man:
GET:
POST:
The code in the sample was offcourse altered from my original API method, but the idea is the same. I copied the sample to a new file in my project and clicked in Postman on create new request. So headers are the default ones.
Your missing MapControllers()
In your startup.cs add MapControllers(), this is required for attribute based routing.
app.MapControllers();
If the version of .NET you are using is < 6.0 then add like so:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
MapControllers is called to map attribute routed controllers.
First of all, the answers and comments given to this question were all helpfull.
I found the culprit. Apperently there was an option enabled in the Startup.cs file that puts an anti-forgery token check on all API calls that can modify stuff, like POST, PUT, DELETE. This is not an issue when calling the API from the frontend with a Javascript fetch() for instance. The token is added to a tag in the document and you can add to the request headers like this:
headers.append('X-XSRF-TOKEN', (document.getElementsByName("__RequestVerificationToken")[0] as any).value)
To be able to make a POST call from Postman for instance you can add this line temporarely above your action.
[IgnoreAntiforgeryToken]
So working example would like this:
[Route("api/v1/service/")]
public class ServiceController : Controller
{
public ServiceController()
{
}
[HttpGet("get")]
public IActionResult Get()
{
return Ok("GET works fine");
}
[IgnoreAntiforgeryToken]
[HttpPost("post")]
public IActionResult Post()
{
return Ok("Hello World"); // <-- Keeps returning 400
}
}
It is important to think about when to use [IgnoreAntiforgeryToken] and not to use it. On methods that allready expect an API key for instance you can use it in a production environment. But when method is public the anti-forgery token is a way of protecting your method from attackers or people/robots trying to spam your API.
I'm developing a REST-Api based on Asp.net Core MVC 1.1.3 If an action has no return type my client library expects a status code 204 (no content)
This is my action:
[HttpPut]
[Route("")]
public async Task Update([FromBody]ApiResourceUpdateDto dto)
{
await _repo.Update(dto.Name, dto.Enabled, dto.DisplayName, dto.Description);
}
As this method doesn't return anything it should return a status code of 204 (no-content). However if I test this method I will get a status code of 200 although the body is empty. I also tried switching to void instead of Task but it didn't change anything.
Request URL:http://localhost:5000/api/apiresource
Request Method:PUT
Status Code:200 OK
Remote Address:[::1]:5000
Referrer Policy:no-referrer-when-downgrade
Is this a bug or am I missing something?
There is the NoContentResult to return HTTP 204 Status Code. Since the rewrite, you have to be a little more explicit with what you are returning because of the merging of the two frameworks from the previous versions. A controller can be either web api or normal MVC so there is no way to automatically infer which type it is.
[HttpPut]
[Route("")]
public async Task<IActionResult> Update([FromBody]ApiResourceUpdateDto dto) {
await _repo.Update(dto.Name, dto.Enabled, dto.DisplayName, dto.Description);
return new NoContentResult();
}
Or you can even use return NoContent();
I have two simple routing methods:
main routing directive:
[RoutePrefix("api/accounts")]
first one
[Route("user/{id:guid}", Name = "GetUserById")]
public async Task<IHttpActionResult> GetUser(string Id)
second one
[Route("user/del/{id:guid}")]
public async Task<IHttpActionResult> DeleteUser(string id)
I wonder why if I test the first method with direct ulr ( GET ) it works:
http://localhost/ReportApp_WebApi/api/accounts/user/1533882b-e1fd-4b52-aeed-4799edbbcda6
And if I try the second link that is just a little different:
http://localhost/ReportApp_WebApi/api/accounts/user/del/1533882b-e1fd-4b52-aeed-4799edbbcda6
I get: The requested resource does not support http method 'GET'.
Can you help me?
The second link is not just a little different. In fact it is pointing to the route that you have defined for your delete method. This method expects DELETE Http verb.
When you write the url directly on the browser it performs a GET request. If you want your delete method to work with a GET verb you have to put the attribte
[HttpGet]
before or just after your Route atttribute. Although this I would not recommend to do it. You can have a HTTP client like Fiddler or Postman to test this
Web Api uses conventions when naming methods in your controllers. Because you named your method DeleteUser, Web Api expects Delete verb.
EDIT>>>Please follow recommendations listed in comments. I have also make bold the part where I do not recommend this
I'm trying to create a REST api based on the Asp.Net MVC4 web api framework. The GET requests are working just fine. All other verbs are getting ignored by the server.
It just says the following:
"No action was found on the controller 'Track' that matches the request."
Though the example error is from the Track controller all other controllers have the same problem.
This is the method on the controller Track I'm trying to call:
[HttpPost]
public Object Index(string token, string juke, string track)
{
}
I'v tried it using a JSON object like so:
{ "track": "0000", "juke": "0000" }
And I tried to use the "normal" way:
track=0000&juke=0000
The '0000' in the examples above are stand-ins for the real id's.
To be sure I'm also posting the Register() from WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Routes.MapHttpRoute(
name: "DefaultIndexBackEnd",
routeTemplate: "back-end/{controller}/{token}",
defaults: new { action = "Index", token = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultBackEnd",
routeTemplate: "back-end/{controller}/{action}/{token}",
defaults: new { token = RouteParameter.Optional }
);
}
Try this in your TrackController. Because you are using multiple parameters, they must be declared as optional.
[HttpPost]
public Object Index(string token="", string juke="", string track="")
{
}
You can make parameter token mandatory because token is declared as optional in routing configuration DefaultIndexBackEnd. I think using [FromBody] attribute is a good idea when there is more than one parameter for POST actions.
First, it's important to understand how a typical REST Web API is supposed to work. Typically, it uses different HTTP verbs (GET, POST, PUT, DELETE) for specific actions.
GET: get an entity (or a collection) from the server
POST: create a new entity
PUT: update an existing entity
DELETE: delete an existing entity
So when I see [HttpPost] on your Index action, it seems to me that the REST API pattern is broken.
Instead, in your controller you should have a Entity Get(int id) action (to get data) and a void Post(Entity entity) action to create new records.
No need to decorate your actions with HttpGet or HttpPost, the MVC Api framework will route the request to your action based on their names.
You can take a look at an example here.
After several hours of trying and researching articles I finally found an article that precisely described my problem! If you're having the same problems, check out the article.
The problem was that I had to use the [FromBody] attribute on one parameter of the of the action. After moving juke and track into a model it finally worked as I had hoped.
Thanks everyone for the help, you all set me on the right track!