2

I have this problem where I try to have an AJAX call to post data the user put in on the webpage to the controller method. The data never arrives at the controller, it always throws an error. The problem is probably that the data the controller expects is not of the same type as what the AJAX call sends to it, but I am really at a loss at what the expected data should look like.

Here's the JS/jQuery that builds the array, which is an array of an array of a table cell's values:

    var i = 0;
    var cells = new Array(); // cell values in one column
    var rows = new Array(); // list of columns (rows)

    $('#datatable').find('tr').each(function () {
        $('td :not([type="button"],[id="Nr."])', this).each(function () {
            cells.push($(this).val());
        });
        if (i > 0) {
            rows.push(cells);
            cells = [];
        }
        i++;
    });

this is what the AJAX call looks like:

    rows = JSON.stringify({ 'rows': rows });

    $.ajax({
        url: "/Home/Q_FourthPage",
        type: "POST",
        datatype: "json",
        traditional: true,
        data: rows,
        success: function (response) {
            alert("Success");
            console.log("Sucess ", response);
        },
        error: function (response) {
            alert("Error");
            console.log("Error ", response);
        }
    });

The controller is still empty - what parameter should it get in order for the data to arrive as an array of an array of strings/integers/whatever? Thank you.

Edit: This is the view model the Q_FourthPage controller method uses:

public class Q4_Answer_VM
{
    public Models.User User { get; set; }
    public List<Models.Capacity> Matrix { get; set; } //this is where the data should go
                                                      //a capacity describes one row of the table
    public Models.QuestionText TMatrix { get; set; }

}

The capacity class looks like this - it is one row of the table.

public class Capacity
{
    // This class is a model for the capacity matrix. it represents one column.

    //PK        
    public int ID { get; set; }

    //FK
    public int Questionnaire_ID { get; set; }

    //Values
    public String Load { get; set; }
    public String Source { get; set; }
    public String Goal { get; set; }
    public String Frequency { get; set; }
    public String Distance { get; set; }


    public virtual Questionnaire Questionnaire_ { get; set; }


}

Here's the head of the controller method:

  [HttpPost]
  public ActionResult Q_FourthPage(ViewModels.Q4_Answer_VM vm)

There are a lot of attributes in these classes - is that why the call fails?

Edit No. 2: The view

Here's the view:

@model MyANTon.ViewModels.Q4_Answer_VM

@{
    ViewBag.Title = "myANTon Anforderungserfassung";
    ViewBag.HideNavBar = false;
}

