3

I've been wracking my brain and the various SO posts on the same topic (see JQuery Ajax and Posting multiple complex objects to asp.net MVC Controller, Passing multiple objects to my controller, etc) but I'm having trouble getting multiple objects to post from an Ajax call to my controller (.NET Core). I believe I have set up my code like this tutorial has (http://www.dotnetcurry.com/aspnet-mvc/1253/multiple-model-objects-input-controller-action-methods) and have also used suggestions from here: http://andrewlock.net/model-binding-json-posts-in-asp-net-core/ to no avail.

The issue: I can only get the first, but not the second, object to bind.

Controller method:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Apply(ApplicationInfoViewModel info, SRAgreementViewModel agreement)
{
     if(ModelState.IsValid){
     //do stuff. ModelState.IsValid returns false here.
     }
}

Here's my JS:

$(function () {
    $("#ApplyBAOForm").submit(function (e) {        
        var jsonObj = $('#ApplyForm').serialize();
        var srFormJsonObj = $('#SRForm').serialize();

        $.ajax({
            url: "ApplyBao/Apply",
            datatype: "json",
            contentType: "application/json; charset=utf-8",
            data: { info: jsonObj, agreement: srFormJsonObj },
            type: "POST"
        }).done(function (result) {
            console.log("Apply success: " + result);
            if (result.type === "success") {
                 //hooray  
            }
            else {
                //boo                
            }
        });

    });
});

So this is a bit of an odd situation: there's actually data from two separate forms here. The SRform data is validated prior to submitting the ApplyForm (it is in a modal), but is only actually submitted for persistence with the ApplyForm. The resulting serialized objects from both forms appears to be correct and similar (jsonObj and srFormObj).

When I hit the ModelState.IsValid statement, it returns false because the SRAgreement object is essentially null (with default values). However, the ApplicationInfoViewModel object correctly shows data from the form.

Other things I've tried:

  • combining the two objects into one view model (both child objects wound up being null?)
  • Adding the SRAgreement object as a child of the ApplicationInfo object; the SRAgreement object was null.
  • adding [FromBody] as described by the blog postfrom Andrew Lock, this jsut resulted in an HTTP 415 unsupported media type response

There's likely a better way to do this, but I would think that what I'm attempting to do should be doable.

Thanks!

EDIT 2:

It appears that I can't even bind one JSON object. I've got to be missing something simple. I've pared it down to just submitting one of the two objects as JSON as such:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Apply([FromBody] ApplicationInfoViewModel info) 
{
    if (ModelState.IsValid)
    {
     // do stuff
    }
 }

JS:

$("#ApplyBAOForm").submit(function (e) {
        e.preventDefault();

        var jsonObj = ConvertFormToJSON($("#ApplyBAOForm"));
        var srFormJsonObj = ConvertFormToJSON($("#SRForm"));
        var obj2 = JSON.stringify({ info: jsonObj });

        $.ajax({
            url: "ApplyBao/Apply",
            datatype: "json",
            contentType: "application/json; charset=utf-8",
            data: obj2,
            //data: JSON.stringify(jsonObj),
            type: "POST"
        }).done(function (result) {
            console.log("Apply success: " + result);

            }
            else {
                //show some error message or something                
            }
        });        
    });
});

Now the request looks like this:

