I have a very simple server call like this:
[HttpPost]
[AllowAnonymous]
public JsonResult Test(TestRequestModel requestModel)
{
//do stuff
return Json(new { result.Success });
}
My TestRequestModel looks like this:
public class TestRequestModel
{
public string Email { get; set; } = string.Empty;
}
I am trying to do a POST request to the server. But for a complex list of reasons I need to be using XMLHttpRequest instead of $.ajax. To do this I am going to show you how I did it in ajax and then how I did it with XMLHttpRequest.
First here is how I call my server:
function MyTestFunction() {
let parameters = {
Email: "[email protected]"
}
CallServer(function () {
//Do stuff
}, function () {
//Do other stuff
}, "/Home/Test", parameters)
}
Ajax:
function CallServer(resolve, reject, path, parameters) {
$.ajax({
type: "POST",
url: path,
data: AddAntiForgeryToken(parameters),
success: function (response) {
//do stuff
},
error: function (xhr) {
//do stuff
},
complete: function () {
//do more stuff
}
});
}
XMLHttpRequest Way:
function CallServer(resolve, reject, path, parameters) {
let xhr = new XMLHttpRequest();
xhr.open("POST", path, true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.setRequestHeader('RequestVerificationToken', GetMyToken());
xhr.onreadystatechange = function (e) {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200 || xhr.status === 201) {
//do stuff
}
else {
//do other stuff
}
}
};
xhr.send(JSON.stringify(parameters));
}
If I run the above code the ajax way, then it works without issues. If I try to do it the XMLHttpRequest way then my request model gets created but the Email field is not populated. Now I found that I can solve this by adding [FromBody] on the request model and it does work, I tested it and no problems.
Also, as I read online and hopefully understood correctly, but ajax uses XMLHttpRequest behind the hood right?
So why do I have to add [FromBody] on my controller for this to work? Is there a way I can modify my XMLHttpRequest so that it is not needed? The solution I am looking for how to not have to specify [FromBody] on a json post request to .net core server.
2
Answers
The results you’re experiencing are the result of Model Binding in ASP.NET.
The bottom line is that the two
post
samples in your question —$.ajax()
and...xhr.send()
— are not the same requests.$.ajax()
application/x-www-form-urlencoded; charset=UTF-8
...xhr.send()
application/json;charset=UTF-8
in request bodyReferencing Microsoft’s documentation on the sources for model binding,
It’s not explicitly stated in your question, but based on your sample controller, your project is ASP.NET MVC. Item 2 in the list above does not apply.
Just after the default list in the model binding documentation, the following is stated:
[FromQuery]
– Gets values from the query string.[FromRoute]
– Gets values from route data.[FromForm]
– Gets values from posted form fields.[FromBody]
– Gets values from the request body.[FromHeader]
– Gets values from HTTP headers..ajax()
sampleThe
.ajax()
method in your sample code is posting a request of content type:You can see this in the header of the
post
request in the Network tab of the DevTools in Chrome.By default, model binding for your MVC controller parses the request model.
XMLHttpRequest
sampleYour
XMLHttpRequest
sample is sending the model data (containing the email field) in the body of the request via thesend()
method:Model binding for your MVC controller is not looking at data in the body by default. This is why you’re seeing an empty email field.
When you explicitly add the
[FromBody]
attribute, the model binder looks for data in the body of the request.Solutions
You can make your
AJAX
code to be equivalent to yourXMLHttpRequest
code or vice versa. The following updates your AJAX code to be consistent with theXMLHttpRequest
. That is, it sends yourparameter
object in the request body.You’d need to keep the
[FromBody]
attribute in your controller method:There are two types of Model Binding in ASP.NET Core, one is binding from Form data(Content-type is
application/x-www-form-urlencoded
/multipart/form-data
) by[FromForm]
and another is binding from body(Content-type isapplication/json
) by[FromBody]
.For Post request, MVC Controller required parameters from form data instead of from body by default. That is why you need add the
[FromBody]
attribute.For why the ajax can work fine. When using
$.ajax
without specifyingcontentType: 'application/json'
, jQuery will serialize the data inapplication/x-www-form-urlencoded
format.If you do not want to use
[FromBody]
attribute, a working demo like below: