How to pass json POST data to Web API method as an object?

815,466

Solution 1

EDIT : 31/10/2017

The same code/approach will work for Asp.Net Core 2.0 as well. The major difference is, In asp.net core, both web api controllers and Mvc controllers are merged together to single controller model. So your return type might be IActionResult or one of it's implementation (Ex :OkObjectResult)


Use

contentType:"application/json"

You need to use JSON.stringify method to convert it to JSON string when you send it,

And the model binder will bind the json data to your class object.

The below code will work fine (tested)

$(function () {
    var customer = {contact_name :"Scott",company_name:"HP"};
    $.ajax({
        type: "POST",
        data :JSON.stringify(customer),
        url: "api/Customer",
        contentType: "application/json"
    });
});

Result

enter image description here

contentType property tells the server that we are sending the data in JSON format. Since we sent a JSON data structure,model binding will happen properly.

If you inspect the ajax request's headers, you can see that the Content-Type value is set as application/json.

If you do not specify contentType explicitly, It will use the default content type which is application/x-www-form-urlencoded;


Edit on Nov 2015 to address other possible issues raised in comments

Posting a complex object

Let's say you have a complex view model class as your web api action method parameter like this

public class CreateUserViewModel
{
   public int Id {set;get;}
   public string Name {set;get;}  
   public List<TagViewModel> Tags {set;get;}
}
public class TagViewModel
{
  public int Id {set;get;}
  public string Code {set;get;}
}

and your web api end point is like

public class ProductController : Controller
{
    [HttpPost]
    public CreateUserViewModel Save([FromBody] CreateUserViewModel m)
    {
        // I am just returning the posted model as it is. 
        // You may do other stuff and return different response.
        // Ex : missileService.LaunchMissile(m);
        return m;
    }
}

At the time of this writing, ASP.NET MVC 6 is the latest stable version and in MVC6, Both Web api controllers and MVC controllers are inheriting from Microsoft.AspNet.Mvc.Controller base class.

To send data to the method from client side, the below code should work fine

//Build an object which matches the structure of our view model class
var model = {
    Name: "Shyju",
    Id: 123,
    Tags: [{ Id: 12, Code: "C" }, { Id: 33, Code: "Swift" }]
};

$.ajax({
    type: "POST",
    data: JSON.stringify(model),
    url: "../product/save",
    contentType: "application/json"
}).done(function(res) {       
    console.log('res', res);
    // Do something with the result :)
});

Model binding works for some properties, but not all ! Why ?

If you do not decorate the web api method parameter with [FromBody] attribute

[HttpPost]
public CreateUserViewModel Save(CreateUserViewModel m)
{
    return m;
}

And send the model(raw javascript object, not in JSON format) without specifying the contentType property value

$.ajax({
    type: "POST",
    data: model,
    url: "../product/save"
}).done(function (res) {
     console.log('res', res);
});

Model binding will work for the flat properties on the model, not the properties where the type is complex/another type. In our case, Id and Name properties will be properly bound to the parameter m, But the Tags property will be an empty list.

The same problem will occur if you are using the short version, $.post which will use the default Content-Type when sending the request.

$.post("../product/save", model, function (res) {
    //res contains the markup returned by the partial view
    console.log('res', res);
});

Solution 2

Working with POST in webapi can be tricky! Would like to add to the already correct answer..

Will focus specifically on POST as dealing with GET is trivial. I don't think many would be searching around for resolving an issue with GET with webapis. Anyways..

If your question is - In MVC Web Api, how to- - Use custom action method names other than the generic HTTP verbs? - Perform multiple posts? - Post multiple simple types? - Post complex types via jQuery?

Then the following solutions may help:

First, to use Custom Action Methods in Web API, add a web api route as:

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
        name: "ActionApi",
        routeTemplate: "api/{controller}/{action}");
}

And then you may create action methods like:

[HttpPost]
public string TestMethod([FromBody]string value)
{
    return "Hello from http post web api controller: " + value;
}

Now, fire the following jQuery from your browser console

