42

I have a Web API service call that updates a user's preferences. Unfortunately when I call this POST method from a jQuery ajax call, the request parameter object's properties are always null (or default values), rather than what is passed in. If I call the same exact method using a REST client (I use Postman), it works beautifully. I cannot figure out what I'm doing wrong with this but am hoping someone has seen this before. It's fairly straightforward...

Here's my request object:

public class PreferenceRequest
{
    [Required]
    public int UserId;

    public bool usePopups;
    public bool useTheme;
    public int recentCount;
    public string[] detailsSections;
}

Here's my controller method in the UserController class:

    public HttpResponseMessage Post([FromBody]PreferenceRequest request)
    {
        if (request.systemsUserId > 0)
        {
            TheRepository.UpdateUserPreferences(request.UserId, request.usePopups, request.useTheme,
                                         request.recentCount, request.detailsSections);

            return Request.CreateResponse(HttpStatusCode.OK, "Preferences Updated");                
        }
        else
        {
            return Request.CreateErrorResponse(HttpStatusCode.NotAcceptable, "You must provide User ID");
        }
    }

Here's my ajax call:

var request = {
    UserId: userId,
    usePopups: usePopups,
    useTheme: useTheme,
    recentCount: recentCount,
    detailsSections: details
};

$.ajax({
    type: "POST",
    data: request,
    url: "http://localhost:1111/service/User",
    success: function (data) {
        return callback(data);
    },
    error: function (error, statusText) {
        return callback(error);
    }
});

I've tried setting the dataType & contentType to several different things ('json', 'application/json', etc) but the properties of the request object are always defaulted or null. So, for example, if I pass in this object:

var request = {
  UserId: 58576,
  usePopups: false,
  useTheme: true,
  recentCount: 10,
  detailsSections: ['addresses', 'aliases', 'arrests', 'events', 'classifications', 'custody', 'identifiers', 'phone', 'remarks', 'watches']
}

I can see a fully populated request object with the valid values as listed above. But in the Web API controller, the request is there, but the properties are as follows:

  UserId: 0,
  usePopups: false,
  useTheme: false,
  recentCount: 0,
  detailsSections: null

FYI - I'm not doing ANY ASP.Net MVC or ASP.NET pages with this project, just using the Web API as a service and making all calls using jQuery $.ajax.

Any idea what I'm doing wrong here? Thanks!

UPDATE: I just want to note that I have many methods in this same Web API project in other controllers that do this exact same thing, and I am calling the exact same way, and they work flawlessly! I have spent the morning comparing the various calls, and there doesn't appear to be any difference in the method or the headers, and yet it just doesn't work on this particular method.

I've also tried switching to a Put method, but I get the exact same results - the request object comes in, but is not populated with the correct values. What's so frustrating is that I have about 20 controller classes in this project, and the Posts work in all of those...

