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.

7
  • 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) Commented Mar 27, 2016 at 14:02
  • Thanks for responding :), I've updated my question, Please, have a look. Commented Mar 27, 2016 at 14:51
  • add contentType: 'application/json, charset=utf-8' in ajax request Commented Mar 27, 2016 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 :( Commented Mar 27, 2016 at 15:21
  • Your Bind attribute does not include SalesSubs so the model binder is ignoring it when parsing the model Commented Mar 27, 2016 at 15:54

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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.
That is because of your Bind attribute. See my updated answer.
That's the answer but I still managed to create a class for validating JSON anti forgery token, check KEN Q's answer here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.