ASP.Net

ASP.NET MVC Display an HTML Table with Checkboxes to Select Row Items


Image by Elif Ayiter | Some Rights Reserved

Often we need to create a table for our ASP.NET MVC web application with a checkbox next to each row, in order to allow the user to select one or more items from our list for additional processing. While the concept is not terribly challenging, it is not intuitive or obvious if you are newer to MVC.

Here is one way to do it.

The Example Project

For our example we will simply display a list of people as rows in a table, with checkboxes to the leftmost end of each row. The user may select one or more people from the list, click a submit button, and implement some sort of processing for the individual items selected.

Our example assumes that the business model requires server-side processing of the selected items, and therefore a direct server request instead of AJAX-style processing.

You can find the working source code at my Github repo.

If you clone or download the source from Github, you will need to Enable Nuget Package Restore in order that VS can automatically download and update Nuget packages when the project builds.

Editor View Models and Editor Templates

ASP.NET MVC affords us the ability to create custom editor templates for our domain classes and models. Similar to the convention-based relationship between Controllers and Views, there is a similar convention-based relationship between Editor View Models and Editor Templates.

The MVC framework already defines its own library of Editor Templates, made available behind the scenes through methods such as EditorFor(), DisplayFor(), LabelFor(), etc. For example, when you place code like this in your view:

<div class="col-md-10">
    @Html.EditorFor(model => model.LastName)
    @Html.ValidationMessageFor(model => model.LastName)
</div>

MVC understands it is to retrieve the proper editor template from System.Web.Mvc.dll for a string to contain the model.LastName property on our page. The default display device will be a textbox.

Utilizing this convention, we can create a custom Editor View Template for Models or View Models within our domain which can then be used in our code. Employing an editable checkbox for each row in a table, the status of which can be sent to and from the server as a member in a list, is one case where we need to employ an Editor View Model and an Editor View Template to make things work.

How we do this becomes more clear as we step through the example code.

The Core Domain Model

For our example, we will start with a simple domain model, represented by the Person class:

The Person Model:
public class Person
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public string firstName { get; set; }
}

Add Test Data

In order to keep our example simple, we will create a test data repository for our code to work with, so we can pretend we are working with a database. We will add the following to our Models folder:

Test Data for the Example Code:
public class TestData
{
    public TestData()
    {
        this.People = new List<Person>();
        this.People.Add(new Person() { 
            Id = 1, LastName = "Lennon", firstName = "John" });
        this.People.Add(new Person() { 
            Id = 2, LastName = "McCartney", firstName = "Paul" });
        this.People.Add(new Person() { 
            Id = 3, LastName = "Harrison", firstName = "George" });
        this.People.Add(new Person() { 
            Id = 4, LastName = "Starr", firstName = "Ringo" });
    }
    public List<Person> People { get; set; }
}

The above will allow us to act as though we have a backend data store.

Adding the Editor View Model

Now let’s create an Editor View Model which will allow us to present each Person instance as a row in a table, and include a Selected status that we can present as a checkbox for each table row.

There are two important things to note about Editor View Models and their relationship to the Editor Template for which they are designed:

The Editor View Model class name must follow the convention ClassNameEditorViewModel

The Corresponding Editor Template must be located in a folder named EditorTemplates, within either the Views => MyClass folder, or the Views => Shared folder. We’ll see more on this in a moment. For now, be aware that the naming of the View Model and the associated View is critical, as is the location of the .cshtml View file itself.

Add the following class to the Models folder. In keeping with the above, we have named our View Model SelectPersonEditorViewModel:

The Select Person Editor View Model
public class SelectPersonEditorViewModel
{
    public bool Selected { get; set; }
    public int Id { get; set; }
    public string Name { get; set; }
}

Looking over the code above, we see that we have included only the information required by our view, and in fact have provided only a single string property for Name. We could have included separate FirstName and LastName properties, but I decided for our list we would just display the concatenated First and Last names for each Person.

