0

I'm developing an ASP.Net MVC5 application in which I'm trying to pass JSON data to the controller's action method through an AJAX request. The JSON data contains a number of form values. Since I'm trying to create a master detail form which has data to be posted in one-to-many format in the database I created an object like this:

var salesmain = { 
    "SalesId": "", 
    "ReferenceNo": "", 
    "SalesDate": "", 
    "SalesPerson": "", 
    "SalesSubs": [] 
};

Note that in the above data the SalesSubs property is an array which is declared in this way :

var salessub = {    
    "SalesId": "", 
    "ItemName": "",
    "Qty": "",
    "UnitPrice":""
};

So, now I get to the point where one salesmain can contain many salessub or array of salessub. After I push some values in it and pass through the AJAX request to the action method it has the SalesSubs array as null. I've checked it by placing a breakpoint there :

enter image description here

In the above image salesmain.SalesSubs, which is an array, is null and this is how I'm trying to pass the data to the action :

$.ajax({
    url: '/Sales/Create',
    data: addRequestVerificationToken(salesmain),
    type: 'POST',
    dataType: 'json',
    traditional : true,
    success: function (result) {
        if (result.Success == "1") {
            window.location.href = "/Sales/Index";
        }
        else {
            alert(result.ex);
        }
    }
});

This is how I'm pushing values in the SalesSubs array :

salesmain.SalesId = $("#SalesId").val();
salesmain.ReferenceNo = $("#ReferenceNo").val();
salesmain.SalesDate = $("#SalesDate").val();
salesmain.SalesPerson = $("#SalesPerson").val();

$('.tbl').DataTable().rows().data().each(function (value, index) {
    salessub.ItemName = value[0];
    salessub.Qty = value[1];
    salessub.UnitPrice = value[2];
    salesmain.SalesSubs.push(salessub);
});

In the above code I've used jQuery's datatable plugin to fetch the whole salessub data from the DataTable. I think the salesmain contains a complete valid data before the ajax request is executed, but somehow the ajax request did not carry the array data (SalesSubs) of the salesmain object after it is executed.

Is there anything wrong with my AJAX part of the code? What is the correct way to pass JSON data to the action method using an AJAX request?

EDIT :

Here is SalesMain.cs model :

public class SalesMain
{
    [Key]
    public int SalesId { get; set; }
    public string ReferenceNo { get; set; }
    public DateTime SalesDate { get; set; }
    public string SalesPerson { get; set; }

    public virtual ICollection<SalesSub> SalesSubs { get; set; }
}

and SalesSub.cs model :

public class SalesSub
{
    [Key, Column(Order = 0)]
    public int SalesId { get; set; }
    [Key, Column(Order = 1)]
    public string ItemName { get; set; }
    public int Qty { get; set; }
    public decimal UnitPrice { get; set; }
    public virtual SalesMain SalesMain { get; set; }
}

and SalesController Create Method :

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "SalesId,ReferenceNo,SalesDate,SalesPerson")] SalesMain salesmain)
    {
        try
        {
            if (ModelState.IsValid)
            {
                // Perform Update 
                if (salesmain.SalesId > 0)
                {

                    var CurrentsalesSUb = db.SalesSubs.Where(p => p.SalesId == salesmain.SalesId);

                    foreach (SalesSub ss in CurrentsalesSUb)
                        db.SalesSubs.Remove(ss);

                    foreach (SalesSub ss in salesmain.SalesSubs)
                        db.SalesSubs.Add(ss);

                    db.Entry(salesmain).State = EntityState.Modified;
                }
                //Perform Save 
                else
                {
                    db.SalesMains.Add(salesmain);
                }

                db.SaveChanges();
                return Json(new { Success = 1, SalesID = salesmain.SalesId, ex = "" });
            }
        }
        catch (Exception ex)
        {
            return Json(new { Success = 0, ex = ex.Message.ToString() });
        }

        return Json(new { Success = 0, ex = new Exception("Unable to save").Message.ToString() });
    }

and if I console.log(salesmain) before the ajax request is sent, this is what I get :

enter image description here

So, I think that the above data is fine to move through the ajax request, But now when it reaches the action salesmain.SalesSubs becomes null, and only the SalesMain table is populated in the database, and the SalesSub table remains empty (No mapping achieved).

Further Edit :

Adding contentType : 'application/json' and data : JSON.stringify(salesmain) leads to a 500 internal server error, after inspection in console window, the error says :

The required anti-forgery form field “__RequestVerificationToken” is not present.

Is it really not present?, because I've already created a function for this :

function addRequestVerificationToken(data) {
        data.__RequestVerificationToken = $('input[name=__RequestVerificationToken]').val();
        return data;
    };

the above code does not add the token to the salesmain when I use contentType : "application/json" and JSON.stringify(), it adds when these two are removed.

