11

I have a view with a list of items from a model. I need to add a checkbox to each row, have the user select multiple check boxes, and pass some identifier of what row was selected to the controller. I know how to pass a single value through an action link, but I'm not sure how to pass multiple values through an action link or how to "collect" which rows were selected. I'll show some of my code attempts below. Can someone help me sort out why I can't get the values of all the checkboxes passed to the controller?

Here's my page

Checkbox     App ID     Date     Name
   []          1        5/10     Bob
   []          2        5/10     Ted
   []          3        5/11     Alice

What I need the user to do is select rows 1 & 3 (for example) and have those App ID's passed to the controller.

I started listing various attempts, but decided just to show my current attempt and see if anyone could point out what I'm doing wrong. The main difference I see between examples online and mine is that mine uses a PagedList and creates the rows of the table in a foreach loop.

The parameter ints is blank when it hits the controller. How do I get the values from the checkboxes into it? I used this site for the basic idea of naming all the checkboxes the same and passing a ICollection through the action link: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

View:

@model PagedList.IPagedList<CarmelFinancialWeb.Models.ModelMerchantSummary>
<div class="block" style="width: 100%; float: left">
<p class="block-heading"> 
Merchant Application Report
</p>
<div class="table-holder">
    <table class="table" style="margin-bottom: 0px">
            <tbody>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>
                            <input type="checkbox" name="ints" value=item.ApplicationID />
                        </td>
                    <td>
                        @Html.ActionLink(item.ApplicationID.ToString(), "ViewApplication", new { ID = item.ApplicationID, edit = 1 }, new AjaxOptions { HttpMethod = "GET" })
                    </td>
                    <td>
                        @Convert.ToDateTime(item.ApplicationDate).ToString("M/d/yy")
                    </td>
                    <td>
                        @item.ApplicantName
                    </td>
        </tbody>
    </table>
</div>
</div>
@Html.ActionLink("Print Application", "PrintApplication", "CreateContract", new { @class = "btn btn-primary" })

Controller:

    [AuthorizeAdmin]
    public ActionResult PrintApplication(ICollection<int> ints, string ID)
    {
        Contracts_Create contract = new Contracts_Create();
        ModelApplication currentApplication = new ModelApplication();
        currentApplication.contract = new ModelContract();
        return File(contract.CreatePDF_PrintedApplication_English(currentApplication.contract.location, currentApplication.contract), "application/pdf");
    }

Edit: This got tagged as a duplicate of another question. The question there was about whether non-sequential input names could be used. My problem is that I'm using inputs that are not non-sequential, but it is still not working. I understand the concept, I just can't figure out why my specific code is not working. I've put a lot of time into this and can't find the answer to my specific code. Thanks!

boilers222
  • 1,901
  • 7
  • 33
  • 71

3 Answers3

10

Try passing a ViewModel into your page, and using the model binder to post the same view model back into your controller

Models:

public class MerchantModel
{
    public int AppId { get; set; }
    public string Name { get; set; }
    public bool IsSelected { get; set; }
}

public class MerchantViewModel
{
    public List<MerchantModel> Merchants { get; set; }
}

Controller:

public class DefaultController : Controller
{
    // GET: Default
    public ActionResult Index()
    {
        var merchant1 = new MerchantModel
        {
            AppId = 1,
            Name = "Bob"
        };
        var merchant2 = new MerchantModel
        {
            AppId = 2,
            Name = "Ted"
        };
        var merchant3 = new MerchantModel
        {
            AppId = 3,
            Name = "Alice"
        };

        List<MerchantModel> list = new List<MerchantModel>();
        list.Add(merchant1);
        list.Add(merchant2);
        list.Add(merchant3);

        var model = new MerchantViewModel
        {
            Merchants = list
        };

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(MerchantViewModel model)
    {
        return View(model);
    }
}

View:

 @model TestCheckBoxes.Models.MerchantViewModel

    @{
        Layout = null;
    }

    <!DOCTYPE html>

    <html>
    <head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div> 
        <form action="/default/index" method="post">
            <table>
                @for (int i = 0; i < Model.Merchants.Count; i++)
                {
                    <tr>
                        <td>
                            @Html.CheckBoxFor(x => x.Merchants[i].IsSelected)
                        </td>
                        <td>
                            @Html.HiddenFor(x => x.Merchants[i].AppId)
                            @Model.Merchants[i].AppId
                        </td>
                        <td>
                            @Html.HiddenFor(x => x.Merchants[i].Name)
                            @Model.Merchants[i].Name
                        </td>
                    </tr>
                }
            </table>

