2

I have the following cshtml form:

model Scraper.Facade.PlayerRow

@using (Html.BeginForm("Calculate", "Home", FormMethod.Post))
{
<table class="table table-striped table-bordered table-condensed table-responsive table-hover">
    @foreach (var player in Model.AttribsPlayerLine)
    {
        <thead>
            <tr class="success">
                @foreach (var attrib in player.AttribsPlayerList)
                {
                    //@Html.DisplayNameFor(x => Model.tytul)
                    <th data-field="@attrib.attribName">@Html.DisplayFor(x => attrib.attribName) </th>
                }
            </tr>
            <tr class="active">
                @foreach (var attrib in player.AttribsPlayerList)
                {
                    <td data-field="@attrib.attribValue">@Html.TextBoxFor(x => attrib.attribValue)</td>
                }
            </tr>
        </thead>

    }
</table>

<input class="btn-danger"  type="submit" name="Next >>" value="Next >>" />
}

which is displaying correctly and then I am trying to get the model in the following controller ActionResult

[HttpPost]
public ActionResult Calculate(PlayerRow model)
{
    GenericHelper _genericHelper = new GenericHelper();
    return View();
}

However the PlayerRow model is always null.

What am I doing wrong?

This is my model definition

public PlayerRow LoadHtmlDoc(string fileLocation)
{
        List<Attrib> attribsHeaderList = new List<Attrib>();
        var playerRow = new PlayerRow();
        playerRow.AttribsPlayerLine = new List<AttribLine>();

        var htmlDoc = new HtmlDocument { OptionFixNestedTags = true };

        // There are various options, set as needed

        // filePath is a path to a file containing the html
        htmlDoc.Load(fileLocation);
}

public class PlayerRow
{
    public List<AttribLine> AttribsPlayerLine; 

}

UPDATE

Hi All, I changed a bit the logic of my application, basically having 2 lists which has the Header Attributes, and the Attributes for all the players, so only 2 classes now, and I changed the cshtml like this, which is working now :-

@using (Html.BeginForm("Calculate", "Home", FormMethod.Post, new { enctype =        "multipart/form-data" }))
{
<table class="table table-striped table-bordered table-condensed table-responsive table-hover">
    <tr>
        @for (int k = 0; k < Model.HeaderAttributes.Count; k++)
        {
            <td>
                @Html.DisplayFor(x => x.HeaderAttributes[k].AttributeName)
                @Html.HiddenFor(x => x.HeaderAttributes[k].AttributeName)
            </td>
        }
    </tr>


    @for (int i = 0; i < Model.PlayerList.Count; i++)
    {
        <tr>
            <td>
                @Html.DisplayFor(x => x.PlayerList[i].PlayerName)
                @Html.HiddenFor(x => x.PlayerList[i].PlayerName)
            </td>
            @for (int j = 0; j < Model.PlayerList[i].AttributesList.Count; j++)
            {
                <td>
                    @Html.DisplayFor(x => x.PlayerList[i].AttributesList[j].AttributeValue)
                    @Html.HiddenFor(x => x.PlayerList[i].AttributesList[j].AttributeValue)
                </td>
            }
        </tr>
    }

</table>


<input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}

Now my problem is, who to award the Answer, since I can say that most of the Answers were really helpful to arrive to my solution

JMon
  • 3,387
  • 16
  • 63
  • 102
  • 1
    Try a `for` loop instead of a `foreach` and use `@Html.TextBoxFor(x => x.AttribsPlayerLine[i].attribValue)`. If this doesn't work, learn how `EditorFor` is used and try `@Html.EditorFor(x => x.AttribsPlayerLine[i])` which will surely work. – mostruash Feb 24 '15 at 19:57
  • Hi mostruash, tried what you said but it did not work – JMon Feb 24 '15 at 20:24
  • Your `foreach` loop is creating multiple inputs with duplicate `id` (invalid html) and `name` attributes (cant be bound to a collection). You need `for` loops or custom `EditorTemplates` for typeof `AttribLine` as @mostruash as stated. –  Feb 24 '15 at 22:56
  • @Johann, You should not have accepted an incorrect answer (others viewing this may waste endless time believing that it works - it does not!). Create you own answer and accept it (and remove your last edit) –  Feb 25 '15 at 23:36

3 Answers3

3

Here is a working example of what you're trying to do. This is about as a close as I can get you.

Let's start with the simplified models:

public class PlayerRow
{
    public List<AttribLine> AttribsPlayerLine { get; set; }
}

public class AttribLine
{
    public string attribName { get; set; }
    public string attribValue { get; set; }
}

Note that it is IMPORTANT to include the { get; set; } on each model property so the model binder knows it's on the block for binding.

Next is a simplified view looking only at the form() section:

