11

I have a form that is generated via jquery:

 $.get("/api/get/getListItems", function (data) {
                var table = "";
                table += "<table>";
                $.each(data, function (y, z) {
                    console.log(z);
                    table += '<tr>';
                    $.each(this, function (k, v) {
                        table += '<td><input type="text" name="' + k + '" id="' + k + '" value="' + v + '" /></td>';
                    });
                    table += '<td><input type="checkbox" name="selected" id="selected" /></td>';

                    table += '</tr>';
                });
                table += '<tr><td><input type="submit" id="submit" name="submit" value="Save To Database" /></td></tr>';
                table += '</table>';
                $('#form').html(table);
            });

and it generates this HTML (10 rows of input fields, 7 columns and 1 checkbox): http://jsfiddle.net/8zpr2fkL/1/

and I am submitting the form when the submit button is clicked:

$("#form").submit(function (event) {
        $.post("/api/update/", $("#form").serialize(), alert('success'));
    });

Now I am passing the data to my ASP.NET API Controller:

[HttpPost]
        public dynamic Post([FromBody]CellModel cells)
        {
                UpdateClass jobs = new UpdateClass();
                return jobs;
        }

and here is my CellModel class:

public class CellModel
    {
        public uint scheduleTaskID { get; set; }
        public string task { get; set; }
        public string baselineDate { get; set; }
        public string scheduledDate { get; set; }
        public string actualDate { get; set; }
        public string finishedDate { get; set; }
        public bool selected { get; set; }

        public override string ToString()
        {
            return scheduleTaskID.ToString();
        }
    }

My Problem is when I hit submit to submit the data and put a breakpoint on the controller method, cells count is 0, is there something I am missing here? I am trying to pass all the data in the input text to controller. Nothing is getting passed to my controller. What am I doing wrong?

This is data im trying to pass via jquery $('#form').serialize():

scheduleTaskID=194&task=Permit&baselineDate=6%2F23%2F2005+8%3A00%3A00+AM&scheduledDate=6%2F23%2F2005+8%3A00%3A00+AM&actualDate=6%2F23%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=195&task=Office+Files&baselineDate=7%2F13%2F2005+8%3A00%3A00+AM&scheduledDate=7%2F13%2F2005+8%3A00%3A00+AM&actualDate=7%2F13%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=196&task=Foundation&baselineDate=7%2F27%2F2005+8%3A00%3A00+AM&scheduledDate=7%2F27%2F2005+8%3A00%3A00+AM&actualDate=8%2F13%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=197&task=Framing&baselineDate=8%2F5%2F2005+8%3A00%3A00+AM&scheduledDate=8%2F5%2F2005+8%3A00%3A00+AM&actualDate=8%2F23%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=198&task=Finishes+Exterior&baselineDate=8%2F26%2F2005+8%3A00%3A00+AM&scheduledDate=8%2F26%2F2005+8%3A00%3A00+AM&actualDate=9%2F14%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=199&task=Drywall&baselineDate=9%2F2%2F2005+8%3A00%3A00+AM&scheduledDate=9%2F2%2F2005+8%3A00%3A00+AM&actualDate=9%2F16%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=200&task=Flooring&baselineDate=9%2F1%2F2005+8%3A00%3A00+AM&scheduledDate=9%2F1%2F2005+8%3A00%3A00+AM&actualDate=9%2F20%2F2005+8%3A00%3A00+AM&finishedDate=&scheduleTaskID=201&task=General+Finish&baselineDate=9%2F12%2F2005+8%3A00%3A00+AM&scheduledDate=9%2F12%2F2005+8%3A00%3A00+AM&actualDate=&finishedDate=&scheduleTaskID=202&task=Final+PDI&baselineDate=10%2F11%2F2005+8%3A00%3A00+AM&scheduledDate=10%2F11%2F2005+8%3A00%3A00+AM&actualDate=&finishedDate=&scheduleTaskID=203&task=Permit&baselineDate=4%2F6%2F2005+8%3A00%3A00+AM&scheduledDate=4%2F6%2F2005+8%3A00%3A00+AM&actualDate=4%2F6%2F2005+8%3A00%3A00+AM&finishedDate=

UPDATE

I have changed:

$("#form").submit(function (event) {
            $.post("/api/update/", $("#form").serialize(), alert('success'));
        });

to