This Editor View Model will be used by MVC to render each row in our table. Now, we need to create a wrapper for the collection of rows data.

Adding the Wrapper View Model

We need a wrapper to contain a list of the EditorViewModels we wish to pass to and from our View. Also, we will include a method which gives us handy access to a list of the Ids of the items where the Selected property is true:

The People Selection View Model:
public class PeopleSelectionViewModel
{
    public List<SelectPersonEditorViewModel> People { get; set; }
    public PeopleSelectionViewModel()
    {
        this.People = new List<SelectPersonEditorViewModel>();
    }
    public IEnumerable<int> getSelectedIds()
    {
        // Return an Enumerable containing the Id's of the selected people:
        return (from p in this.People where p.Selected select p.Id).ToList();
    }
}

As convoluted as this may sound, what we do is use the PeopleSelectionViewModel to wrap an IEnumerable of SelectPersonEditorViewModel items, and pass this to out primary view. Now let’s take a look at the views we will need to make this work.

Create an Editor View Template

Here is where we start to bring this all together. As mentioned previously, we need a custom Editor View Template in order to pass our individually selectable row data to and from our main view. We can locate individual Editor Templates in a folder named EditorTemplates nested in either the Folder specific to the main view in question (in this case, Views => People => EditorTemplates) or within the Views => Shared folder (which then makes the Editor Template available to the entire application).

In the current case, we will add an EditorTemplates folder to the Views => People folder, since this is a rather special purpose template, specific to the People controller.

Add an Editor Templates Folder to the Appropriate Directory:

add-the-editor-templates-folder

Now, let’s add our Editor Template View. Right-Click on the new folder, add a new View, and name it very specifically SelectPersonEditorViewModel, so that the name matches the name of the actual View Model we created earlier. Then add the following code:

The Select Person Editor View Model View:
@model AspNetCheckedListExample.Models.SelectPersonEditorViewModel
<tr>
    <td style="text-align:center">
        @Html.CheckBoxFor(model => model.Selected)
    </td>
    <td>
        @Html.DisplayFor(model => model.Name)
    </td>
    <td>
        @Html.HiddenFor(model => model.Id)
    </td>
    <td>
        @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
        @Html.ActionLink("Details", "Details", new { id = Model.Id }) |
        @Html.ActionLink("Delete", "Delete", new { id = Model.Id })
    </td>
</tr>

The Editor Template View above provides display items for the relevant model data, as well as en editor for the checkbox, and a hidden element for the mode.Id property. We will use this Id property, and the value of the Selected property to determine which people the user has selected for additional processing when the list of View Models is returned from the view in an Http POST request.

Adding the Index View

Also in the Views => People folder, add a new Empty View named Index. This is the main View we will use to contain our table of People. Once the view has been added, add the following code:

The People Index View:
@model AspNetCheckedListExample.Models.PeopleSelectionViewModel
  
@{
    ViewBag.Title = "People";
}
  
<h2>People</h2>
  