            <button type="submit">Submit</button>
        </form>



    </div>
</body>
</html>

Back in your [HttpPost] method in your controller, you will have a list of MerchantModel's with the bool value either true or false. This way you can check if it's true and just grab the AppId from there.

Nick Young
  • 885
  • 1
  • 10
  • 21
9

Don't use foreach in mvc, always iterate using for and an indexing variable.

You'll also need a bool to track the selection status.

This example code works for me:

public class AModel
{
    public List<AnotherModel> Items { get; set; }
}

public class AnotherModel
{
    public int ApplicationId { get;set; }
    public DateTime ApplicationDate { get; set; }
    public string ApplicantName { get; set; }
    public bool Selected { get; set; }
}

Page.cshtml

@using (Html.BeginForm("PostIndex", "Home", FormMethod.Post))
{
    <table class="table" style="margin-bottom: 0px">
        <tbody>
        @for (var i = 0; i < Model.Items.Count(); i++)
        {
            <tr>
                <td>
                    <label>@Html.CheckBoxFor(model => model.Items[i].Selected) @Html.DisplayFor(model => model.Items[i].ApplicantName)</label>
                </td>
                <td>
                    @Html.ActionLink(Model.Items[i].ApplicationId.ToString(), "ViewApplication", new {ID = Model.Items[i].ApplicationId, edit = 1}, new AjaxOptions {HttpMethod = "GET"})
                </td>
                <td>
                    @Html.DisplayFor(model => model.Items[i].ApplicationDate)
                </td>
                <td>
                    @Html.DisplayFor(model => model.Items[i].ApplicationId)
                </td>
            </tr>
        }
        </tbody>
    </table>
    <input type="submit"/>
}
C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • Thank you @CBauer. This seems to substitute the class AModel for my PagedList, which I need. I'll try your for statement using the pagedlist class for the model. I didn't created this view; just trying to modify it. Whoever wrote it before used the pagedlist (as well as the foreach, which is why that was there); I'm trying to modify the view without breaking anything else. I'll write back and let you know how it goes. – boilers222 May 12 '15 at 20:34
  • @boilers222 Yes, I could not find an example model in your OP so I just substituted in an example. All you really need to do is add the 'selected' bool to your pagedlist class and replace the rendering code with the mvc element. – C Bauer May 12 '15 at 20:35
  • 1
    Or rather than using for loops and naming the objects explicitly try using templates: http://www.codeguru.com/csharp/.net/net_asp/mvc/using-display-templates-and-editor-templates-in-asp.net-mvc.htm – nik0lai May 13 '15 at 15:11
  • 1
    I am not a big fan of the templates, but have used them in complex scenarios. However, I believe you still have to generate the editor using the `for` syntax in the end anyway. – C Bauer May 13 '15 at 15:53
  • 1
    Why must you use a for and not a foreach? – Michael LeVan Mar 02 '18 at 23:32
  • @MichaelLeVan I should have been more specific; at the time of writing this answer MVC used the counting integer to define the ID of the element generated which in turn could be deserialized when submitting a form in order to maintain MVC post behavior (IE: retrieving the values in the UI). – C Bauer Mar 05 '18 at 15:50
0

@CBauer and @NickYoung essentially had the answer. The keys were (1) making sure my checkboxes had the ID written with a sequential number in brackets with the same suffix, (2) using a submit button instead of an action link, and (3) correctly writing the controller to accept the IDs from the checkboxes. Thought I'd post some of my code to give you an idea of what I got to work:

View (partial):

@model PagedList.IPagedList<CarmelFinancialWeb.Models.ModelMerchantSummary>

...

@{var i = 0;}
@foreach (var item in Model)
{
  var tmp = "[" + i + "].ints";
  <tr>
      <td>
          <input name="ints" type="checkbox" id="@tmp" class="chkclass" value="@item.ApplicationID" />
      </td>

...

<input id="Submit" type="submit" class="btn btn-primary" value="Print Application(s)" />

...

Controller:

[AuthorizeAdmin]
[HttpPost]
public ActionResult MerchantApplicationSummary(string[] ints, FormCollection form, int? page)
{

...

Thanks @CBauer and @NickYoung for your help!

boilers222
  • 1,901
  • 7
  • 33
  • 71
  • You do not need `@{var i = 0;}` or `var tmp = "[" + i + "].ints";` or `id="@tmp"` in the checkbox (`id` attributes have nothing to do with whats posted back - only a controls `name` and `value` values are posted as name/value pairs) –  May 14 '15 at 21:28