Eddie
  • 1,228
  • 3
  • 17
  • 31
  • 3
    what is the content-type of your ajax post if if I am not mistaken jquery default is url-form-encoding and what you want is application/json? – Dalorzo Jun 09 '14 at 22:17
  • It's json. As I mentioned, I have tried specifying content-type: 'json', content-type:'application/json' and I get the same result with either of those, or neither. – Eddie Jun 09 '14 at 22:53
  • check this possible cause [WebApi post configuration](http://stackoverflow.com/a/23925564/1959948) – Dalorzo Jun 09 '14 at 23:01
  • I checked that and it looks like I've configured the json formatter properly. Also, note that I can do Posts and Puts using JSON throughout this project. It's just not working on this method. – Eddie Jun 10 '14 at 16:18
  • Can you share your routing configuration the register class on the global asax? – Dalorzo Jun 10 '14 at 16:29
  • 2
    and also have you tried this ` data: "="+JSON.stringify(request),` and server side `function void Post(object demo)` and check if demo is null? – Dalorzo Jun 10 '14 at 16:33
  • This actually worked for me! I had to combine this on the client side with blorkfish's fix on the server side, and the request came in properly. I was able to convert it to the request object and process the values. I still don't know why this particular method is having so many issues, but I'm relieved to have a fix! Thanks!! – Eddie Jun 10 '14 at 20:08

18 Answers18

65

This seems to be a common issue in regards to Asp.Net WebAPI.
Generally the cause of null objects is the deserialization of the json object into the C# object. Unfortunately, it is very difficult to debug - and hence find where your issue is.
I prefer just to send the full json as an object, and then deserialize manually. At least this way you get real errors instead of nulls.
If you change your method signature to accept an object, then use JsonConvert:

public HttpResponseMessage Post(Object model)
        {
            var jsonString = model.ToString();
            PreferenceRequest result = JsonConvert.DeserializeObject<PreferenceRequest>(jsonString);
        }
blorkfish
  • 21,800
  • 4
  • 33
  • 24
  • I tried this, and it did continue to work in my REST call using Postman, but from jQuery the model was null when it hit the Post method, so I got an exception in the Json.net deserialization attempt. I will start scrutinizing the headers in the two calls to see if I can figure out what is different in the jQuery call, or why the Postman call works... – Eddie Jun 10 '14 at 14:52
  • Using this, along with Dalorzo's suggestion above for the client side, I was able to get it to work. Thank you! – Eddie Jun 10 '14 at 20:09
  • 11
    Sheesh ... this seems like an elementary feature for a REST framework. How could Web API fail to get this right? Thanks for your workaround anyway! – McGarnagle Sep 04 '15 at 21:34
  • I've spent half a day trying my posted model to work. blorkfish's method works 100% of the time. Thank you! – tno2007 Oct 05 '15 at 21:18
  • Is there any other way to debug such an issue? – dmigo Dec 16 '15 at 09:52
  • 7
    This is excellent to be a temporal workaround for debugging. After fixing serialization problems, the workaround can safely be removed again. – Michael Hilus Jul 01 '16 at 09:41
  • I received the same error. I realized my client was running on Newtonsoft v6 and my server was running on Newtonsoft v11. Once I updated my client to v11 my model was populated. Hope this helps someone. – Pierre Nortje Jun 29 '18 at 17:49
  • 2
    Sorry But I cannot get this to work. Object is System.Object and ToString produces "System.Object" and the JsonConvert crashes. Am I missing something??? – Juanjo Jun 29 '18 at 22:01
  • I used a JArray and not a JObject, but I'm upvoting this becasue it got me to my original error. – gemini88mill Mar 06 '19 at 17:41
  • Ugh. I added `[FromBody]` AND `JObject` and yet my collection property is still null. (The other property, a simple `int` comes across just fine.) :( – Scott Fraley May 31 '19 at 19:48
16

So there are 3 possible issues I'm aware of where the value does not bind:

  1. no public parameterless constructor
  2. properties are not public settable
  3. there's a binding error, which results in a ModelState.Valid == false - typical issues are: non compatible value types (json object to string, non-guid, etc.)

So I'm considering if API calls should have a filter applied that would return an error if the binding results in an error!

andyrut
  • 134
  • 7
baHI
  • 1,510
  • 15
  • 20
  • Also there's a way to handle such errors easily + a possible way to distinguish between binding / validation errors. see: https://stackoverflow.com/questions/45941246/how-to-discern-between-model-binding-errors-and-model-validation-errors/52830010#52830010 – baHI Oct 16 '18 at 07:28
  • Good on you @baHI for collating answers. Please see my post below as another possible issue people may be having. – Delorian Feb 13 '19 at 03:46
  • thanks my main class was not set as public. It was the cause of the problem – balron Nov 26 '20 at 11:07
  • arggghhh... "public settable"... impossible to debug! #madness – Nicholas Franceschina Feb 03 '21 at 03:48
14

Maybe it will help, I was having the same problem.

Everything was working well, and suddently, every properties was defaulted.

After some quick test, I found that it was the [Serializable] that was causing the problem :

public IHttpActionResult Post(MyComplexClass myTaskObject)
{
    //MyTaskObject is not null, but every member are (the constructor get called).
}

and here was a snippet of my class :

[Serializable]  <-- have to remove that [if it was added for any reason..]
public class MyComplexClass()
{
     public MyComplexClass ()
     {
        ..initiate my variables..
     }

     public string blabla {get;set;}
     public int intTest {get;set;
}
Simon
  • 2,266
  • 1
  • 22
  • 24
4

I guess problem is that your controller is expecting content type of [FromBody],try changing your controller to

public HttpResponseMessage Post(PreferenceRequest request)

and ajax to

$.ajax({
    type: "POST",
    data: JSON.stringify(request);,
    dataType: 'json',
    contentType: "application/json",
    url: "http://localhost:1111/service/User",

By the way creating model in javascript may not be best practice.

Anshul Nigam
  • 1,608
  • 1
  • 12
  • 26
  • Thanks Anshul - I actually had started without the [FromBody] attribute, but added it later in hopes it would fix the issue. Unfortunately whether it's in or not I get the same result. I'm curious, if you are passing a complex object to a Web API method, how would you create the object to be used in the ajax call if not in javascript? – Eddie Jun 10 '14 at 14:41
  • Eddie you need to use a good MVVM(Model View View Model) library, I am using knockout.js http://knockoutjs.com/ and for mapping complex object to JavaScript I am using its mapping plugin http://knockoutjs.com/documentation/plugins-mapping.html. By using this i do not need to worry about model's be in sync – Anshul Nigam Jun 11 '14 at 02:51
4

Using this technique posted by @blorkfish worked great:

public HttpResponseMessage Post(Object model)
    {
        var jsonString = model.ToString();
        PreferenceRequest result = JsonConvert.DeserializeObject<PreferenceRequest>(jsonString);
    }

I had a parameter object, which had a couple of native types and a couple more objects as properties. The objects had constructors marked internal and the JsonConvert.DeserializedObject call on the jsonString gave the error:

Unable to find a constructor to use for type ChildObjectB. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.

I changed the constructors to public and it is now populating the parameter object when I replace the Object model parameter with the real object. Thanks!

Russell D
  • 326
  • 2
  • 9
3

I know, this is a bit late, but I just ran into the same issue. @blorkfish's answer worked for me as well, but led me to a different solution. One of the parts of my complex object lacked a parameter-less constructor. After adding this constructor, both Put and Post requests began working as expected.

Storm
  • 3,062
  • 4
  • 23
  • 54
3

I have also facing this issue and after many hours from debbug and research can notice the issue was not caused by Content-Type or Type $Ajax attributes, the issue was caused by NULL values on my JSON object, is a very rude issue since the POST neither makes but once fix the NULL values the POST was fine and natively cast to my respuestaComparacion class here the code:

JSON

 respuestaComparacion: {
                            anioRegistro: NULL, --> cannot cast to BOOL
                            claveElector: NULL, --> cannot cast to BOOL
                            apellidoPaterno: true,
                            numeroEmisionCredencial: false,
                            nombre: true,
                            curp: NULL, --> cannot cast to BOOL
                            apellidoMaterno: true,
                            ocr: true
                        }

Controller

[Route("Similitud")]
[HttpPost]
[AllowAnonymous]

public IHttpActionResult SimilitudResult([FromBody] RespuestaComparacion req)
{
   var nombre = req.nombre;
}

Class

public class RespuestaComparacion
    {
        public bool anioRegistro { get; set; }
        public bool claveElector { get; set; }
        public bool apellidoPaterno { get; set; }
        public bool numeroEmisionCredencial { get; set; }
        public bool nombre { get; set; }
        public bool curp { get; set; }
        public bool apellidoMaterno { get; set; }
        public bool ocr { get; set; }
    }

Hope this help.

Jorge Solano
  • 41
  • 1
  • 2
  • Yes, 1) [FromBody] 2) a parameterless constructor (in most cases can be even protected, but would check that later, once issue fixed) and also 3) all properties must have a public settter! In the RespuestaComparacion i'd use bool? (nullable) if the NULL value from the client should be a valid one. – baHI Nov 28 '18 at 10:35
2

I came across the same issue. The fix needed was to ensure that all serialize-able properties for your JSON to parameter class have get; set; methods explicitly defined. Don't rely on C# auto property syntax! Hope this gets fixed in later versions of asp.net.

  • I use `Auto properties` and I haven't came across such problem because of this! Do you have an actual experience with this, or this is just a `hunch`? – Rickless Jan 02 '18 at 10:45
2

A bit late to the party, but I had this same issue and the fix was declaring the contentType in your ajax call:

    var settings = {
        HelpText: $('#help-text').val(),
        BranchId: $("#branch-select").val(),
        Department: $('input[name=departmentRadios]:checked').val()

    };

$.ajax({
        url: 'http://localhost:25131/api/test/updatesettings',
        type: 'POST',
        data: JSON.stringify(settings),
        contentType: "application/json;charset=utf-8",
        success: function (data) {
            alert('Success');
        },
        error: function (x, y, z) {
            alert(x + '\n' + y + '\n' + z);
        }
    });

And your API controller can be set up like this:

    [System.Web.Http.HttpPost]
    public IHttpActionResult UpdateSettings([FromBody()] UserSettings settings)
    {
//do stuff with your UserSettings object now
        return Ok("Successfully updated settings");
    }
Dan
  • 231
  • 1
  • 12
1

In my case problem was solved when i added

get{}set{}

to parameters class definition:

public class PreferenceRequest
{
    public int UserId;
    public bool usePopups {get; set;}
    public bool useTheme {get; set;}
    public int recentCount {get; set;}
    public string[] detailsSections {get;set;}
}

enstead of:

   public class PreferenceRequest
    {
        [Required]
        public int UserId;
        public bool usePopups;
        public bool useTheme;
        public int recentCount;
        public string[] detailsSections;
    }
alexey
  • 783
  • 1
  • 7
  • 19
0

As this issue was troubling me for almost an entire working day yesterday, I want to add something that might assist others in the same situation.

I used Xamarin Studio to create my angular and web api project. During debugging the object would come through null sometimes and as a populated object other times. It is only when I started to debug my project in Visual Studio where my object was populated on every post request. This seem to be a problem when debugging in Xamarin Studio.

Please do try debugging in Visual Studio if you are running into this null parameter problem with another IDE.

tno2007
  • 1,993
  • 25
  • 16
0

Today, I've the same problem as yours. When I send POST request from ajax the controller receive empty object with Null and default property values. The method is:

[HttpPost]
public async Task<IActionResult> SaveDrawing([FromBody]DrawingModel drawing)
{


    try
    {

        await Task.Factory.StartNew(() =>
        {
            //Logic
        });
        return Ok();
    }
    catch(Exception e)
    {
        return BadRequest();
    }
}

My Content-Type was correct and everything else was correct too. After trying many things I found that sending the object like this:

$.ajax({
    url: '/DrawingBoard/SaveDrawing',
    type: 'POST',
    contentType: 'application/json',
    data: dataToPost
}).done((response) => { }); 

Won't work, but sending it like this instead worked:

$.ajax({
    url: '/DrawingBoard/SaveDrawing',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify(dataToPost)
}).done((response) => { }); 

Yes, the missing JSON.stringify() caused the whole issue, and no need to put = or anything else as prefix or suffix to JSON.stringify. After digging a little bit. I found that the format of the request payload completely different between the two requests
This is the request payload with JSON.stringify

Request payload with JSON.stringify

And this is the request payload without JSON.stringify

Request payload without JSON.stringify

And don't forget, when things get magical and you use Google Chrome to test your web application. Do Empty cache and hard reload from time to time.

Rickless
  • 1,377
  • 3
  • 17
  • 36
0

I ran into the same issue, the solution for me was to make certain the types of my class attributes matched the json atributes, I mean

Json: "attribute": "true"

Should be treated as string and not as boolean, looks like if you have an issue like this all the attributes underneath the faulty attribute will default to null

0

I ran into the same problem today as well. After trying all of these, debugging the API from Azure and debugging the Xamarin Android app, it turns out it was a reference update issue. Remember to make sure that your API has the Newtonsoft.JSON NUGET package updated (if you are using that).

0

My issue was not solved by any of the other answers, so this solution is worth consideration:

I had the following DTO and controller method:

public class ProjectDetailedOverviewDto
{
    public int PropertyPlanId { get; set; }

    public int ProjectId { get; set; }

    public string DetailedOverview { get; set; }
}

public JsonNetResult SaveDetailedOverview(ProjectDetailedOverviewDto detailedOverview) { ... }

Because my DTO had a property with the same name as the parameter (detailedOverview), the deserialiser got confused and was trying to populate the parameter with the string rather than the entire complex object. The solution was to change the name of the controller method parameter to 'overview' so that the deserialiser knew I wasn't trying to access the property.

Delorian
  • 330
  • 1
  • 3
  • 13
0

I face this problem this fix it to me

  1. use attribute [JsonProperty("property name as in json request")] in your model by nuget package newton
  2. if you serializeobject call PostAsync only

like that

var json = JsonConvert.SerializeObject(yourobject);
var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var response = await client.PostAsync ("apiURL", stringContent);
  1. if not work remove [from body] from your api
slfan
  • 8,950
  • 115
  • 65
  • 78
0

HttpGet

public object Get([FromBody]object requestModel)
{
    var jsonstring = JsonConvert.SerializeObject(requestModel);
    RequestModel model = JsonConvert.DeserializeObject<RequestModel>(jsonstring);
}
shizhen
  • 12,251
  • 9
  • 52
  • 88
Tarrah Arshad
  • 74
  • 2
  • 9
  • What if your Http method was a post? And the handler for the httppost is a razor page. what would you do then? – Ahhzeee Oct 28 '19 at 23:38
0
  public class CompanyRequestFilterData
    {
             public string companyName { get; set; }
             public string addressPostTown { get; set; }
             public string businessType { get; set; }
             public string addressPostCode{ get; set; }
             public bool companyNameEndWith{ get; set; }
             public bool companyNameStartWith{ get; set; }
             public string companyNumber{ get; set; }
             public string companyStatus{ get; set; }
             public string companyType{ get; set; }
             public string countryOfOrigin{ get; set; }
             public DateTime? endDate{ get; set; }
             public DateTime? startDate { get; set; }
}

webapi controller

    [HttpGet("[action]"),HttpPost("[action]")]
    public async Task<IEnumerable<CompanyStatusVm>> GetCompanyRequestedData(CompanyRequestFilterData filter)
    {}

jsvascript/typescript

export async function GetCompanyRequesteddata(config, payload, callback, errorcallback) {
await axios({
    method: 'post',
    url: hostV1 + 'companydata/GetCompanyRequestedData',
    data: JSON.stringify(payload),
    headers: {
        'secret-key': 'mysecretkey',
        'Content-Type': 'application/json'
    }
})
    .then(res => {
        if (callback !== null) {
            callback(res)
        }
    }).catch(err => {
        if (errorcallback !== null) {
            errorcallback(err);
        }
    })

}

this is the working one c# core 3.1

my case it was bool datatype while i have changed string to bool [companyNameEndWith] and [companyNameStartWith] it did work. so please check the datatypes.

Ahsanul
  • 215
  • 2
  • 5