@using (Html.BeginForm("SubmitSelected", "People", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
    <table class="table">
        <tr>
            <th>
                Select
            </th>
            <th>
                Name
            </th>
            <th></th>
        </tr>
        @Html.EditorFor(model => model.People)
    </table>
    <hr />
    <br />
    <input type="submit" name="operation" id="submit" value="Submit Selected" />
}

As you can see in the above, we have created a basic HTML Form, and within that have defined a table. The table headers are clearly laid out within this view, but we can see that where we would ordinarily populate our rows using a foreach() construct, here we simply specify @Html.EditorFor() and specify the the IEnumerable<SelectPersonEditorViewModel> represented by the People property of our Model.

Tying it all together – the People Controller

Of course, none of this means anything without the Controller to govern the interaction of our Views and Models. For our ultra-simple project, we are going to define an ultra-simple controller, which will include only two Action methods: Index, and SubmitSelected.

Add a controller to the controllers folder, appropriately named PeopleController, and add the following code:

The People Controller Class:
public class PeopleController : Controller
{
    TestData Db = new TestData();
  
    public ActionResult Index()
    {
        var model = new PeopleSelectionViewModel();
        foreach(var person in Db.People)
        {
            var editorViewModel = new SelectPersonEditorViewModel()
            {
                Id = person.Id,
                Name = string.Format("{0} {1}", person.firstName, person.LastName),
                Selected = true
            };
            model.People.Add(editorViewModel);
        }
        return View(model);
    }
  
  
    [HttpPost]
    public ActionResult SubmitSelected(PeopleSelectionViewModel model)
    {
        // get the ids of the items selected:
        var selectedIds = model.getSelectedIds();
        // Use the ids to retrieve the records for the selected people
        // from the database:
        var selectedPeople = from x in Db.People
                             where selectedIds.Contains(x.Id)
                             select x;
        // Process according to your requirements:
        foreach(var person in selectedPeople)
        {
            System.Diagnostics.Debug.WriteLine(
                string.Format("{0} {1}", person.firstName, person.LastName));
        }
        // Redirect somewhere meaningful (probably to somewhere showing 
        // the results of your processing):
        return RedirectToAction("Index");
    }
}

In our Index method, we pull a List of Person instances from our database, and for each instance of Person we create an instance of SelectPersonEditorViewModel. Note that for our example application, I have decided that the default value of the Selected property will be set to true, as it is most likely the user will process the entire list.

We then load each instance of SelectPersonEditorViewModel into our PeopleSelectionViewModel, and then pass this to the Index View itself.

Processing the Selection Results in the Submit Selected Method

When the user submits the form represented in our index view, the model is returned in the POST request body. Note the we needed to decorate the SubmitSelected method with the [HttpPost] attribute. We then use the getSelectedItems() method we added to our PeopleSelectionViewModel to return a list of the selected Ids, and use the Ids to query the appropriate records from our database.

In the example, I obviously am just writing some output to the console to demonstrate that we have, indeed, returned only the selected items from our View. Here is where you would add your own processing, according to the needs of your business model.

Adding a Select All Checkbox to Toggle Selected Status

Commonly in this scenario, we would want to afford our user the ability to “Select All” the items in the list, or “Select None” of the items. The JQuery code for this has been added to the project source, and you can read more about how to do this in Adding a Select All Checkbox to a Checklist Table Using JQuery.

Modifications to Make the Demo Project Run (not required)

In order make the example project work, I went ahead and made the following modifications to the rest of the standard MVC project. The following is not really related to creating a table with checkboxes, but might help if you are following along and building this out from scratch instead of cloning the project from Github.

Make the Index View of the People the Default Home Page

In the <body> section of the shared _Layout.cshtml file (Views => Shared => _Layout.cshtml) I removed the tabs for Home, Contact, and About, and added a tab for People, pointing at the Index method of the People Controller. The revised <body> section now looks like this:

Modified Body Section of _Layout.cshtml:
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "People", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("People", "Index", "People")</li>
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
  
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>

Update the Route Configuration

Also, I modified the Route.config so that the default route points to the Index method of the People Controller, instead of Home. The modified Route mapping looks like this:

Modified Route Mapping:
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "People", action = "Index", id = UrlParameter.Optional }
);

Remove Unneeded Controllers and Views

For the demo project, I had no need for the extra controllers and views included with the default MVC project, so I removed them. We can delete the Home Controller, as well as the Home folder in the Views directory.

Additional Resources and Items of Interest

C#
.Net DataGridview: Change Formatting of the Active Column Header to Track User Location
CodeProject
Use Postgres JSON Type and Aggregate Functions to Map Relational Data to JSON
C#
C#: Query Excel and .CSV Files Using LinqToExcel
  • asif

    asifasif

    Author Reply

    Thanks a lot, you saved my day :)