Request URL:http://localhost:19194/ApplyBao/Apply
Request Method:POST
Status Code:400 Bad Request
Remote Address:[::1]:19194
Response Headers
view parsed
HTTP/1.1 400 Bad Request
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccmdiMDA3M1xEb2N1bWVudHNcYmFvYWRtaW5pc3RyYXRpb25cc3JjXEJBT0FkbWluaXN0cmF0aW9uXEFwcGx5QmFvXEFwcGx5?=
X-Powered-By: ASP.NET
Date: Thu, 12 Jan 2017 21:59:27 GMT
Content-Length: 0
Request Headers
view parsed
POST /ApplyBao/Apply HTTP/1.1
Host: localhost:19194
Connection: keep-alive
Content-Length: 580
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://localhost:19194
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: application/json; charset=UTF-8
Referer: http://localhost:19194/ApplyBao
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: .AspNetCore.Antiforgery.l7Q8WGuEtq8=CfDJ8K9L5D4kNnBPrb3_9Hu-w57rjjf751Qf9eh-z57qWmtMA53AB6FXsZ7pbIuLkdP2F6GjA7TGl0Tz7TfACCn3QeFt_uC-BgsYnk3Nd8z0ZA0c90XVEA90NnQOnmVFRu_KF2_2DXV89Jur84rMa-s26nQ
Request Payload
view parsed
{"info":{"HearAboutUsSelectedId":"137","FirstName":"Ron","LastName":"Bud","MiddleName":"","MaidenName":"","Email":"r@test.com","BirthDate":"1900-01-15","Phone":"123456","Fax":"","Street":"1234","City":"Denton","State":"TX","Zip":"12345","Country":"USA","SelectedExamTrack":"BCaBA","SelectedTranscriptSendMethod":"requested","Degree":"Education","SraId":"00000000-0000-0000-0000-000000000000","__RequestVerificationToken":"CfDJ8K9L5D4kNnBPrb3_9Hu-w56wPCITEDVZQo7flUIB70Pu4Q81TlRXa_oI4t8Bleou6l45oHHmUFrKusKofA6Gey-uSgKP7M3L-DrawE1TVJnrDsULHlnOE9ngg9LuyFK6-cylBJ-91h5fmaico-0yrZE"}}

The JSON now looks like a proper JSON object, but it seems my princess is in another castle. Is there something else in the pipeline that I'm missing to enable this?

UPDATE:

I finally figured it out. Turns out that the AntiForgery token was causing the request to fail, as mentioned here: POSTing complex JSON to ASP.NET Core Controller Removing that for the moment is allowing me to move forward. Thanks!

Community
  • 1
  • 1
RGB
  • 103
  • 2
  • 9
  • 1
    Wrap the two objects inside a view model and just receive that one wrapper object. Also, since you are .net core, you *must* have the `[FromBody]`. – nurdyguy Jan 12 '17 at 19:42
  • I've tried using the [FromBody] with a wrapper object, but I'm now getting "Failed to load resource: the server responded with a status of 415 (Unsupported Media Type)" . I'll look into this error more. – RGB Jan 12 '17 at 19:53

2 Answers2

3

You are not actually sending 2 objects, you are sending 1 object which contains 2 other objects. That is what this line is, the creation of a single object that will be sent.

data: JSON.stringify( { info: jsonObj, agreement: srFormJsonObj } ), // this is a single object that is created and sent by the http action

You need to receive it the same way on the server with a single model that then contains the 2 types you are currently using as the method parameters.

Model code

public class Model{
    public ApplicationInfoViewModel Info{get;set;} 
    public SRAgreementViewModel Agreement {get;set;}
}

Controller code

public IActionResult Apply([FromBody]Model model)
{
     if(ModelState.IsValid){
     //do stuff. ModelState.IsValid returns false here.
     }
}

Edit

The error is probably because you are sending both url encoded data and json and in a json object. You should use one or the other but not a combination of both. If you look at your latest update you can see the values being sent for properties info and agreement and they are form encoded string values.

