You know you should be moving code out from behind your forms, windows and web pages and into stand-alone classes. Everyone preaches that this is what to do, everyone shows you examples of ViewModel classes, but no one really shows you a real-world example of how to get rid of the code behind.
In this article I will first show you a typical WPF window that uses ADO.NET to add, edit and delete data from a table. All of the code to interact with ADO.NET is in the code behind of the window. You will then see the exact same window and learn how to move each method into a class and call that class from the code behind. The intent here is not to teach you a pure Model-View-View-Model (MVVM) approach, but how to create classes to perform all of your data access and other functions that you might be doing in your code behind.
The WPF Window
In Figure 1 you see an example of a very simple add, edit delete screen for shirts. This screen allows you to input a new shirt name, select a size and color and add that new shirt. You may select an existing shirt and modify the name, size and color, and you may also delete a shirt. There are three tables in this simple database: Shirts, ShirtSizes and ShirtColors. The WPF window has 375 lines of code in order to accomplish the add, edit and delete functionality. When you move the code behind into a ViewModel class, you will cut the code behind down to 55 lines of code!
When you move the code behind into a ViewModel class, you will cut the code behind from 375 down to 55 lines of code!
This does not mean you are eliminating 320 lines of code. This code simply is moved into another class file. In fact, you will end up writing more code in the ViewModel class in order to get the same functionality. The advantage you get by moving code into a ViewModel class is you can reuse this same class in WPF and ASP.NET. With a little careful work, you could even use this ViewModel class from a WCF service to serve up data to Silverlight, Windows Phone and Windows 8 Metro XAML applications. And, the best advantage of moving code into a ViewModel class is you can now unit test all of that code in the ViewModel and never have to run your user interface! This saves a ton of time and leads to more reliable code.
A big advantage of moving code into a ViewModel class is you can now unit test all of that code in the ViewModel and never have to run your user interface.
The workings of this add, edit and delete screen are fairly simple to understand. When the application loads the WPF window, the sizes and colors of shirts are loaded into the respective combo boxes. The window also loads all of the shirts in the Shirts table into a ListView control. When you click on an item in the ListView control, the information for that shirt is shown in the detail area at the bottom of the screen. There are buttons for adding and deleting shirts located between the ListView and the detail area. If you change the Shirt name or change a size or color in the detail area, then the screen is placed into “edit mode” and other buttons will appear which will allow you to save or cancel the changes you made.
Loading the Data
Look at Listing 1 and Listing 2 to see the code that loads shirts into the ListView and the Colors into the Color ComboBox. A DataLayer class with static methods to load a DataTable and execute SQL is used to wrap up ADO.NET so you don’t have to write all of the ADO.NET code in your code behind. I assume that most everyone today is using some sort of class wrapper around ADO.NET like this one, or using the Entity Framework or LINQ to SQL. You can look at the sample application that comes with this article to see the code for the DataLayer, but it is just very straight-forward ADO.NET code.
Listing 1 shows the LoadShirts method used to fill the ListView control named lstData with the results from a view called vwShirts. This view is a join between the Shirts, ShirtSizes and ShirtColors tables. To create the columns for the ListView control there is a method called CreateGridViewColumns which, if you pass in a DataTable, will extract the column names and use those as the View for the ListView. You can learn more about this method by reading my blog entry at http://bit.ly/cOWjaz.
The LoadColors (Listing 2) and LoadSizes (not shown) methods are almost identical. They both use the DataLayer class to retrieve the color and size data and fill their respective combo boxes. I have just listed the LoadColors method (Listing 2) in the article to save space.
After you have loaded all shirts, you can now select one from the ListView control by clicking on a row with your mouse pointer. When you do this, the SelectionChanged event procedure on the ListView will fire.
private void lstData_SelectionChanged(
object sender,
SelectionChangedEventArgs e)
{
ListView vw = (ListView)sender;
DisplayAShirt(Convert.ToInt32(
((DataRowView)vw.Items[vw.SelectedIndex])
["ShirtId"]));
}
In the lstData_SelectionChanged event procedure you will need to get the selected index of the item you clicked on. The item at this index is a DataRowView object from the DataTable that loaded the ListView. Once you get the DataRowView you can then extract the ShirtId field from that row and pass that integer value to a method called DisplayAShirt (Listing 3).
The DisplayAShirt method retrieves a single row of shirt data from the Shirts table. You need to get all of the data from the Shirts table so you can get the ColorId and SizeId fields. You’ll use these fields to set the position of the Color and Size combo boxes.
Adding a New Shirt
When you click on the button to add a new shirt you need to do a few things. First, you need to set a property called IsAddMode to true in this window so you know you are going into add mode. Next, you need to blank out the text box where the user will enter a shirt name. I left the combo boxes on the same size and color, but you could also set their SelectedIndex to -1 to blank them out and force the user to choose a new size and color. You place the rest of the window into an “edit state” as shown in Figure 2. The following code snippet shows the Click event procedure for the add button.
private void btnAdd_Click(object sender,
RoutedEventArgs e)
{
IsAddMode = true;
txtShirtName.Text = string.Empty;
EditMode();
}
You’ll call the method named EditMode in order to place the window into a valid edit state. When the user is editing data you do not want them to be able to click the Add or Delete buttons, so you make those invisible. You want a Save and a Cancel button to be visible so the user can save or undo the changes they make to the record. In addition, you don’t want the user to move to another item until they finish editing, so you will disable the ListView control. The code snippet below shows the EditMode method:
Saving Data
Once the user is done editing the data, they can either click on the save or the cancel button. When they click the save button you need to validate the data. The btnSave_Click event procedure (Listing 4) calls a method named DataValidate (Listing 6) that returns a true value if the data is valid, otherwise it returns a false value. If the data is valid you then detect whether or not they clicked on the Add button and thus you need to insert data, or they modified something in a text box or a combo box directly and thus you need to update the data.
When you need to insert a new record, the InsertData method (Listing 5) is called. This method builds an INSERT statement with the fields in the Shirts table. It uses parameters in the dynamic SQL to avoid any SQL injection attacks. A SqlCommand object is built, the data from the text box and combo boxes are filled into SqlParameter objects, these parameter objects are added to the SqlCommand, and finally the SqlCommand object is passed to the ExecuteSQL method of the DataLayer class.
If the INSERT is successful, a true value is passed back from this method. When that happens, the btnSave_Click event procedure places the window back into normal mode. Normal mode means to put the Add and Delete button back on the screen, make the Save and Cancel button invisible, enable the ListView control and reset the list box that displayed any validation errors. The btnSave_Click event procedure calls the NormalMode method, shown below.
private void NormalMode()
{
btnAdd.Visibility = Visibility.Visible;
btnDelete.Visibility = Visibility.Visible;
btnSave.Visibility = Visibility.Collapsed;
btnCancel.Visibility = Visibility.Collapsed;
lstData.IsEnabled = true;
lstMessages.Visibility = Visibility.Collapsed;
}
Updating data (the UpdateData method) is very similar to the InsertData method, so it is not listed here in the article. You can download the complete sample program at the location listed at the end of this article where you can see the UpdateData method and others that are not pertinent to this discussion.
Validating Data
Validating data is important and must be done prior to inserting or updating a table in a database. The DataValidate method, shown in Listing 6, checks to see if each piece of data entered is correct. If any business rule fails, a new ValidationMessage object is created with the message to display. Using a collection class for ValidationMessage objects is a great mechanism for creating a list of messages to display to the user. A collection can be displayed in WPF in a list box, in ASP.NET as a bulleted list, etc. Any user interface can deal with a collection class of objects. Figure 3 shows an example of what is displayed if all of the business rules fail on this particular window.
Deleting Data
If the user selects a row in the ListView and clicks on the Delete button, you ask them if they wish to delete the shirt. If they answer “yes” then call the DeleteAShirt method passing in the ShirtId that is set in the DisplayAShirt method. Below is the code in the btnDelete_Click event procedure to ask the user and call the DeleteAShirt method:
private void btnDelete_Click(object sender,
RoutedEventArgs e)
{
if (MessageBox.Show("Delete this Shirt?",
"Delete?",
MessageBoxButton.YesNo) ==
MessageBoxResult.Yes)
DeleteAShirt(ShirtId);
}
The DeleteAShirt method (Listing 7) accepts a ShirtId as a parameter. This parameter is then used to build a DELETE statement to submit to the database. A SqlCommand object is created with a single parameter and the ExecuteSQL method of the DataLayer class is called to delete the shirt from the database. If the shirt is deleted successfully, the shirts are re-loaded. By reloading the shirts it removes any deleted shirts, plus will read any additions or changes to the underlying table by other users.
The ViewModel Approach
To get rid of code behind you need to take approximately 95% of the code you wrote in the code behind event procedures and move those methods to a new class. This class, known as a “ViewModel” class will have the responsibility to gather data from your model classes (in this case, using ADO.NET), and set what “state” the form should be in; Edit or Normal, for example.
To get rid of code behind you need to take approximately 95% of the code you wrote in the code behind event procedures and move those methods to a new class.
In the sample code that accompanies this article I included a class called ShirtViewModel where I have moved all of the methods from the code behind. When you move your code behind into your ViewModel class you will always do a few common things.
- Change most of the methods from a “private” scope to a “public” scope.
- Remove any references to any controls such as list boxes, text boxes, check boxes, buttons, etc.
- For each action that you perform on a control, create a property for that action. For example, if you change the Visibility of a Save button, you will create a property named IsSaveVisible.
As an example of what to do, let’s take a look at the EditMode method in the code behind:
private void EditMode()
{
btnAdd.Visibility = Visibility.Collapsed;
btnDelete.Visibility = Visibility.Collapsed;
btnSave.Visibility = Visibility.Visible;
btnCancel.Visibility = Visibility.Visible;
lstData.IsEnabled = false;
}
Notice that you set the Visibility property of the various buttons and the IsEnabled property of the ListView control. So for each of these actions you now create properties in your ViewModel class as shown in the following code snippet for each of the buttons and list boxes.
public bool IsAddVisible { get; set; }
public bool IsDeleteVisible { get; set; }
public bool IsSaveVisible { get; set; }
public bool IsCancelVisible { get; set; }
public bool IsListEnabled { get; set; }
The public properties listed above are auto-properties just to keep the code simple for purposes of print. In the actual WPF application sample, each property has a private backing property and calls the PropertyChanged event in order to inform the user interface that a property has been updated and the UI needs to update itself as appropriate.
In an ASP.NET application these properties can be simple auto-properties since the PropertyChanged event is not required.
Change the EditMode method in your ViewModel class from “private” to “public” scope. Modify each of the properties to the same value that you set the control to in your code behind as shown in the following code snippet.
public void EditMode()
{
IsAddVisible = false;
IsDeleteVisible = false;
IsSaveVisibile = true;
IsCancelVisibile = true;
IsListEnabled = false;
}
Notice that you are now using simple Boolean values to express the state each button should be in. Using Boolean values allows you to reuse the ViewModel class in a WPF application, Windows Forms, ASP.NET, Silverlight, Windows Phone and Metro XAML applications. In addition, you can now write a unit test against the EditMode method and verify the state of each property after calling this method. If they are all set appropriately the test succeeds.
You now just bind each of these ViewModel properties to the appropriate property of the WPF control on the window. For the Visibility properties, you will have to use a Boolean to Visibility converter class. In an ASP.NET application you only need to write code like the following in order to set the appropriate buttons from the ViewModel.
private void SetUIState()
{
btnAdd.Visible = _ViewModel.IsAddVisible;
btnCancel.Visible = _ViewModel.IsCancelVisible;
btnSave.Visible = _ViewModel.IsSaveVisible;
...
...
}
Loading Data in the ViewModel
On this WPF window there are three lists that must be loaded: the Shirts, the Colors and the Sizes. In the code behind model you called the DataLayer to build a DataTable and placed that DataTable object directly into the DataContext of the ListView and the two ComboBox controls. When using a ViewModel you will create three properties to hold this data as shown in the following code snippet.
private DataTable Shirts { get; set; }
private DataTable Colors { get; set; }
private DataTable Sizes { get; set; }
To keep things simple, I am using a DataTable for each of these properties. It would be better for you to use a collection class to represent each table.
When you move the LoadShirts method (Listing 1) into your ViewModel class you will make the method public and replace the two lines that set the ListView named lstData to simply set the Shirts property as shown in Listing 8.
You will make this same change to both the LoadColors method and the LoadShirts method.
Display a Shirt in ViewModel
To display a single shirt after the user clicks on an entry in the ListView control, you will set a SelectedIndex property in your ViewModel to be used as an index into the Shirts DataTable property. For each text box, combo box and other controls on a window or Web page, add a property to hold the data for each control. In our simple three-control window you will need a ShirtId (the primary key), ColorId, SizeId and ShirtName property.
public int SelectedIndex { get; set; }
public int ShirtId { get; set; }
public int ColorId { get; set; }
public int SizeId { get; set; }
public string ShirtName { get; set; }
From the WPF user interface you can bind the SelectedIndex property of the ListView control to the SelectedIndex property of the ViewModel. When the user clicks on an item in the ListView control in WPF, the “set” of the SelectedIndex property will fire. In this “set” procedure you can write code like the following:
public int SelectedIndex
{
get { return _SelectedIndex; }
set
{
if (_SelectedIndex != value)
{
_SelectedIndex = value;
DisplayAShirt();
RaisePropertyChanged("SelectedIndex");
}
}
}
Notice the call to the DisplayShirt method. This method, shown in Listing 9, then uses the SelectedIndex property to grab a data row from the Shirts DataTable property.
In an ASP.NET Web Form application where you are not using model binding, you won’t use a SelectedIndex property. Instead you will most likely grab the ID from the CommandArgument of an Edit button. You would then call the DisplayAShirt method passing in the value of the ShirtId primary key. You can see an example of this in the ASP.NET Web Form application in the download for this article.
The New Code Behind Just Calls the ViewModel Class
When you are done moving all methods to the ViewModel class and making the appropriate methods public, your code behind will now look like Listing 10. Notice how there is very little code left. Most of the event procedures are simply calls to methods in the ViewModel. This is what you should strive to get to in your WPF, Windows Forms, ASP.NET, Silverlight, Metro XAML and any type of application that you develop. Keep the code in the code behind to an absolute minimum. For WPF, Silverlight and Metro XAML apps you can use Commanding to even further reduce the code in your code behind. You can explore that on your own, as that is beyond the scope of this article.
Summary
In this article you saw a dramatic reduction in the code behind of a typical WPF window. By moving your code out of the code behind and into a class you gain reusability and testability. Using classes for all of your data access, data validation and UI properties is the key to great application architecture. You won’t find that writing a little extra code in your ViewModel class is a bother; instead, you will find the logic for the code much simpler since that logic is contained in one place. Be sure to look at the sample code for this article. In the download you will find four applications: a WPF application with code behind and the same WPF application using a ViewModel class, an ASP.NET Web Forms application using code behind and an ASP.NET Web Forms application using a ViewModel class. From these downloads I am sure you will be able to use these as a model for moving your applications into a MVVM-type of application.