$("#form").submit(function (event) {
        var array = [];
        $('#form > table > tbody  > tr').each(function (elem) {
            var item = {};
            item.scheduleTaskID = $(this).find("td > #scheduleTaskID").val();
            item.task = $(this).find("td > #task").val();
            item.baselineDate = $(this).find("td > #baselineDate").val();
            item.scheduledDate = $(this).find("td > #scheduledDate").val();
            item.actualDate = $(this).find("td > #actualDate").val();
            item.finishedDate = $(this).find("td > #finishedDate").val();
            item.selected = $(this).find("td > #selected").val();
            array.push(item);
        });
        console.log(JSON.stringify(array));
        $.post("/api/update/", JSON.stringify(array), alert('success'), 'json');
    });

in my console log my data looks like this:

[{"scheduleTaskID":"203","task":"Permit","baselineDate":"4/6/2005 8:00:00 AM","scheduledDate":"4/6/2005 8:00:00 AM","actualDate":"4/6/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"195","task":"Office Files","baselineDate":"7/13/2005 8:00:00 AM","scheduledDate":"7/13/2005 8:00:00 AM","actualDate":"7/13/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"196","task":"Foundation","baselineDate":"7/27/2005 8:00:00 AM","scheduledDate":"7/27/2005 8:00:00 AM","actualDate":"8/13/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"197","task":"Framing","baselineDate":"8/5/2005 8:00:00 AM","scheduledDate":"8/5/2005 8:00:00 AM","actualDate":"8/23/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"198","task":"Finishes Exterior","baselineDate":"8/26/2005 8:00:00 AM","scheduledDate":"8/26/2005 8:00:00 AM","actualDate":"9/14/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"199","task":"Drywall","baselineDate":"9/2/2005 8:00:00 AM","scheduledDate":"9/2/2005 8:00:00 AM","actualDate":"9/16/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"200","task":"Flooring","baselineDate":"9/1/2005 8:00:00 AM","scheduledDate":"9/1/2005 8:00:00 AM","actualDate":"9/20/2005 8:00:00 AM","finishedDate":"","selected":"on"},{"scheduleTaskID":"201","task":"General Finish","baselineDate":"9/12/2005 8:00:00 AM","scheduledDate":"9/12/2005 8:00:00 AM","actualDate":"","finishedDate":"","selected":"on"},{"scheduleTaskID":"202","task":"Final PDI","baselineDate":"10/11/2005 8:00:00 AM","scheduledDate":"10/11/2005 8:00:00 AM","actualDate":"","finishedDate":"","selected":"on"},{"scheduleTaskID":"203","task":"Permit","baselineDate":"4/6/2005 8:00:00 AM","scheduledDate":"4/6/2005 8:00:00 AM","actualDate":"4/6/2005 8:00:00 AM","finishedDate":"","selected":"on"},{}]

and in my ASP.NET API Controller, I changed my method to this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class UpdateController : ApiController
    {
        [HttpPost]
        public dynamic Post(List<CellModel> cells)
        {
                UpdateClass jobs = new UpdateClass();
                //jobs.PostScheduledTasks(cells);
                return cells;
        }

    }
}

I put a breakpoint at the start of the method Post and when it hits the breakpoint, it says cells Count = 0..I see network call, only if i put a return false after my post call and the response is empty [] Why is the data not passing to my controller, is it because the form is being generated by jquery?

UPDATE

Still no solution, I looked at my network call this AM and the Status Code is 301:

enter image description here

Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
user979331
  • 11,039
  • 73
  • 223
  • 418
  • 1
    Shouldn't that be a list of CellModels? It looks like you are sending back more than 1... This may not solve the problem, but its something to look at... – Dylan Corriveau Oct 22 '14 at 13:38
  • Yes I am sending more than one.... – user979331 Oct 22 '14 at 13:40
  • One other thing to try. Have you taken out the [FromBody] from your post? Also, is that data generated from something like Fiddlr? Is that what is being passed over the network? – Dylan Corriveau Oct 22 '14 at 14:31
  • If you do this from Chrome, you can use the inspector, the Network tab and see exactly what was posted back to your controller. I'd also put the breakpoint after the Post line, to be sure cells is populated before breaking. – Jason Oct 22 '14 at 18:13
  • Also, I use this to test my controllers/REST APIs: https://chrome.google.com/webstore/detail/dev-http-client/aejoelaoggembcahagimdiliamlcdmfm – Jason Oct 22 '14 at 18:14
  • This may seem silly, but what happens if you remove the `dynamic` return type and instead returned something like an `object` (or better yet, an `HttpResponseMessage`)? – entropic Oct 24 '14 at 13:59
  • what would this look like? – user979331 Oct 24 '14 at 14:26

3 Answers3

23

Don't use $.post use ajax post and set the content type to "application/json; charset=utf-8"