Igor
  • 60,821
  • 10
  • 100
  • 175
  • I did try that (and just tried again). Unfortunately, both child objects of the Model object end up being null. Any idea why? – RGB Jan 12 '17 at 19:50
  • @RGB - do you have the FromBody attribute (see edit)? – Igor Jan 12 '17 at 19:51
  • I have tried that as well, but I'm now getting "Failed to load resource: the server responded with a status of 415 (Unsupported Media Type)" instead. I'm still trying to figure out what to do with that. – RGB Jan 12 '17 at 19:54
  • 1
    Put `JSON.stringify(...)` around your model in the ajax call. – nurdyguy Jan 12 '17 at 19:57
  • Unfortunately doing `data: JSON.stringify({ info: jsonObj, agreement: srFormJsonObj })` results in the same error 415. – RGB Jan 12 '17 at 20:00
  • @RGB - intercept your request from your script and update your question with that information. Include the http headers as well as the message body. Also make sure the server is configured to allow json. – Igor Jan 12 '17 at 20:03
  • 1
    @RGB - of interest is the `content-type` header. See also http://stackoverflow.com/a/34685114/1260204 – Igor Jan 12 '17 at 20:04
  • Look at your content-type on the request, `application/x-www-form-urlencoded`. And above that the "unsupported media type". Igor's comment should fix that. – nurdyguy Jan 12 '17 at 20:19
  • @RGB - your json serialized message does not make sense. Part of the value is json but the other part is form encoded. See the values being sent in your latest update. This is probably why you are getting the error. – Igor Jan 12 '17 at 20:26
  • @nurdyguy I did see that, but I am both using the [FromBody] attribute and am setting the content type in the request. Any idea why I'd be getting 3 requests sent? – RGB Jan 12 '17 at 20:26
  • @igor that's what I was thinking. I figured it was ok since the first object was originally binding. I'll see if there is a better way to create that object. – RGB Jan 12 '17 at 20:28
  • @RGB - Not sure what you are using for your front end framework but angular, angular2, and other such UI frameworks make this sort of thing much easier as you are essentially dealing with objects on the client side and the fact you are actually dealing with html input fields is abstracted (for the most part). This then makes it easier to just json stringify an object you want to send to the server. – Igor Jan 12 '17 at 20:31
  • @Igor I'm just using MVC Core for the moment - no angular/2 or whatnot. – RGB Jan 12 '17 at 20:38
  • @RGB - those are 2 separate things. Angular/2 could basically replace what you are now using jQuery for, the sending of data from javascript back to your server asynchronously. You could still use MVC Core for your server and serving data and pages. – Igor Jan 12 '17 at 20:39
  • If what fires the event is a form submit button that you are also catching with a jQuery bind, remember to add `e.preventDefault` or it will do *both* the form submit and the ajax call. – nurdyguy Jan 12 '17 at 21:02
  • @RGB Do you have 3 forms? A wrapper and then 2 nested? That, plus the ajax, could explain why there are 3 actual submits happening. – nurdyguy Jan 12 '17 at 21:05
  • @nurdyguy There's actually 2 forms (not nested, the other is just later in the page); one that does an ajax call and just validates its data and the second form is the one I'm working with here. Throwing in the `e.preventDefault` does get rid of the HTTP 415 error, but I think the problem is now the format of the JSON object i'm trying to send back (as @Igor mentioned). Thanks for that tip, I think that is also causing some issues. – RGB Jan 12 '17 at 21:16
  • I've updated the post with the latest. Still no dice with only one proper JSON object. Ideas? – RGB Jan 12 '17 at 22:10
1

I also stumbled across this problem with .NET Core 2.1

It seems that only one parameter can be passed with the [FromBody] attribute. You have to summarize all parameters in one single object.

Example Ajax call:

 var data = {
                        messageIds: selectedIds,
                        read: true
                    };
                    $.ajax({
                        url: "@Url.Action("SetMessagesReadFlag", "Message")",
                        type: "POST",
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        data: JSON.stringify(data),
                        cache: false,
                        error: function(xhr, status, error) {
                            //do something about the error
                            alert(error);
                        },
                        success: function(response) {
                            if (response != null) {
                                grid.refresh();
                            }
                        }
                    });

Controller action with Dummy helper class:

[HttpPost]

public IActionResult  SetMessagesReadFlag([FromBody] Dummy data)
{
    ....
    return Json(true);
}

public class Dummy
{
    public List<Guid> MessageIds { get; set; }

    public bool Read { get; set; }
}
Sven
  • 2,345
  • 2
  • 21
  • 43