I have a .net Core 2 API setup with some test function. (Visual Studio 2017)
Using postman I do a post with the raw data to that method, but the model is just blank? Why?
// POST api/Product/test
[HttpPost]
[Route("test")]
public object test(MyTestModel model)
{
try
{
var a = model.SomeTestParam;
return Ok("Yey");
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
public class MyTestModel
{
public int SomeTestParam { get; set; }
}
You need to include the [FromBody] attribute on the model:
[FromBody] MyTestModel model
See Andrew Lock's post for more information:
In order to bind the JSON correctly in ASP.NET Core, you must modify your action to include the attribute [FromBody] on the parameter. This tells the framework to use the content-type header of the request to decide which of the configured IInputFormatters to use for model binding.
As noted by #anserk in the comments, this also requires the Content-Type header to be set to application/json.
To add more information to the accepted answer:
There are three sources from which parameters are bound automatically without the use of an Attribute:
Form values: These are form values that go in the HTTP request using
the POST method. (including jQuery POST requests).
Route values: The set of route values provided by Routing
Query strings: The query string part of the URI.
Note that Body is NOT one of them (though I think it should be).
So if you have values that need to be bound from the body, you MUST use the attribute binding attribute.
This tripped me up yesterday as I assumed that parameters from the Body would be bound automatically.
The second minor point is that only one parameter can be bound to the Body.
There can be at most one parameter per action decorated with [FromBody]. The ASP.NET Core MVC run-time delegates the responsibility of reading the request stream to the formatter. Once the request stream is read for a parameter, it's generally not possible to read the request stream again for binding other [FromBody] parameters.
Thus if there is more than one parameter you need, you need to create a Model class to bind them:
public class InputModel{
public string FirstName{get;set;}
public string LastName{get;set;}
}
[HttpPost]
public IActionResult test([FromBody]InputModel model)...
The Docs
Alternatively, add the [ApiController] attribute to your controller.
This has annoyingly affected me so many times (months apart) that I want this answer visible.
I deal with this issue for some hours. This problem stems from several reasons. Let's consider the request is Reactjs (javascript) and backend (API) is Asp .Net Core.
in the request, you must set in header Content-Type:
Axios({
method: 'post',
headers: { 'Content-Type': 'application/json'},
url: 'https://localhost:44346/Order/Order/GiveOrder',
data: order,
}).then(function (response) {
console.log(response);
});
and in the backend (Asp .net core API) u must have some setting:
1. in Startup --> ConfigureServices:
#region Allow-Orgin
services.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin());
});
#endregion
2. in Startup --> Configure before app.UseMvc() :
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
3. in the controller before action:
[EnableCors("AllowOrigin")]
In my case I had { get; set; } missing in my .cs model which results in an object with all members null on POST.
Related
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 saw some code like this:
[HttpPost]
[Consumes("application/json")]
public string SaveProductJson(ProductBindingTarget product) {
return $"JSON: {product.Name}";
}
[HttpPost]
[Consumes("application/xml")]
public string SaveProductXml(ProductBindingTarget product) {
return $"XML: {product.Name}";
}
I get the idea of how Consumes filter work, but a little bit confused about how it work internally. Below is the picture from MSDN:
From my understanding, the routing middleware will select the matching action method. Let's say I post a json document to the application, so both SaveProductJson and SaveProductXml match the request (because they have the same routing template [HttpPost]) and Consumes filter hasn't kicked in yet (filters run in endpoint middleware), since Consumes filter runs after routing middleware, how does Consumes filter tell the routing middleware to select SaveProductJson action method?
The Consumes attribute works together with Content-Type header. In your case application/json or application/xml, but it also supports other content types such as application/x-www-form-urlencoded if you want to submit a form.
The Consumes attribute allows an action to limit the supported request content types. Apply the Consumes attribute to an action or controller, specifying one or more content type.
Read more here
The ConsumeAttribute class inherits from IResourceFilter, which is an extensible hook in the MVC pipeline that is called from Controller/Page implementations of ResourceInvoker.
When the ResourceInvoker is executing during the middleware pipeline, it calls the OnResourceExecuting method of the ConsumesAttribute if it has been previously discovered on target actions.
This method then checks the incoming Content-Type header of the request and compares it
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Only execute if the current filter is the one which is closest to the action.
// Ignore all other filters. This is to ensure we have a overriding behavior.
if (IsApplicable(context.ActionDescriptor))
{
var requestContentType = context.HttpContext.Request.ContentType;
// Confirm the request's content type is more specific than a media type this action supports e.g. OK
// if client sent "text/plain" data and this action supports "text/*".
//
// Requests without a content type do not return a 415. It is a common pattern to place [Consumes] on
// a controller and have GET actions
if (!string.IsNullOrEmpty(requestContentType) && !IsSubsetOfAnyContentType(requestContentType))
{
context.Result = new UnsupportedMediaTypeResult();
}
}
}
In addition, the attribute also inherits from IActionConstraint which will call the Accept method of targetable constraints in the ActionConstraintMatcherPolicy. This component determines the appropriate target action, based on the matching policies against the content-type which is provided by the ConsumeAttribute.Accept method.
I have the following WEB API method, and have a SPA template with Angular:
[HttpPost]
public IActionResult Post([FromBody]MyViewModel model)
I thought, based on this topic, there is no need to use [FromBody] here, since I want to read the value from the message body, so there is no need to override the default behavior, but, if I don't use [FromBody], the model that is coming from Angular is null. I'm really confused, why should I use [FromBody], since I have used the default behavior?
For anyone seeing this issue .net core 3 - you need to add the [ApiController] to the controller where you extend ControllerBase.
The [FromBody] is only needed if you're doing an MVC controller.
This causes the body to get automatically processed in the way you're expecting.
Microsoft documentation for the ApiController attribute
The question you linked to is referring to web-api. You are using core-mvc which has been re-written to merge the pipelines for the previous mvc and web-api versions into one Controller class.
When posting json (as apposed to x-www-form-urlencoded), the [FromBody] attribute is required to instruct the ModelBinder to use the content-type header to determine the IInputFormatter to use for reading the request.
For a detailed explanation of model binding to json in core-mvc, refer Model binding JSON POSTs in ASP.NET Core.
And here's an alternate approach assuming you need to support both [FromForm] and [FromBody] in your Controller APIā¦
Front-End (Angular Code):
forgotPassword(forgotPassword: ForgotPassword): Observable<number> {
const params = new URLSearchParams();
Object.keys(forgotPassword).forEach(key => params.append(key, forgotPassword[key]));
return this.httpClient.post(`${this.apiAuthUrl}/account/forgotpassword`, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
}
Back-End (C# Code):
[AllowAnonymous]
[HttpPost("[action]")]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model) { }
Now your signature can remain the same so it can support both.
And another more permanent approach I thought about while addressing.
https://benfoster.io/blog/aspnet-core-customising-model-binding-conventions.
Hope it helps someone!
See my discussion https://stackoverflow.com/a/75263628/5555938 on [FromBody]. It explains everything in great detail!
But in summary, [FromBody] does NOT accept HTML Form field name-value pairs like [FromForm]. It does NOT accept a traditional HTML form submission! It requires the following:
JavaScript POST Request manually sent to the Web API server
JavaScript Http Header with JSON mime-type attached
JavaScript Http Body with form field extracted data, reformatted and submitted as JSON. Traditional HTML POST name-value pairs will not work!
I have a method as described below which get user as parameter.
To send the user parameter values, I am using postman request/response tool.
My question is, if the request already have user parameter in body request (see at the postman print screen ) why do I need to directly specify [FromBody] in action controller? When this attribute removed, parameter send with null values.
Thanks
[HttpPost("register")]
public IActionResult Register([FromBody]User user)
{
//.. code here
}
public class User
{
public string Name { get; set; }
public string Password { get; set; }
}
The [FromBody] directive tells the Register action to look for the User parameter in the Body of the request, rather than somewhere else, like from the URL. So, removing that confuses your method, and that's why you see null values as it's not sure where to look for the User parameters.
See: https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for more information/learning.
For Model Binding in ASP.NET Core, MVC and Web APi uses the same model binding pattern.
There are default model bindings like Form Values, Route values and Query Strings. If these bindings fails, it will not throw an error, and return null.
If you do not want to add [FromBody], you could try Form Values binding and send request from Postman like below:
[FromBody] will override default data source and specify the model binder's data source from the request body.
You could refer Model Binding in ASP.NET Core for detail information
the attribute [FromBody] ensures the API reads the JSON payload in the postman body. For more info, read
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
I have several Odata(4.0)/Rest controllers in my project(c#/.net 4.62). All but 1 work as expected. This is the controller that returns a 406 every time I call it.
[RoutePrefix("api/SecurityConfig")]
[ApiExplorerSettings(IgnoreApi = false)]
public class SecurityConfigController : ODataController
{
[Route("GetRoleGroupWithRoles")]
[EnableQuery(MaxExpansionDepth = 6)]
[HttpGet]
public virtual async Task<IHttpActionResult> GetRoleGroupWithRoles([FromODataUri] Guid key)
{
return Ok("Hello World");
}
}
I call using the following headers:
Accept: application/* (though I've tried just "*" and "application/json")
Content-Type: application/json
I'm baffled why this controller is not working and the rest of them are......thanks in advance for any thoughts on how I can figure this out.
I think you are missing the first two steps of building an OData service. ODataController, as the name says, only works with OData routes. You need to build an EDM model representing your OData service, and, add an OData route exposing that EDM model. Refer to this official documentation and blog post for details on how to build OData services. [source]:How can I avoid a 406 when receiving an OData.PageResult<T>?