var data = JSON.stringify(array);
$.ajax({
  url:"/api/update/",
  type:"POST",
  data:data,
  contentType:"application/json; charset=utf-8",
  dataType:"json",
  success: function(data){
    console.log(data);
  }
});

the problem is that you need to say to the webserver you are sending json and is not possible with $.post

This is really normal, and I have been struggling with this too (and sometimes I still forget), here you can see that you have to use $.ajax

Jquery - How to make $.post() use contentType=application/json?

Community
  • 1
  • 1
dariogriffo
  • 4,148
  • 3
  • 17
  • 34
  • 2
    @user979331 It works, but it has worse time-space performance than a solution using $.post (check out my answer). You are converting the form elements to JSON in a foreach loop, and doing repeated DOM search using jQuery which is completely unnecessary. It is much simpler to just serialize the form and post if you follow the rules outlined below. – Faris Zacina Oct 31 '14 at 12:29
  • @TheZenCoder, didn't know your approach, is good, but I after reading all the links I found that the lost of performance on the client side is despicable. (to be honest) I may not have worked with huge forms, I don't know any other, but the average form you can create there is no loss. Anyway I voted your approach as positive. Have a nice weekend :D – dariogriffo Oct 31 '14 at 15:20
  • 1
    @dariogriffo, yeah maybe it's not a huge performance difference, but the code to serialize() a form and $.post is simpler, since you don't have to deal with JSON. Anyway, your approach works too and it is widely accepted. It's always good to know multiple solutions to the same problem ;) Thanks for the upvote! – Faris Zacina Oct 31 '14 at 15:28
4

While you got an alternative approach by @dariogriffo, i want to give you a full solution using your initial approach with $.post.

Your initial approach with form serialization was correct, so the following code is correct:

$("#form").submit(function (event) {
   $.post("/api/update/", $("#form").serialize(), alert('success'));
});

However, this wasn't working because your dynamic form is not following the naming conventions for input fields expected by the ASP.NET MVC Default model binder, and as a consequence your serialized form was not something the default model binder was capable of binding to your cells model. That is why you were not getting any cells in the controller when doing a POST.

To clarify what that means, ASP.NET expects each input field that corresponds to a model property to have the following name format if you are posting to a regular MVC 5 Controller:

actionattributename[index].propertyname

If you are posting to a Web API 2 controller it should be:

[index].propertyname

Since your action attribute is named cells and it has a property scheduledTaskID, and you are posting to a WebAPI controller one of your inputs would look like:

<input type="text" name="[0].scheduleTaskID" id="scheduleTaskID" value="194">

There are a couple more rules involved in structuring a form to make it bindable. You can find a nice blog post about it here:

http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

If you followed that form convention you would be able to Post your serialized form to your controller expecting a List<CellModel> cells without having to use the JSON approach, which is much more expensive as suggested in the other answers.

Below is a fiddle with an example form structured correctly by Web API 2 default binder rules:

http://jsfiddle.net/8zpr2fkL/12/

You can try to $.post this form using $.post to your web api controller and it should work like a charm!

Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
0

I know, this is already solved using .ajax instead of .post. I thought of sharing this , as i have solved this using .post. As mentioned above, since it posts with content type Content-Type:application/x-www-form-urlencoded; charset=UTF-8, parameter cells in post method will contain count = 0.

To solve this, you have to manually capture request object and get post data and then do deserialize and get object as List<CellModel>. I have used all posted code by OP, and just modified post method as shown in following, and it worked.

[HttpPost]
public dynamic Post(List<CellModel> cells)
{
    string content = string.Empty;
    if (HttpContext.Current.Request.InputStream.CanSeek)
    {
        HttpContext.Current.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
    }
    using (System.IO.StreamReader reader = new System.IO.StreamReader(HttpContext.Current.Request.InputStream))
    {
        content = reader.ReadToEnd();
    }
    if (!string.IsNullOrEmpty(content))
    {
        // Deserialize and operate over cells.
        try
        {
            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject(content, typeof(List<CellModel>));
        }
        catch (Exception ex)
        {
            return ex;
        }

    }
    return cells;
}

Here is what i get while debugging.

enter image description here

enter image description here

Arindam Nayak
  • 7,346
  • 4
  • 32
  • 48
  • Suppose that you have 120 POST endpoints in your API this is completely unmaintainable – dariogriffo Oct 26 '14 at 13:08
  • I don't know, what will be OP's usecase for this, if you are aware of that, let me know, i've just tried to solve with `.post`, and that is doable. But yes, `.ajax` is best way to handle `HTTPPost` used in webapi. – Arindam Nayak Oct 26 '14 at 13:35