@using (Html.BeginForm(Html.BeginForm("Q_FourthPage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })))

{

    <div class="container">
        <div align="center">
            <ol class="breadcrumb" align="center" text-align="center">
                <li class="breadcrumb-item">@Html.ActionLink("Lasten", "Q_Firstpage", "Home", new { @class = "elements" }, null)</li>
                <li class="breadcrumb-item">@Html.ActionLink("Lastaufnahme-/abgabe", "Q_Secondpage", "Home", new { @class = "elements" }, null)</li>
                <li class="breadcrumb-item">@Html.ActionLink("Weitere Anforderungen", "Q_Thirdpage", "Home", new { @class = "elements" }, null)</li>
                <li class="breadcrumb-item"><b><u>@Html.ActionLink("Kapazitäten", "Q_Fourthpage", "Home", new { @class = "elements" }, null)</u></b></li>
                <li class="breadcrumb-item">@Html.ActionLink("Auftragserzeugung", "Q_Fifthpage", "Home", new { @class = "elements" }, null)</li>
            </ol>
        </div>
        <div class="jumbotron">

            <div>
                @Html.TextBox("file", "", new { type = "file" }) <br />

                <input type="submit" value="Upload" />

                @ViewBag.Message

            </div>

            <h1>4. Kapazitätsbetrachtung</h1>
            <p>Wie viele Fahrzeuge werden benötigt? Um dies auszurechnen wird eine Transportmatrix benötigt.</p>
            <p>Ein Beispiel einer Transportmatrix ist in Tabelle 1 zu sehen.</p>
            <p>Um die Anzahl der Roboter zu berechnen ist auch eine Angabe der Produktionsschichten relevant:</p>
            <ul>
                <li>In wie vielen Schichten läuft die Fertigung?</li>
            </ul>
            <p>
                Um mögliche Engpässe oder Umwege zu betrachten ist ein Layout der Produktionsstätte wie in Abbildung 3 von Vorteil.
                Idealerweise mit Angaben über andere Verkehrsteilnehmer (Personenverkehr, Staplerverkehr, Kreuzungen).
            </p>
            <input type="button" id="BtnPlus" name="BtnPlus" class="BtnPlus" value="Zeile hinzufügen (+)" align="center" style="vertical-align: middle" />

            <table class="grid" id="datatable" style="table-layout:fixed">
                <thead>
                    <tr style="text-align: center" id="header">
                        <th style="vertical-align: middle" id="Nr.">Nr.</th>
                        <th>Last</th>
                        <th>Quelle</th>
                        <th>Ziel</th>
                        <th>Frequenz [/h]</th>
                        <th>Abstand [m]</th>
                        <th>Zeile löschen</th>
                </thead>
                <tbody></tbody>
            </table>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" class="btn btn-default" name="Speichern" value="Speichern" id="BtnSave" />
                    <input type="submit" class="btn btn-default" name="Speichern und weiter" value="Speichern und weiter" />
                    <input type="button" class="btn btn-default" value="Weiter zu Schritt 5" onclick="@("window.location.href='" + @Url.Action("Q_Fifthpage", "Home") + "'");" />
                </div>

            </div>

        </div>
    </div>

}


<script src="~/Scripts/jquery-3.2.1.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.datatables.net/1.10.16/css/jquery.dataTables.css">
<script type="text/javascript" charset="utf8" src="//cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script>
@Scripts.Render("~/bundles/datatables")

Edit no. 3: it works now thanks to the solution provided by user derloopkat below. As a hint: it seems that Ajax and Forms don’t go well together. I had to move the “save” button outside the form (@using...{}).

mneumann
  • 713
  • 2
  • 9
  • 42
  • 1
    Would you share Q_FourthPage action and model class ? – lucky Jan 04 '18 at 12:54
  • see my edit, i added the view model, the model class of the "capacity" which is the array of arrays and the start of the controller method. I know I am not passing all the data that the class expects. I would like to send only the array data first and then build the capacity matrix in the controller before I save it to the database. – mneumann Jan 04 '18 at 13:01
  • appearently you are sending an matrix of strings (array of arrays) and your webmethod should recieve that '[HttpPost] public ActionResult Q_FourthPage(string[] rows)' – LPZadkiel Jan 04 '18 at 13:13
  • I guess I am sending an array of an array of strings. string[] rows would just be an array of strings, though? – mneumann Jan 04 '18 at 13:27
  • Even if it's a list instead of matrix, your html page has only one value for each cell whereas in `List`, Capacity has different members. Where does the value goes? – derloopkat Jan 04 '18 at 13:34
  • Apparently, jagged arrays can't really be used as function parameters. Is that true? – mneumann Jan 04 '18 at 13:34
  • @derloopkat1 The table has five cells each row which corresponds to five values in the capacity: load, source etc. – mneumann Jan 04 '18 at 13:36
  • Sorry about the spam, but does anyone know what the function parameter of Q_FourthPage should be? – mneumann Jan 04 '18 at 14:08

1 Answers1

3

This worked. The thing is creating an object in json matching the exact names and structure in your model. You don't need to have all members in your json but those who appear should match the model if you want to be able to retrieve their values at server.

var capacities = new Array(); // cell values in one column
$('#datatable').find('tr').each(function () {
    var cells = $(this).find('td :not([type="button"],[id="Nr."])');
    var capacity = {
        Load: cells[0].value,
        Source: cells[1].value,
        Goal: cells[2].value,
        Frequency: cells[3].value,
        Distance: cells[4].value
    };
    capacities.push(capacity);
});

In your Ajax call, dataType is what you expect to get from server, not the type you're sending (that is contentType).

var model = JSON.stringify({ 'Matrix': capacities });
$.ajax({
    url: "/Home/Q_FourthPage",
    type: "POST",
    contentType: "application/json; charset=utf-8",
    traditional: true,
    data: model,
    success: function (response) {
        alert("Success");
        console.log("Sucess ", response);
    },
    error: function (response) {
        alert("Error");
        console.log("Error ", response);
    }
});

My table was dummy Html.

<table id="datatable">
    <tr>
        <td><input type="text" value="11" /></td>
        <td><input type="text" value="12" /></td>
        <td><input type="text" value="13" /></td>
        <td><input type="text" value="14" /></td>
        <td><input type="text" value="15" /></td>
    </tr>
    <tr>
        <td><input type="text" value="21" /></td>
        <td><input type="text" value="22" /></td>
        <td><input type="text" value="23" /></td>
        <td><input type="text" value="24" /></td>
        <td><input type="text" value="25" /></td>
    </tr>
</table>

And action method is:

[HttpPost]
public ActionResult Q_FourthPage(ViewModels.Q4_Answer_VM vm)
{
    return View("Index");
}

If you intend to return json from here, then you need to adddataType: 'json' in Ajax call.

derloopkat
  • 6,232
  • 16
  • 38
  • 45
  • Thank you for the answer. Unfortunately, this does not work here. The `vm` in the controller is still null and I still receive an Error message from the ajax call. Furthermore, the jquery function does not get all the data from the table but only the first row n times, n being the number of `td` in the table. Is there a way I can see the contents of the ajax data, like in a debugger or something like that? – mneumann Jan 04 '18 at 15:58
  • Replicated row issue was caused by your second `each` that isn't using current row from parent 'each'. Need to add `$(this).find` to fix that. I have edited my answer. With regards to `vm` model getting null and error messages, check what's different in your code. – derloopkat Jan 04 '18 at 18:31
  • thanks a lot, I will try this on monday and get back to you! – mneumann Jan 04 '18 at 22:20
  • is it possible that some model binding in the view might be the problem? for now, my view is pretty convoluted because there have been multiple people working on it. I will update the question with the view. – mneumann Jan 08 '18 at 11:54
  • @mneumann, well if Capacity has a constructor that takes a parameter and there's no parameterless constructor, that would break the binder before your code runs. But this ground for speculation. You need to able to reproduce the error you're having before posting question according to the rules https://stackoverflow.com/help/mcve. – derloopkat Jan 08 '18 at 12:27
  • ok - thanks a lot. My table looks different, since the values are not stored as `value = X` so `cells[0].value` always returns `undefined`. But this is another problem, I guess, as you hint. I add multiple cells with the `table.row.add()` function from the `datatables` library. They look like this: `''`. Can you give me a hint how I would get the input values of each one? `cells[0].val()` only returns the first value of the object, and `cells[0].value` does not work at all. – mneumann Jan 09 '18 at 13:52
  • try adding space and "input" at the end of the selector `$(this).find('td:not([type="button"],[id="Nr."]) input')`. This should select inputs instead of td tags. – derloopkat Jan 09 '18 at 14:14
  • I accepted your answer, since it works great now. I also solved my problem with the ajax (which always returned error). Turns out my "save" button was inside a form, which apparently causes JQuery to always return "error" - see also [this very helpful answer](https://stackoverflow.com/a/30006363/6892466). – mneumann Jan 15 '18 at 11:48