Introduction
In many ways, ASP.NET MVC represents a big step forward from Web Forms. Instead of working with an abstraction that tries to impose a non-web model onto a web development framework, ASP.NET MVC embraces the HTTP model and presents developers with a way of working that is much more in-tune with how the web actually works. This is liberating, but for developers only familiar with the Web Forms model, it can be a bit intimidating as well. To work effectively in MVC, we need to have a solid understanding of how HTML forms work, this is something that Web Forms never really required or encouraged (though it was always desirable). In this article, I'll be looking at creating a classic UI construct in MVC: indicating selection by moving items from one list box to another.Pre-requisites
You'll need at least Visual Studio 2008 (or the equivalent Express edition) with MVC version 2 installed. I'm assuming a reasonable level of familiarity with ASP.NET development and how MVC projects are structured.Getting started
Getting in the spirit of the season, we are going to build a Christmas gift selector. Of course, if you aren't reading this in December, it's a bit less topical. Our Christmas-themed UI is shown below:Look at all that snow! Doesn't it make you feel festive? Needless to say, I've left the styling as an exercise for the reader. The two list-box scenario is an interesting one; it's fairly commonly used, and is very easy to implement in Web Forms, but it's actually not a very natural fit for HTML forms. To understand why, let's delve a little into how the list box works; once we understand this, implementing our user interface will be much easier.
The (slightly simplified) HTML source for one of the list boxes shown above looks like this:
Collapse | Copy Code
<select multiple="multiple" name="AvailableSelected" size="6">
<option value="1">Games Console</option>
<option value="2">MP3 player</option>
<option value="3">Smart Phone</option>
<option value="4">Digital Photo frame</option>
<option value="5">E-book reader</option>
<option value="6">DVD Box Set</option>
</select>
The select
element represents the list itself; the options represent the items in the list. Each option has a value
attribute specified, this determines the value that will be sent back
in the form data when an item is selected. Let's say a user selects the
Games Console, Digital Photo Frame, and DVD Box Set. The following data
gets posted back to the server: AvailableSelected=1&AvailableSelected=4&AvailableSelected=6
. 'AvailableSelected' comes from the name given to the select
element; 1, 4, and 6 refer to the IDs of the selected products. Notice
that if an item is not selected, then no information is sent about it.
This presents a problem for our two-list scenario: we are not so much
interested in what items are selected but the contents of each list. We
will therefore need to find another way of tracking what items the user
has selected. As it turns out, this is not terribly difficult, but
before we move on, let's spend a moment thinking about how this would
work in a Web Forms scenario.Web Forms ListBox
With a Web FormsListBox
, the items stored in the
listbox are available to the server-side code after a form post. For our
scenario, this would be very useful. We know that no information is
sent in the form data, so how does this work? The ListBox
server control automatically saves all of its contents into the Web
Form's ViewState hidden field. When the form is posted back, it
recreates its contents from this data, then inspects the form data to
see if any of its items were selected or not. Without any extra work on
the part of the developer, state is automatically maintained across form
posts; this is great, right? Well, yes and no. This method works really
well as long as you stay within the confines of the Web Forms
methodology. Try to step outside this though, and you start hitting
problems. For example, try manipulating the list items through
JavaScript, and you'll find the changes are not reflected in your server
code. Worse, you'll probably find your app will start throwing
exceptions unless you adjust the ViewState security settings. What
should be a simple modification turns into a huge mess. And if you don't
understand the underlying mechanisms, it can be very difficult to fix
the problem; I've encountered ASP.NET developers genuinely baffled as to
why changes they make in JavaScript aren't visible in the server code.Using the Code
Now that we understand a bit of theory, let's have a look at some of the key parts of the sample code TheProduct
that represents the gifts the user can choose is shown below:
Collapse | Copy Code
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Decimal Price { get; set; }
}
We'll also create a View Model that encapsulates the data going to /
from the View. To fill the two list boxes, we'll need two lists of
product data: one for the available list, and one for the requested
list. We'll need to get back which items in the lists are selected when
the user moves items from one box to another. Thinking back to our
explanation earlier, the list box data is represented as repeated key /
value pairs, with the name of the list box as the key and each selected
ID as a value. Fortunately, we don't have to decode this data manually,
ASP.NET MVC's model binding facility can do this for us. We will store
the data as an array of int
s, which will be
populated for us from the form data. We'll also need somewhere to save
the state between form posts; all we need to remember is what product
IDs are in the requested list box. We'll do that by saving a comma
delimited list in a string.The relevant parts of the
ViewModel
class are shown below:
Collapse | Copy Code
public class ViewModel
{
public List<Product> AvailableProducts { get; set; }
public List<Product> RequestedProducts { get; set; }
public int[] AvailableSelected { get; set; }
public int[] RequestedSelected { get; set; }
public string SavedRequested { get; set; }
}
The list box part of the View is shown below:
Collapse | Copy Code
<%using(Html.BeginForm()){ %>
<div>
<table>
<thead>
<tr>
<th>Available</th><th>
</th><th>Requested</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top">
<%=Html.ListBoxFor(model => model.AvailableSelected,
new MultiSelectList(Model.AvailableProducts, "Id",
"Name", Model.AvailableSelected),
new { size = "6" })%>
</td>
<td valign="top">
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
</td>
<td valign="top">
<%=Html.ListBoxFor(model => model.RequestedSelected,
new MultiSelectList(Model.RequestedProducts, "Id",
"Name", Model.RequestedSelected))%>
</td>
</tr>
</tbody>
</table>
<br />
<%=Html.HiddenFor(model=>model.SavedRequested) %>
</div>
<%} %>
Note the Html.ListBoxFor
format; the first parameter sets the property of the model to use (one of the arrays); the second parameter creates a MultiSelectList
, which is a collection of objects similar to the ListItem
class in Web Forms - each item has a value, text, and selected
property. The constructor sets the collection to use as the list, and
the properties to use as the value and text fields, as well as an IEnumerable
representing the values that should be selected to which we pass one of our arrays. Because it's a MultiSelectList
,
multiple items can be selected and moved at the same time. A hidden
field stores the information that is used to store the state between
form posts.The controller code that receives the form values is shown below:
Collapse | Copy Code
[HttpPost]
public ActionResult Index(ViewModel model, string add, string remove)
{
//Need to clear model state or it will interfere with the updated model
ModelState.Clear();
RestoreSavedState(model);
if (!string.IsNullOrEmpty(add))
AddProducts(model);
else if (!string.IsNullOrEmpty(remove))
RemoveProducts(model);
SaveState(model);
return View(model);
}
Thanks to the magic of model binding, the method takes a parameter of type ViewModel
.
ASP.NET MVC will automatically parse the incoming form values and match
up what it can. This means the arrays will have values (if any items
were selected), as will the SavedRequested
field, but the List
s of Product
s will be null
.The string parameters represent the two buttons. When a submit button is pressed, its name and value (text) are included in the form values. By checking which of the string values isn't empty, we can determine which button was pressed. The
RestoreSavedState
method gets the saved product IDs from the SavedRequested
field and uses this to recreate the list of requested products from the previous roundtrip. The SaveState
method takes the current state and stores it in the string for saving
in the hidden field. With that, the list box functionality is complete,
and items can be moved freely between them. The downloadable sample
contains the full application, including some simple validation and a
details list for the requested items.
No comments:
Post a Comment