38

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?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • You have to use `[FromBody]` if you are using HttpPost from your angular side to call the api in .net Core – Niladri Jun 09 '18 at 06:58
  • 1
    @Niladri OK. I know. But Why? –  Jun 09 '18 at 06:59
  • Check the link given by stephen below ... it's due to json model binding in .NET Core. For query string you have to use `[FromQuery]` with httpget. – Niladri Jun 09 '18 at 07:00
  • 1
    In ASP.NET Core 2.1, this attribute may be optional if it's clear the body is assigned to that parameter in a Post. – Ray Jun 09 '18 at 07:02

4 Answers4

40

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

Community
  • 1
  • 1
Rob
  • 663
  • 8
  • 8
  • I have seen the same problem in two projects, is there something I missed in the migration documentation from 2.1 -> 2.2 -> 3.0 -> 3.1 – Mathias Haugsbø Dec 11 '19 at 13:55
  • are it mean i need add [ApiController] in any route O_o it's so much!( – xSx Dec 19 '19 at 15:37
  • 1
    I was looking all over for this! Thanks. I have a .NET Core Web API 3.1 project and didn't add [ApiController] to my controller. Then I needed [FromBody]. Added [ApiController] now it works without [FromBody]. – duyn9uyen Apr 01 '20 at 21:43
  • I owe you a complete-day! You've saved me!! – Onur Omer Dec 29 '20 at 16:56
  • This is not correct. It has to do with which content-type is being used. 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. https://andrewlock.net/model-binding-json-posts-in-asp-net-core/ –  Feb 02 '21 at 18:15
13

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.

2

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!

Tim Harker
  • 2,367
  • 1
  • 15
  • 28
1

See my discussion https://stackoverflow.com/a/75263628/5555938 on [FromBody]. It explains everything in great detail!

But in summary...

Angular, like all JavaScript API's, does NOT post traditional HTML form field data (name-value pairs in the HttpRequest body). These JavaScript API's use XMLHttpRequest-type POSTS that supersede the traditional HTML browser-based form POST and send data directly in a special JSON Content-type message directly to the server.

What Angular people do not tell you is that this is not HTML-type <form> data POST's. Angular hijacks all form field post calls in the web browser and sends its own data to the server.

So many modern Web API endpoints are not set up to receive these Angular HttpRequests as special POST JSON formats without additional "hints" as to what type of modeled data they are receiving. Remember JSON is just a plain text string, only identified as "special text" via the JSON mime-type or Content-type Http Header information attached with the data saying its JSON object notation! JSON objects are just data packaged into these special formatted text strings. But most servers do not know this. Some do.

But that is why in ASP.NET Core and WebAPI Core we now have two decorators to help us: [FromBody] and [FromForm]

That is what [FromBody] does in ASP.NET Core or WebAPI. It tells the end point to listen for JSON data or text strings coming in as objects and helps convert them into your modeled data types in the parameter list of the ASP.NET endpoint method.

[FromForm] is the opposite....it is designed for ASP.NET MVC and more traditional HTML name-value data posts to the server that don't require all that extra JavaScript and JSON formatting. Use that if you are posting plain HTML <form> posts of data to the server from a plain HTML web page.

So, again...[FromBody] does NOT accept HTML Form field name-value pairs like [FromForm]. It is designed for API's like Angular that send JSON data.

Since you are using Angular, you should set up your server endpoints with [FromBody].

Keep in mind, however, on your client Angular-side, sending data to the server now requires the following:

  1. JavaScript POST Request manually sent to the Web API server
  2. JavaScript Http Header with JSON mime-type attached
  3. JavaScript Http Body with form field extracted data, reformatted and submitted as JSON. Traditional HTML POST name-value pairs will not work!

However, because Angular Observables and HttpClientModule calls take care of all that for you, you should be good to go! If you are NOT using Angular, however, my list above would apply to your custom JavaScript calls sending JSON to your new endpoint.

Stokely
  • 12,444
  • 2
  • 35
  • 23