Bilal Ahmed
  • 1,043
  • 4
  • 17
  • 32
  • 1
    This sounds like a ModelBinder issue. Can you please add the exact JSON string you're sending in the request and also the signature of the receiving Action (along with the class outlines) – Rory McCrossan Mar 27 '16 at 14:02
  • Thanks for responding :), I've updated my question, Please, have a look. – Bilal Ahmed Mar 27 '16 at 14:51
  • add contentType: 'application/json, charset=utf-8' in ajax request – santosh singh Mar 27 '16 at 15:01
  • @Santosh, I've also tried this, adding contentType: 'application/json' does not even pass the data to the action, giving error on the client side `The required anti-forgery form field "__RequestVerificationToken" is not present`, it is not reading the token field, for the sake of argument, I removed [ValidateAntiForgery] action filter, and then checking it, `SalesSubs` is still `null` :( – Bilal Ahmed Mar 27 '16 at 15:21
  • Your `Bind` attribute does not include `SalesSubs` so the model binder is ignoring it when parsing the model – Nkosi Mar 27 '16 at 15:54
  • @Nkosi , It changes a bit, but bothers even more, including `SalesSubs` now not show the `null` but it shows `Count = 0`, no matter how many rows `SalesSubs` have, it always shows `Count = 0` and not even getting inside the action and hence throws exception. Is it really necessary to add it, because Entity framework itself did not. – Bilal Ahmed Mar 27 '16 at 17:21
  • RE: `The required anti-forgery form field "__RequestVerificationToken" is not present`, is it safe to assume that you have `@Html.AntiForgeryToken()` somewhere in your code and that your client side function is actually getting the value. – Nkosi Mar 27 '16 at 18:04

1 Answers1

1

Convert your js object to a json stringified version using JSON.stringify method. Also specify the contentType as "application/json".

$.ajax({
        data: JSON.stringify(salesmain),
        contentType : "application/json",
        success: function (result) { alert(result); }
       // Your existing code
});

I also suggest using Html helpers such as Url.Action to generate the correct relative path to your action method instead of hard coding them.

url: '@Url.Action("Create","Sales")',

The above should work if your js code is inside a razor view. If your code is inside an external js file, Use the solution described in this post.

EDIT : When you send the data with contentType='application/json', your RequestVerificationToken is not going to work because the code expect the form to be submitted as normal ( where contentType is set as default "application/x-www-form-urlencoded"). So you may either disable the [ValidateAntiForgeryToken] in your HttpPost action method.

Also i see you have used Bind attribute to include only certain properties. You need to add the SalesSubs properties to the include list so that Model binding works for that property as well.

The below code should work fine.

client side code to send the data .

var salesmain = { SalesSubs: [] };
salesmain.SalesId =33;
salesmain.ReferenceNo ="Test";
salesmain.SalesDate ="12/12/2004";
salesmain.SalesPerson = "Shyju";

var salessub = {};
salessub.ItemName = "Test";
salessub.Qty = 2;
salessub.UnitPrice = 25.33;
salesmain.SalesSubs.push(salessub);
console.log(salesmain);

$.ajax({
    url: '/Sales/Create'  ,
    data: JSON.stringify(salesmain),
    type: 'POST',
    contentType: 'application/json',
    success: function (result) {
        console.log('response');
        console.log(result);
    }
});

And the server code would be

[HttpPost]
public ActionResult Create([Bind(Include = "SalesId,ReferenceNo,SalesDate,SalesPerson
                                               ,SalesSubs")] SalesMain salesmain)
{
   // do something with salesmain
}

The best way to prevent over posting is to use a view model with properties you absolutely needed from the view. I personally prefer that approach to transfer data between views and action method instead of using ORM generated entity classes with Bind attribute.

Community
  • 1
  • 1
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thanks for answering, I've already tried it, adding contentType: 'application/json' does not even pass the data to the action, giving error on the client side `The required anti-forgery form field "__RequestVerificationToken" is not present`, it is not reading the token field when I pass the data your way, I removed `[ValidateAntiForgery]` action filter, and then checked it, SalesSubs is still `null` , Also, I've already created a function to append `__RequestVerificationToken` (Updated my question), So, it appends when I remove `contentType : "application/json"` and `JSON.stringify` else not. – Bilal Ahmed Mar 27 '16 at 17:03
  • That is because of your Bind attribute. See my updated answer. – Shyju Mar 27 '16 at 17:44
  • That's the answer but I still managed to create a class for validating JSON anti forgery token, check KEN Q's answer [here](http://stackoverflow.com/questions/2906754/how-can-i-supply-an-antiforgerytoken-when-posting-json-data-using-ajax) – Bilal Ahmed Mar 29 '16 at 12:33