MVC - Displaying CheckBoxLists for Generic Lists

When developing an application, you may encounter a scenario where you have, for example, a customer record and that customer may be a member of several (but not all) customer lists in your application. 


All fairly straightforward. In an ASP.NET WebForms application for a customer management page where you want to select or un-select the lists a customer is in, you would typically bind the available customer lists to a CheckBoxList and in the DataBound event for this CheckBoxList control you would then mark checkboxes as checked for the lists your customer is in.


Depending on how you structure your application, this could involve two calls to your data store - one to get the available customer lists and one to get the customer details.


With MVC, the approach to this scenario takes a little adjustment!


The MVC Approach



There are several ways to handle displaying checkbox lists, however when I am developing MVC applications  I prefer to work solely with the strongly typed ViewModel I have created for my view. When doing this however, it is important to get the data in your ViewModel in a certain way.


Taking the example scenario above, in my application I want a customer edit view where I can edit the customer details and select or un-select the lists the customer is a member of. My customer entity looks something like:


    public class Customer
    {


        public Guid Id { get; set; }


        [Required(ErrorMessage = "A first name is required"), Display( Name = "First name")]
        public string Firstname { get; set; }


        [Required(ErrorMessage = "A surname is required"), Display(Name = "Surname")]
        public string Surname { get; set; }


        [Required(ErrorMessage = "An email address is required"), Display(Name = "Email address")]
        public string EmailAddress { get; set; }


        [Display(Name = "Added on")]
        public DateTime AddedOn { get; set; }


        [Display(Name = "Modified on")]
        public DateTime ModifiedOn { get; set; }


        [Display(Name = "Customer lists")]
        public List<CustomerList> Lists { get; set; }
    }

As you can see, I am including the lists a customer is a member of when I return an instance of a customer. My CustomerList entity is:



    public class CustomerList
    {


        public Guid Id { get; set; }


        [Required(ErrorMessage = "A name is required"), Display(Name = "Name")]
        public string Name { get; set; }


        [Display(Name = "Created on")]
        public DateTime CreatedOn { get; set; }


        [Display(Name = "Modified on")]
        public DateTime ModifiedOn { get; set; }


        public bool Checked { get; set; }
    }



In my customer repository, I have a GetCustomerById method to return a customer entity populated with the details for the customer:


        public Customer GetById(Guid id, string connectionString)
        {
            Customer customer = null;
            SQLQuery query = new SQLQuery("Customers.GetById", CommandType.StoredProcedure, connectionString);
            query.AddParameter("@Id", SqlDbType.UniqueIdentifier, id, ParameterDirection.Input);
            DataTableReader reader = query.ExecuteDataSet().Tables[0].CreateDataReader();


            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    customer = new Customer
                                   {
                                       AddedOn = (DateTime) reader["AddedOn"],
                                       EmailAddress = (string) reader["EmailAddress"],
                                       Firstname = (string) reader["Firstname"],
                                       Id = new Guid(reader["Id"].ToString()),
                                       ModifiedOn = (DateTime) reader["ModifiedOn"],
                                       Surname = (string) reader["Surname"], 
                                       Lists = GetListsByCustomerId(id, connectionString)
                                   };
                }
            }
            return customer;
        }

The Lists property of my Customer entity is populated by another method called GetListsByCustomerId, which takes the Id of the customer and returns a list of CustomerLists to populate my Customer Lists property:


        public List<CustomerList> GetListsByCustomerId(Guid id, string connectionString)
        {
            List<CustomerList> lists = new List<Domain.Entities.CustomerList>();
            SQLQuery query = new SQLQuery("Customers.GetListsByCustomerId", CommandType.StoredProcedure, connectionString);
            query.AddParameter("@Id", SqlDbType.UniqueIdentifier, id, ParameterDirection.Input);
            DataTableReader reader = query.ExecuteDataSet().Tables[0].CreateDataReader();


            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    CustomerList customerList = new Domain.Entities.CustomerList
                    {
                        CreatedOn = (DateTime)reader["CreatedOn"],
                        Name = (string)reader["Name"],
                        Id = new Guid(reader["Id"].ToString()),
                        ModifiedOn = (DateTime)reader["ModifiedOn"],
                        Checked = Convert.ToBoolean(reader["CustomerInList"])
                    };
                    lists.Add(customerList);
                }
            }
            return lists;
        }


Note: The SQLQuery object in the above sample is a custom SQLQuery component - if you are wanting to copy this code, you will need to change this appropriately to your own code to return a DataTableReader.

You'll have noticed in my CustomerList object, I have a property called Checked. This is because in my stored procedure to return the lists for a customer, I am returning all lists and not just the lists for that customer. In my stored procedure I am returning a value to indicate whether the customer is in the list or not, and setting the Checked property appropriately from my query value CustomerInList. This allows for the available lists and the lists a customer is in to be returned in one recordset, saving a database call. My stored procedure is:


DECLARE @temp TABLE ( Id uniqueidentifier, Name nvarchar(50), CreatedOn datetime, ModifiedOn datetime, CustomerInList bit)
INSERT INTO @temp (Id, Name, CreatedOn, ModifiedOn, CustomerInList)
SELECT 
Lists.Id, 
Lists.Name, 
Lists.CreatedOn, 
Lists.ModifiedOn,
(SELECT COUNT(Id) FROM Customers.CustomerLists WHERE ListId = Lists.Id AND CustomerId = @Id)
FROM Customers.Lists

SELECT * FROM @temp




The MVC View

In my Edit view, I now want to display my list of CustomerLists as checkboxes, with lists my customer is in marked as checked.


The best way to do this is with a PartialView and include this in your edit page:

                @model Services.ViewModels.EditCustomer 



                @foreach (var list in Model.Details.Lists)
                {
                    @Html.EditorFor(x => list, "CustomerLists")
                }
                <div>@Html.ValidationMessageFor(m => m.Details.EmailAddress)</div>

Note: The EditCustomer model is myViewModel which has a property of details. My populated customer entity is returned in this property

My CustomerList partical view is:


@model Domain.Entities.CustomerList


@Html.HiddenFor(x => x.Id)
@Html.HiddenFor(x => x.Name)
<div>
    @Html.CheckBoxFor(x => x.Checked)
    @Html.LabelFor(x => x.Checked, Model.Name)
</div>




When the view is rendered, I will now have a list of checkboxes, with one for each of my CustomerLists, and checked if my customer is a member of that list.


Hope this helps - you can download a sample MVC3 application from here





Comments

Popular posts from this blog

Connecting to SQL Azure with Dynamic IP Addresses

HTML to PDF Conversion in MVC 4

WebAPI and Subscriber Authentication by Custom HTTP Headers