$.ajax({
    type: 'POST',
    url: 'http://localhost:33649/api/TestApi/TestMethod',
    data: {'':'hello'},
    contentType: 'application/x-www-form-urlencoded',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

Second, to perform multiple posts, It is simple, create multiple action methods and decorate with the [HttpPost] attrib. Use the [ActionName("MyAction")] to assign custom names, etc. Will come to jQuery in the fourth point below

Third, First of all, posting multiple SIMPLE types in a single action is not possible. Moreover, there is a special format to post even a single simple type (apart from passing the parameter in the query string or REST style). This was the point that had me banging my head with Rest Clients (like Fiddler and Chrome's Advanced REST client extension) and hunting around the web for almost 5 hours when eventually, the following URL proved to be of help. Will quote the relevant content for the link might turn dead!

Content-Type: application/x-www-form-urlencoded
in the request header and add a = before the JSON statement:
={"Name":"Turbo Tina","Email":"[email protected]"}

PS: Noticed the peculiar syntax?

http://forums.asp.net/t/1883467.aspx?The+received+value+is+null+when+I+try+to+Post+to+my+Web+Api

Anyways, let us get over that story. Moving on:

Fourth, posting complex types via jQuery, ofcourse, $.ajax() is going to promptly come in the role:

Let us say the action method accepts a Person object which has an id and a name. So, from javascript:

var person = { PersonId:1, Name:"James" }
$.ajax({
    type: 'POST',
    url: 'http://mydomain/api/TestApi/TestMethod',
    data: JSON.stringify(person),
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

And the action will look like:

[HttpPost]
public string TestMethod(Person person)
{
    return "Hello from http post web api controller: " + person.Name;
}

All of the above, worked for me!! Cheers!

Solution 3

I've just been playing with this and discovered a rather odd result. Say you have public properties on your class in C# like this:

public class Customer
{
    public string contact_name;
    public string company_name;
}

then you must do the JSON.stringify trick as suggested by Shyju and call it like this:

var customer = {contact_name :"Scott",company_name:"HP"};
$.ajax({
    type: "POST",
    data :JSON.stringify(customer),
    url: "api/Customer",
    contentType: "application/json"
});

However, if you define getters and setters on your class like this:

public class Customer
{
    public string contact_name { get; set; }
    public string company_name { get; set; }
}

then you can call it much more simply:

$.ajax({
    type: "POST",
    data :customer,
    url: "api/Customer"
});

This uses the HTTP header:

Content-Type:application/x-www-form-urlencoded

I'm not quite sure what's happening here but it looks like a bug (feature?) in the framework. Presumably the different binding methods are calling different "adapters", and while the adapter for application/json one works with public properties, the one for form encoded data doesn't.

I have no idea which would be considered best practice though.

Solution 4

Use the JSON.stringify() to get the string in JSON format, ensure that while making the AJAX call you pass below mentioned attributes:

  • contentType: 'application/json'

Below is the give jquery code to make ajax post call to asp.net web api:

var product =
    JSON.stringify({
        productGroup: "Fablet",
        productId: 1,
        productName: "Lumia 1525 64 GB",
        sellingPrice: 700
    });

$.ajax({
    URL: 'http://localhost/api/Products',
    type: 'POST',
    contentType: 'application/json',
    data: product,
    success: function (data, status, xhr) {
        alert('Success!');
    },
    error: function (xhr, status, error) {
        alert('Update Error occurred - ' + error);
    }
});

Share:
815,466
Andrus
Author by

Andrus

Updated on October 05, 2020

Comments

  • Andrus
    Andrus over 3 years

    ASP.NET MVC4 Web API application defines post method to save customer. Customer is passed in json format in POST request body. Customer parameter in post method contains null values for properties.

    How to fix this so that posted data will passed as customer object ?

    If possible Content-Type: application/x-www-form-urlencoded should used since I dont know how to change it in javascript method which posts form.

    Controller:

    public class CustomersController : ApiController {
    
      public object Post([FromBody] Customer customer)
            {
                return Request.CreateResponse(HttpStatusCode.OK,
                new
                {
                    customer = customer
                });
            }
        }
    }
    
    public class Customer
        {
            public string company_name { get; set; }
            public string contact_name { get; set; }
         }
    

    Request:

    POST http://localhost:52216/api/customers HTTP/1.1
    Accept: application/json, text/javascript, */*; q=0.01
    X-Requested-With: XMLHttpRequest
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    
    {"contact_name":"sdfsd","company_name":"ssssd"}
    
  • Grayson
    Grayson over 9 years
    Not sure what I did, but I came back in this morning and back in the same boat. Object is null in the controller. here we go again lol
  • Grayson
    Grayson over 9 years
    okay, 2 hours in and I cant figure this out. In my case just for testing I am am posting from fiddler. I have the content type set to Content-Type: application/json. I have a viewmodel that I am trying to bind the data too. I have gone as far as modifying the viewmodel so that it only contains a single element, { \"AssetName\":\"testname\" } and my controller looks like this [HttpPost] public HttpResponseMessage LogValidation([FromBody]myViewModel myviewmodel){ the post hits the controller but myviewmodel is always null.
  • Dushan Perera
    Dushan Perera over 9 years
    @Grayson can you post a minimized sample solution so that i can take a look ?
  • ioWint
    ioWint over 9 years
    make sure the content type is written "Content-Type: application/json" while you are using fiddler. Cheers!
  • Gil Allen
    Gil Allen over 9 years
    You simply solved me a day of work!!! This tiny function "JSON.stringify(data)" made it!
  • candlejack
    candlejack about 9 years
    @Shyju what software you have used for display that information?
  • crthompson
    crthompson almost 9 years
    Properties vs fields is why its different. Properties are the best practice. What you call properties in that first example are in fact, fields. When you put a get/set on them, they then have an auto created backing field which makes them properties.
  • Arbiter
    Arbiter almost 9 years
    Keep in mind that if you do this (change the Content-Type header) and you're making a CORS request, jQuery will start adding preflight OPTIONS requests before your POST which the server will have to handle.
  • Kinetic
    Kinetic over 8 years
    I seem to hit this problem every few months, most of the time I eventually solve it, but this time I have given up. None of the tips above solve it for me, so I decided to bin this as an approach. If it's SO hard to get right, why bother? It's only a convenience anyway - just take the content in as a string and use newtonsoft to transform it. Done. Took probably 30 seconds to solve it the "hard" way after trying for about an hour to solve it the "easy" way. I'm not wild about the approach, but is there a fundamental problem with it?
  • drewid
    drewid over 8 years
    @Grayson - I know this is a year later but I am having the same really aggravating issue. Using the proper application/json content-type; using json values that match a class. But in the web api function the paramater is showing up as null. Even more frustrating - I have another api function where a different class works just fine! I am using postman so am able to construct the call exactly how it should be. There is no discernible difference between the two that work and don't work.
  • BornToCode
    BornToCode about 8 years
    Because of the problem with complex types I think it's a got habit to just specifiy 'contentType: 'application/json;' and json stringify the js object and then there's no need to use the [FromBody] attribute.
  • blazkovicz
    blazkovicz about 8 years
    In my case some of the model parameters are omitted by parser for no reason! Model fields of type DateTime? aren't parsed from strings 'yyyy-MM-dd' in POST but parsed in GET with [FromUri] set for model.
  • blazkovicz
    blazkovicz about 8 years
    If anyone is having same problems with parsing some of the model parameters: attribute [JsonConverter(typeof (YourCustomConverter))] is the solution for such fields.
  • James Wilkins
    James Wilkins about 8 years
    This is so true, and odd. Normal classes with only fields won't bind to form posts, but properties will. BTW: Still doesn't explain why this is the case...? I can only guess the internal logic will only bind JSON data to fields, and form post data to properties, and that's simply it...?
  • Shavais
    Shavais almost 8 years
    How do you pass some simple types and a complex type?
  • Erik Philips
    Erik Philips almost 8 years
    It's the case because the code only looks for properties. Since using public fields is not best practice, the MS team decided not to allow not best practice scenarios, pretty good reason IMHO.
  • Erik Philips
    Erik Philips almost 8 years
    dataType is not required.
  • Vaibhav
    Vaibhav almost 8 years
    PS: In WebApi2, we can now use Route Decorators. So this issue is primarily, addressed. asp.net/web-api/overview/web-api-routing-and-actions/…
  • Vaibhav
    Vaibhav almost 8 years
    @Shavais, AFAIK, it is not possible (by default). Just club all the data in a single DTO.
  • fobus
    fobus over 7 years
    @Shavais Can you please check my question? I can't solve it : stackoverflow.com/questions/40545603/…
  • Vaibhav
    Vaibhav over 7 years
    Would like to add an observation. Sometimes, the reason for the model-binding to fail (null) on the WebAPI side, when passing a complex type (ex: DTO), is that one or more properties in the model will be incompatible (or fail to parse). Eg. A Guid property being assigned an invalid GUID. In this case, try using the default/empty values for all the object properties and try again.
  • Si8
    Si8 over 7 years
    I am getting this error: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • Pranay Dutta
    Pranay Dutta about 6 years
    i owe u a party dude
  • Ian Warburton
    Ian Warburton almost 6 years
    And every other year we'll have to work out how to do it all over again.