Home ASP Creating your own CheckBoxList control

Creating your own CheckBoxList control

by admin

Foreword

This article will look at creating your own control and using it as part of a new project in ASP .NET MVC 3.0.Everything written below is the author’s point of view and may not coincide with common or generally accepted methods of creating controls, so criticism and comments are welcome.

Introduction

Probably many users who have previously worked with WinForms or ASP .NET WebForms in their projects have noticed that the Html helpers in the ASP .NET MVC project do not provide the ability to create a control like CheckBoxList, which could be useful in complex data filter forms or when multiple choices of unstructured data, whether in a profile about a user or when adding a new topic to hubrahabr. Of course, no one forbids using a single CheckBox or CheckBoxFor, but whether working with such a group of checkboxes will be convenient and the code will be easily extensible, understandable to the maintainer and duplicate-proof – these are not the last questions for a programmer who plans to use his achievements in future projects. And if you take into account that we can add some useful options to our control to display it visually, the need to create it becomes more and more evident.
Before we dive fully into the subject area, I would like to note that the idea of creating the control in question was inspired after viewing the site http://mvc.devexpress.com which has a lot of nice and functional controls, some of them look really amazing. If this article is approved and met with positive feedback, further review and implementation of the controls presented on the site is possible as part of a series of articles for the hubra community.

Analysis

First, we need to understand what we are going to do. The control will be represented by a group of CheckBoxes, which allows us to output the data collections passed to it, uses the Model Binding concept when sending the form to the server, and has the following display settings :

  • Layoutt – wireframe (in div or table);
  • Direction – direction of output (vertical/horizontal);
  • RepeatColumns – number of columns;
  • htmlAttributes – attributes that will be applied to the CheckBoxes container.

Project creation

Create an empty ASP .NET MVC 3 project and add a controller to it DemoController with the method and representation of Index :
Creating your own CheckBoxList control
Creating your own CheckBoxList control
Creating your own CheckBoxList control
After the minimal preparations are done, we go directly to the control.

Adding a CheckBoxList item

Adding a folder to the root of the project Core with the file Controls.cs and static class Controls which will contain the implementation of our CheckBoxList.
Creating your own CheckBoxList control
At this point, in static class Controls we can already create our own Html helper implementations, so let’s add the necessary amount of code for this :

public static MvcHtmlString CheckBoxList(this HtmlHelper helper){return new MvcHtmlString("Hello, i'm your CheckBoxList!");}

And let’s try to call it in our view Index , controller DemoController
Note :I intentionally don’t write about include assemblies and scope files in the article text, as I hope you can do it without me mentioning them. The only thing worth mentioning is adding the line <add namespace="MVC3Controls_H.Core"/> to the file /Views/web.config, where MVC3Controls_H.Core, is the location of your static class with CheckBoxList.
To call our element, we add in Index.cshtml (if you specified the Razor Engine when adding the new view) the following line :

@Html.CheckBoxList()

and specify in the file Global.asax , which is located at the root of the project, that by default it is required to call the Demo , method Index :

