30

I am facing a weird issue and almost spent 4 hours with no luck.

I have a simple Web API which I am calling on form submit.

API-

// POST: api/Tool
[HttpPost]
public void Post([FromBody] Object value)
{
    _toolService.CreateToolDetail(Convert.ToString(value));
}

HTML-

<!DOCTYPE html>
<html>
<body>

<h2>HTML Forms</h2>
<form name="value" action="https://localhost:44352/api/tool" method="post">
  First name:<br>
  <input type="text" id="PropertyA" name="PropertyA" value="Some value A">
  <br>
  Last name:<br>
  <input type="text" id="PropertyB" name="PropertyB" value="Some value B">
  <br><br>
  <!--<input type="file" id="Files" name="Files" multiple="multiple"/>-->
  <br><br>
  <input type="submit" value="Submit">

  </form>
</body>
</html>

When I hit the submit button I get below error-

{"":["The input was not valid."]}

Configurations in Startup class-

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddSingleton<IConfiguration>(Configuration);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseMvc();
}

This only happens for POST request. GET request works fine. Same issue when testing in Postman REST client. Any help please? Please let me know if I can provide more details.

Souvik Ghosh
  • 3,824
  • 10
  • 45
  • 64

8 Answers8

37

Don't use FromBody. You're submitting as x-www-form-urlencoded (i.e. standard HTML form post). The FromBody attribute is for JSON/XML.

You cannot handle both standard form submits and JSON/XML request bodies from the same action. If you need to request the action both ways, you'll need two separate endpoints, one with the param decorated with FromBody and one without. There is no other way. The actual functionality of your action can be factored out into a private method that both actions can utilize, to reduce code duplication.

Chris Pratt
  • 207,690
  • 31
  • 326
  • 382
  • Yes I eventually figured that out. I am able to access the fields which was posted by the form using `HttpContext`. Just wondering is there any disadvantage of doing it this way? By the `FormBody` is working for one of my colleague in a different project. – Souvik Ghosh Jul 06 '18 at 15:37
  • 1
    Yes, definitely. What you're talking about is the raw post data. It has not been sanitized, validated, etc. It's also not strongly-typed that way, so you're in string hell, where you've got to make sure accessing exactly the right keys and updating that if anything in your model should change. Just go with a separate endpoint. – Chris Pratt Jul 06 '18 at 15:42
  • Wow, this is un-intuitive when coming from Web Api 2. I'm used to using postman, and then using the form-data to build the request, by adding in keys and values. Web Api used to look at the content-type header, and read the body accordingly. Why would they now require you to make an endpoint for each content type? It seems to have gone backwards in terms of functionality, it would be nice to understand the rational behind this change if you have any information. – Doctor Jones Jan 18 '19 at 15:22
  • While I don't have any official source, I believe it was a design choice to remove an ambiguity issue MVC had. In MVC, you couldn't control the binding source, so something in the query string could clobber something in the post data. There was also an order of ops issue in that you didn't necessarily know what source might clobber another. Now, everything is clearly defined. There's probably also some performance improvements, as model binding becomes more straight forward. – Chris Pratt Jan 18 '19 at 16:39
14

I just worked through a similar situation here; I was able to use the [FromBody] without any issues:

public class MyController : Controller
{
   [HttpPost]
   public async Task<IActionResult> SomeEndpoint([FromBody]Payload inPayload)
   {
   ...
   }
}

public class Payload
{
   public string SomeString { get; set; }
   public int SomeInt { get; set; }
}

The challenge I figured out was the ensure that the requests were being made with the Content-Type header set as "application/json". Using Postman my original request was returned as "The input was not valid." Adding the Content-Type header fixed the issue for me.

Adam Schumph
  • 148
  • 6
  • have you uploaded a file? I just can't see any properties that can accept a file in your Payload – Konstantin Oct 28 '18 at 13:00
  • I have not uploaded a file; the HttpPost and FromBody work perfectly together to deserialize the HTTP Request body into the inPayload parameter. My point was to ensure that the request making the call has the "application/json" header; without that header it wouldn't work. – Adam Schumph Oct 29 '18 at 14:37
  • For some reason (I definitely just don't know something) I couldn't make it work with FromBody attribute, but after I changed the attribute to FromForm it started to work just fine – Konstantin Oct 29 '18 at 15:36
  • 2
    [FromForm] populates the parameters from a HTML Form; whereas [FromBody] populates the parameters from the HTTP request body (payload). Additionally, there's a [FromQuery] that will transcribe the variables from the query string. The one you're looking for will depend on how you're receiving the request -- of course you could support all three and have each one refer to a private function to do the actual work. But this is a little off-topic... – Adam Schumph Oct 30 '18 at 16:04
  • Thankyou! You saved hours! – Karishma Sep 20 '19 at 06:20
10

Just change [FromBody] to [FromForm].
The FromForm attribute is for incoming data from a submitted form sent by the content type application/x-www-url-formencoded while the FromBody will parse the model the default way, which in most cases are sent by the content type application/json, from the request body.
Thanks to https://stackoverflow.com/a/50454145/5541434

2

i have the same problem my solution was disable de attribute ApiController i dont know why you could read this [https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#multipartform-data-request-inference] i dont understend what is the problem

[Produces("application/json")]
    [Route("api/[controller]")]
    //[ApiController]<<remove this
    public class PagosController : ControllerBase

and you method

[HttpPost("UploadDescuento")]
public async Task<IActionResult> UploadDescuento(IEnumerable<IFormFile> files)
0

Like mjwills and DavidG said, you should probably use a concrete class in your controller parameter implementation, something like:

public class MyClass
{
    public string PropertyA { get; set; }

    public string PropertyB { get; set; }
}
Ben Krueger
  • 1,281
  • 1
  • 13
  • 20
0

If you inspect the network tab on your browser, you will notice that your form is sending the input values as parameters to your url, not sending itself. So naming it value won't send a property called "value" to your server.

Instead, the post method is expecting a property called value, which can be of any type (since it's an Object), and that parameter is never being fulfilled, as it is receiving these other parameters: PropertyA, PropertyB and Files.

Your full post url should probably look like this right now:

https://localhost:44352/api/tool?PropertyA=X&PropertyB=Y&Files=Z

Also notice that you did not specify an url to your Post method, so Im not sure how the client will reach /api/Tool. You probably need to specify that url on your controller, adding the Route attribute:

[Route("api/tool")]

The natural route, otherwise, is Hostname/Controller/Method or https://localhost:44352/api/post, if your controller is named Api. Otherwise it will replace "api" by the controller's name.

ShadowKras
  • 293
  • 4
  • 13
0

Change the body text type to the required format for example from text to JSON(application).

tonderaimuchada
  • 117
  • 1
  • 6
0

JavaScript and DotNet Core Web API implementation. Hope this helps someone out there.

    //JavaScript

    var dataList = [];
    var dataObject = {};

    dataObject["field1"] = "field1";
    dataObject["field2"] = "field2";

    dataList.push(dataObject);

    $.ajax("https://localhost:1111/api/Sample",
    {
        type: "POST",
        async: false,
        contentType: "application/json",
        dataType: 'json',
        data: JSON.stringify(dataList),
        success: function (responseObject) {
            console.log(responseObject);
        }
    });

    //DOT NET CORE WEB API
    //Sample Request Model
    public class RequestObject{
        public string field1 { get; set; }
        public string field2 { get; set; }
    }
    
    [Route("api/[controller]")]
    [ApiController]
    public class SampleController : ControllerBase
    {
          //Sample POST API
          [HttpPost]
          public string Post([FromBody] List<RequestObject> requestObjectList)
          {
               var youWelcome = requestObjectList;
               return "JavaScript Post Successful.";
          }
    }
Bernard Oreva
  • 94
  • 1
  • 4