skip to Main Content

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


  1. 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.

    Request Method Content type Model Binding Source
    $.ajax() application/x-www-form-urlencoded; charset=UTF-8 Form fields
    ...xhr.send() application/json;charset=UTF-8 in request body Default model binding is not looking for data in the request body

    Referencing Microsoft’s documentation on the sources for model binding,

    By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:

    1. Form fields
    2. The request body (For controllers that have the [ApiController] attribute.)
    3. Route data
    4. Query string parameters
    5. Uploaded files

    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.

    public JsonResult Test(TestRequestModel requestModel)
    {
        //do stuff
        return Json(new { result.Success });
    }
    

    Just after the default list in the model binding documentation, the following is stated:

    If the default source is not correct, use one of the following attributes to specify the source:

    • [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() sample

    The .ajax() method in your sample code is posting a request of content type:

    application/x-www-form-urlencoded; charset=UTF-8
    

    You can see this in the header of the post request in the Network tab of the DevTools in Chrome.

    enter image description here

    By default, model binding for your MVC controller parses the request model.

    XMLHttpRequest sample

    Your XMLHttpRequest sample is sending the model data (containing the email field) in the body of the request via the send() method:

    xhr.send(JSON.stringify(parameters));
    

    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 your XMLHttpRequest code or vice versa. The following updates your AJAX code to be consistent with the XMLHttpRequest. That is, it sends your parameter object in the request body.

    $.ajax({
        type: "POST",
        contentType: "application/json",
        url: path,
        headers: {
            RequestVerificationToken: GetMyToken()
        },
        data: JSON.stringify(parameters),
        success: function (response) {
            //do stuff
        },
        error: function (xhr) {
            //do stuff
        },
        complete: function () {
            //do more stuff
        }
    });
    

    You’d need to keep the [FromBody] attribute in your controller method:

    public JsonResult Test([FromBody] TestRequestModel requestModel)
    
    Login or Signup to reply.
  2. 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?

    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 is
    application/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 specifying contentType: 'application/json', jQuery will serialize the data in application/x-www-form-urlencoded format.

    If you do not want to use [FromBody] attribute, a working demo like below:

    <form id="testForm" method="post">
        <input type="text" id="Email" value="[email protected]" />
        <button type="button" onclick="submitForm()">Submit</button>
    </form>
    @section Scripts
    {
        <script>
            function submitForm()
            {
                var formData = new FormData();
                formData.append("Email", $("#Email").val());
                CallServer(null, null, "/Home/test", formData)
            }
            function CallServer(resolve, reject, path, parameters) {
                let xhr = new XMLHttpRequest();
    
                xhr.open("POST", path, true);
                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(parameters);
            }
            function GetMyToken() {
                return $('[name=__RequestVerificationToken]').val();
            }
        </script>
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search