routes.MapRoute("Default", // Route name"{controller}/{action}/{id}", // URL with parametersnew { controller = "Demo", action = "Index", id = UrlParameter.Optional } // Parameter defaults);

If all steps have been done correctly, you will see the following page :
Creating your own CheckBoxList control
Great. Now let’s move on to implementing the structure of our CheckBoxList.

Implementations of CheckBoxList structure

First of all, let’s add its display settings to the file Controls.cs In our case, it will be 3 objects like enum :

public enum Layoutt{Table = 0, Flow = 1}public enum Direction{Horizontal = 0, Vertical = 1}public enum RepeatColumns{OneColumn = 1, TwoColumns = 2, ThreeColumns = 3, FourColumns = 4, FiveColumns = 5}

And class CheckBoxList_Settings where we will pass them in the call of our control:

public class CheckBoxList_Settings{public string cbl_Name= "SelectedCheckBoxListItems";public Layoutt cbl_Layout = Layoutt.Table;public Direction cbl_Direction = Direction.Horizontal;public RepeatColumns cbl_RepeatColumns = RepeatColumns.FiveColumns;}

Our default settings are a table frame, horizontal output direction, and a five-column display. The property cbl_Name will be explained later.
We modify our CheckBoxList constructor to accept a collection of type IDictionary<string, int> and an instance of the settings class :

public static MvcHtmlString CheckBoxList(this HtmlHelper helper, IDictionary<string, int> items, CheckBoxList_Settings settings){return new MvcHtmlString("Hello, i'm your CheckBoxList!");}

We have arrived at the fact that in the view we now not only need to call our element, but also to pass it 2 parameters, so we add in Index method of our controller data collection :

Dictionary<string, int> languages = new Dictionary<string, int>{{"ActionScript", 0}, {"Delphi", 1}, {"GO", 2}, {"Lua", 3}, {"Prolog", 4}, {"Basic", 5}, {"Eiffel", 6}, {"Haskell", 7}, {"Objective-C", 8}, {"Python", 9}, {"C", 10}, {"Erlang", 11}, {"Java", 12}, {"Pascal", 13}, {"Ruby", 14}, {"C++", 15}, {"F#", 16}, {"JavaScript", 17}, {"Perl", 18}, {"Scala", 19}, {"C#", 20}, {"Fortran", 21}, {"Lisp", 22}, {"PHP", 23}};

And let us provide our view of this data as a model :

return View(languages);

In our view code, we specify that the model for it is the Dictionary<string, int> data type and call our CheckBoxList:

@model Dictionary<string, int>@using MVC3Controls_H.Core@{ViewBag.Title = "Index";}<h2> Index</h2>@Html.CheckBoxList(Model, new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItems", cbl_Layout = Layoutt.Table, cbl_Direction = Direction.Horizontal, cbl_RepeatColumns = RepeatColumns.FiveColumns})

Run the project and see the same familiar record :
Creating your own CheckBoxList control

Analysis of current application structure

Before we go any further, we need to understand the current structure of the application and possibly change it. This is what I would like to focus on :

  • At the moment we are passing a model of Dictionary<string, int> type into our view, but in most cases the application uses other data passed from the controller as well, be it page meta tags (title, description) or entities (registration, order form) – hence we should not monopolize the model exclusively for our control. Moreover, we need to store and retrieve the user-selected values of our control somewhere.
  • Suppose, over time, in our project CheckBoxList will be called in more and more views, and at some point, we will need to add a new parameter or change the existing ones. In this case, we’ll have to change all the calls to our control (in all views), and this isn’t very convenient.

It follows from the above that we will need :

  • Add a model for the view (ViewModel) and pass the collection to be displayed in the CheckBoxList as part of that model, along with other data.
  • Move the call of our control from the view to the partial view, so that if we change the constructor or parameters, we need to change the call for all the elements used in our project in just one place.
  • Take the values passed to CheckBoxList into a separate data model for our control and pass it into our partial view to isolate our views from element call changes as much as possible.

Note : These and many other questions should be considered and asked yourself before you start writing the application code, however, since this article is designed for those who are trying to do something like this for the first time, I will try to lead you step by step through trial and error.

Adding a representation model

Since our view model serves not only to transmit data in it, but also to receive data in it, we need to add an array of selected items in addition to the collection to be sent. Let’s add to the folder Models folder ViewModels , then add in ViewModels file ViewModel_Index.cs as shown below :
Creating your own CheckBoxList control
and our representation model code :

public class ViewModel_Index{public IDictionary<string, int> Languages { get; set; }public int[] SelectedCheckBoxListItems { get; set; }}

Index our DemoController ‘a will change as follows :

public ActionResult Index(){Dictionary<string, int> languages = new Dictionary<string, int>{{"ActionScript", 0}, {"Delphi", 1}, {"GO", 2}, {"Lua", 3}, {"Prolog", 4}, {"Basic", 5}, {"Eiffel", 6}, {"Haskell", 7}, {"Objective-C", 8}, {"Python", 9}, {"C", 10}, {"Erlang", 11}, {"Java", 12}, {"Pascal", 13}, {"Ruby", 14}, {"C++", 15}, {"F#", 16}, {"JavaScript", 17}, {"Perl", 18}, {"Scala", 19}, {"C#", 20}, {"Fortran", 21}, {"Lisp", 22}, {"PHP", 23}};ViewModel_Index_ViewModel_Index= new ViewModel_Index{Languages = languages};return View(_ViewModel_Index);}

Since we have changed the model to be transferred in the controller, this must also be done in the associated view :

@using MVC3Controls_H.Models.ViewModels@using MVC3Controls_H.Core@model ViewModel_Index@{ViewBag.Title = "Index";}<h2> Index</h2>@Html.CheckBoxList(Model.Languages, new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItems", cbl_Layout= Layoutt.Table, cbl_Direction = Direction.Horizontal, cbl_RepeatColumns= RepeatColumns.FiveColumns})

So if you need to send something else to the page in addition to the data collection for our control, you can do so by simply changing the view model /Models/ViewModels/ViewModels_Index.cs (by adding new properties to it) and adding their initialization in the controller method.

Encapsulation of our CheckBoxListcall

In its first article , I already used a similar scheme to call the expanding method, but for somewhat different purposes.In the current project, in order to call our CheckBoxList in PartialView, we will add the /Views folder Partial , therein the folder Controls , and in the folder Controls new partial representation CheckBoxList.cshtml :
Creating your own CheckBoxList control
The structure of our application now looks like this :
Creating your own CheckBoxList control
Changing our Index representation by calling our Partial View instead of CheckBoxList:

@using MVC3Controls_H.Models.ViewModels@using MVC3Controls_H.Core@model ViewModel_Index@{ViewBag.Title = "Index";}<h2> Index</h2>@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml")

Adding a Data Model for CheckBoxList

Since we plan to move the call of our control to our Partial View, we also need to pass the data for it there as well – hence, we need to add a data model for it. In the folder Models create a new folder CheckBoxList , let’s add the class CheckBoxList_Model.cs :

public class CheckBoxList_Model{public IDictionary<string, int> items;public CheckBoxList_Settings settings;}

Change in our Index Partial View call, passing the added model as data :

@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml", new CheckBoxList_Model{items = Model.Languages, settings = new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItems", cbl_Layout = Layoutt.Table, cbl_Direction = Direction.Horizontal, cbl_RepeatColumns = RepeatColumns.FiveColumns}})