@using (Html.BeginForm("Calculate", "PlayerRow", FormMethod.Post))
{
    <table>
        @*/*Build out header*/*@
        <tr>
            @foreach (AttribLine a in Model.AttribsPlayerLine)
            {
                <th>@Html.Label("title", a.attribName)</th>
            }
        </tr>
        @*/* Add row of our player attributes */*@
        <tr>
            @foreach (AttribLine a in Model.AttribsPlayerLine)
            {
                using (Html.BeginCollectionItem("AttribsPlayerLine"))
                {
                    <td>
                        @Html.TextBox("attribValue", a.attribValue)
                        @Html.Hidden("attribName", a.attribName)
                        @*
                           /* Add any more [AttribLine] properties as hidden here */
                        *@
                    </td>
                }
            }
        </tr>
    </table>
    <input class="btn-danger" type="submit" name="Next >>" value="Next >>" />
}

Note that it is IMPORTANT here to make sure that even though a property is not editable by the user, it needs to be included as a hidden element inside our CollectionItem so the model binder can SET it on the [POST]

Hope this helps.

pnm
  • 140
  • 7
  • Hi pnm, it is giving me an error, "Cannot resolve BeginCollectionItem – JMon Feb 24 '15 at 20:28
  • ok added the Nuget, and did not give any problems. However the model is still null! – JMon Feb 24 '15 at 20:37
  • looking at http://stackoverflow.com/questions/23436360/system-web-mvc-htmlhelpermodelname-does-not-contain-a-definition-for sounds like restarting visual studio should fix your issue (?) – pnm Feb 24 '15 at 20:40
  • can you add the model def to your question please? – pnm Feb 24 '15 at 20:42
  • restarted VS but nothing new. Added my model def – JMon Feb 24 '15 at 20:53
  • for AttribsPlayerLine, are the only two properties {attribName,attribValue}? if so try dropping that first tr tag and loop to see if that's confusing the model binder – pnm Feb 24 '15 at 20:58
  • AttribPlayerLine is a class and has the following public List AttribsPlayerList = new List(); – JMon Feb 24 '15 at 21:00
  • and Attrib has the following :- public string attribId { get; set; } public string attribName { get; set; } public string attribValue { get; set; } public int attribValuePosMult { get; set; } – JMon Feb 24 '15 at 21:00
  • Should I replace the Edit with the top one? Even in your edit this x => attrib.attribName is not valid since I have attrib.AttribPlayerList, so I need an indexer to get the attribName – JMon Feb 24 '15 at 21:18
  • Thanks pnm, actually I did not see your solution before coming to my Answer, but I have awarded it to you since you helped me a lot to arrive to my conclusion. Thanks! – JMon Feb 25 '15 at 18:05
0

You are correctly passing the model to the view, but once it gets to the view, the model data will essentially be 'cleared' unless you either edit it or pass it on the next controller, in this situation "Calculate".

I'm not sure exactly what parts of the viewmodel you want passed to the controller, but you can always use this:

  @Html.HiddenFor(model => model.DataYouWantPassedToController)

Now, you can always use these items also to pass data if you want to edit/change:

  @Html.EditorFor(model => model.DataYouWantToEditAndPass)... and so on.

If you simple loop through data without changing or passing it, like you do in the @foreach, the data will be lost during your 'Post' method.

Eli
  • 533
  • 1
  • 5
  • 24
  • No luck Eli. I tried to add the following @Html.HiddenFor(model => Model.AttribsPlayerLine) and when I put a breakpoint there is data, however when I press the submit button, the model is still null. Also EditorFor did not work, still not getting any data – JMon Feb 24 '15 at 20:19
  • Hmmm that is very weird. Should definitely be giving you a partially filled data viewmodel. – Eli Feb 24 '15 at 21:22
0

Try use a @Html.EditorForModel() like this:

@model Scraper.Facade.PlayerRow

@using (Html.BeginForm("Calculate", "Home", FormMethod.Post))
{
    @Html.EditorForModel()

    <input class="btn-danger"  type="submit" name="Next >>" value="Next >>" />
}

Create a file named PlayerRow.cshtml as PartialView on the folder Views/Shared/EditorTemplates and add the following code:

    @model Scraper.Facade.PlayerRow
    <table class="table table-striped table-bordered table-condensed table-responsive table-hover">
        @foreach (var player in Model.AttribsPlayerLine)
        {
            <thead>
                <tr class="success">
                    @foreach (var attrib in player.AttribsPlayerList)
                    {
                        //@Html.DisplayNameFor(x => Model.tytul)
                        <th data-field="@attrib.attribName">@Html.DisplayFor(x => attrib.attribName) </th>
                    }
                </tr>
                <tr class="active">
                    @foreach (var attrib in player.AttribsPlayerList)
                    {
                        <td data-field="@attrib.attribValue">@Html.TextBoxFor(x => attrib.attribValue)</td>
                    }
                </tr>
            </thead>

        }
    </table>

I think that this will help you.

Richard Dias
  • 220
  • 3
  • 10