and define this model as the model for our Partial View in /Views/Partial/Controls/CheckBoxList.cshtml , calling and passing values to our control :

@model CheckBoxList_Model@Html.CheckBoxList(Model.items, Model.settings)

This concludes the structure of the application. Let’s take a look at its main points :
Creating your own CheckBoxList control
The good news is that we’re finally done with the structure of our application, and the bad news is that we’ve barely written anything about our CheckBoxList itself yet.

CheckBoxList implementation

Let’s start by describing the general algorithm for building our element :
1. Selection of the frame HTML tag (table, div) based on the parameter cbl_Layout
2 Define the number of iterations over the collection based on the parameter cbl_RepeatColumns and the number of records in the collection.
3. Forming a condition for selecting items from the collection based on the sequence number of the current iteration :
3.1 If the output is horizontal and columns = 4, then the first row should consist of the first 4 elements in the collection, the second row should consist of the next 4, and so on:

[0 1 2 3] - 0 iteration[4 5 6 7] - 1 iteration[8 9 10 11]

3.2 If the output is vertically, columns = 4 and 24 elements in total, then the first line must consist (!) of every sixth element in the collection, the second of iteration # + 6, etc:

[0 6 12 18] - 0 iteration[1 7 13 19] - 1 iteration[2 8 14 20][3 9 15 21][4 10 16 22][5 11 17 23]

3.3 Don’t forget about the remainder of division != 0 when calculating iterations (number of items / number of columns). In this case, we need an extra iteration to output the whole collection.

[0 1 2 3] - 0 iteration[4 5 6 7] - 1 iteration[8 9 10 11][12 13 - -]

4. Forming a string of suitable samples at each iteration, depending on the framing tag (div, table):
4.1 For div it will be only hyphenation at the end of the line.
4.2 For table it is :
4.2.1.

at the beginning of the line. label in the process of enumerating the elements.

at the end of the line.
5. Closes the framing tag and returns the generated string to the view.
Since the main point of the article is not the code and algorithms, I will give only part of the functions, the rest you can see by downloading the project at the end of the article, except to describe the formation of the CheckBox markup a little more detail:

HTML Markup Formation of CheckBox and Model Binding

Generally speaking, this is the simplest code to which our collection item and our control’s settings are transferred. Perhaps the most important thing in this code is that the name attribute is assigned the name of our entire CheckBoxList. This is done so that in the future, when we get data on the server side in our method Index controller DemoController we were able to get the selected values as a generated array, -I will show this later with an example.

public static string GenerateHtmlMarkup_CheckBox(KeyValuePair<string, int> item, CheckBoxList_Settings settings){TagBuilder tagBuilder = new TagBuilder("input");tagBuilder.MergeAttribute("type", "checkbox");tagBuilder.MergeAttribute("name", settings.cbl_Name);tagBuilder.MergeAttribute("value", item.Value.ToString());return tagBuilder.ToString(TagRenderMode.SelfClosing);}

Generating HTML markup for a Label

public static string GenerateHtmlMarkup_Label(KeyValuePair<string, int> item){TagBuilder tagBuilder = new TagBuilder("label");tagBuilder.SetInnerText(item.Key);return tagBuilder.ToString(TagRenderMode.Normal);}

Calculation of the number of iterations :

int iMod = items.Count % (int)settings.cbl_RepeatColumns;int iterationsCount = items.Count / (int)settings.cbl_RepeatColumns + (iMod == 0 ? 0 : 1);

Sampling condition for each iteration :

foreach (KeyValuePair<string, int> item in items.Where((item, index) =>settings.cbl_Direction == Direction.Horizontal ?index / (int)settings.cbl_RepeatColumns == i:(index - i) % iterationsCount == 0))

where i is the iteration number.
When outputting horizontally, we take the elements whose indexes when divided by the number of columns == i:

[0 1 2 3][4 5 6 7][8 9 10 11]

When displaying vertically, we take the elements whose index and the current iteration divided by the number of iterations == 0:

[0 6 12 18][1 7 13 19]

Performance check

Let’s use 3 CheckBoxLists as an example and see what we end up with:

<h2> Index</h2>@using (Html.BeginForm()){@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml", new CheckBoxList_Model{items = Model.Languages, settings = new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItems", cbl_Layout = Layoutt.Table, cbl_Direction = Direction.Horizontal, cbl_RepeatColumns = RepeatColumns.FiveColumns}, htmlAttributes = new { @cellpadding = "0", @cellspacing = "0" }})<br />@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml", new CheckBoxList_Model{items = Model.Languages, settings = new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItemsTwo", cbl_Layout = Layoutt.Flow, cbl_Direction = Direction.Vertical, cbl_RepeatColumns = RepeatColumns.ThreeColumns}})<br />@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml", new CheckBoxList_Model{items = Model.Languages, settings = new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItemsThree", cbl_Layout = Layoutt.Flow, cbl_Direction = Direction.Horizontal, cbl_RepeatColumns = RepeatColumns.TwoColumns}})<br /><input type="submit" value="send" />}

Creating your own CheckBoxList control
In order to obtain the selected values, we will need to model our ViewModel_Index add 3 arrays with names that we pass to our CheckBoxLists :

public class ViewModel_Index{public IDictionary<string, int> Languages { get; set; }public int[] SelectedCheckBoxListItems { get; set; }public int[] SelectedCheckBoxListItemsTwo { get; set; }public int[] SelectedCheckBoxListItemsThree { get; set; }}

as well as add the controller method Index which processes post requests :

[HttpPost]public ActionResult Index(ViewModel_Index model){//TODO:return View();}

After putting a breakpoint, let’s look at the result :
Creating your own CheckBoxList control
If you don’t care about code duplication, view models and maintenance, you can just call the CheckBoxList itself in the view and get the selected values on the server directly in the array (but remember that the CheckBoxList name and the server parameter should be the same):
Creating your own CheckBoxList control
Creating your own CheckBoxList control
Creating your own CheckBoxList control
Well, now that we’ve completely finished what we planned, there’s one last thing left.Consider extending our control with new properties by object htmlAttributes

Expanding CheckBoxList

In order to pass a new property to our item code, we have to go from the constructor, to the initial call (or vice versa, as one likes). It all starts with our constructor in Controls.cs – We add a new parameter object htmlAttributes:

public static MvcHtmlString CheckBoxList(this HtmlHelper helper, IDictionary<string, int> items, CheckBoxList_Settings settings, object htmlAttributes)

Our constructor is called in the Partial View with the name CheckBoxList.cshtml We add a new property for the CheckBoxList model:

public class CheckBoxList_Model{public IDictionary<string, int> items;public CheckBoxList_Settings settings;public object htmlAttributes;}

And pass it when called in our Partial View:
Before :

@model CheckBoxList_Model@Html.CheckBoxList(Model.items, Model.settings)

After :

@model CheckBoxList_Model@Html.CheckBoxList(Model.items, Model.settings, Model.htmlAttributes)

It remains to add the initialization of the property itself in Index view and append the code of our element :

@Html.Partial("~/Views/Partial/Controls/CheckBoxList.cshtml", new CheckBoxList_Model{items = Model.Languages, settings = new CheckBoxList_Settings{cbl_Name = "SelectedCheckBoxListItemsThree", cbl_Layout = Layoutt.Table, cbl_Direction = Direction.Vertical, cbl_RepeatColumns = RepeatColumns.FourColumns}, htmlAttributes = new { @border = "3", style = "color: Grey; border-style:dashed;" }})

Code for selecting a frame (table, div):

public static TagBuilder GenerateHtmlMarkup_OuterTag(Layoutt cbl_Layout, IDictionary<string, object> htmlAttributes){...TagBuilder tagBuilder = new TagBuilder(htmlTag);tagBuilder.MergeAttributes(htmlAttributes);...}

As you can see below, everything works :
Creating your own CheckBoxList control

Conclusion

As a conclusion, I would like to understand what we have done after all:

  • Considered creating, using, and extending your own control based on the ASP .NET MVC Html helper;
  • Created a project framework for further development of controls;
  • Implemented web application structure with separation of responsibilities of each functional unit;
  • Learned about a site with which to compete when writing your own controls.

List of materials

1. http://mvc.devexpress.com/Editors/CheckBoxList
2. How to handle checkboxes in ASP.NET MVC forms
3. Project source code

You may also like