Not long after I joined Microsoft, I was looking at the Silverlight Airlines code sample. I was new to Silverlight, so I used this demo as a way to help learn Silverlight. The demo app is pretty cool, and ScottGu showed it off at Mix, so I had seen it before. I took on an exercise to attempt to add some features to the application, and this helped me learn more about Silverlight.
To select a flight, you drag your mouse from one city to another, and then drag over the date range for your travel. This will show a list of possible flights between the 2 cities. You can hover over a flight and it will animate. You can also click on a flight to “lock it in.”
There were 4 features that I wanted to add to this application:
- Allow a Return Flight to also be picked. Right now, the date range you select is a bit useless; only the start date is used. I wanted to use the end date for selecting a return flight.
- Allow a Forms based view over this data.
- Allow for business-logic driven boundaries on date selection.
- Demonstrate how the stock calendar control could be dropped in and still have the application work – this would prove that the calendar control was decoupled from the rest of the application.
When I dove into the code to try to make this happen, I found that the code was a bit tangled up. The main XAML page has 3 controls on it: Map, Calendar, ItineraryPicker. These controls all know about each other and communicate with each other. For instance, when cities are connected on the map, it informs the itinerary picker of the new cities. When an itinerary is selected on the itinerary picker, it tells the map what to animate. When dates are selected on the calendar, it tells the itinerary to get a new list of flights, which then tells the map to stop animating.
In order to support the return flight selection, I would have to be hacking away at the code in some not-so-pretty ways. And I couldn’t see any way to offer a forms view. The code needed to be refactored. Into a ViewModel pattern.
If you look at the FlightSelectionMapView.xaml, you’ll see that there’s a lot more markup now. However, this markup sets up the bindings to the ViewModel. Here’s how it works:
- App.xaml.cs instantiates the FlightSelectionViewModel
- This ViewModel instance is set as the DataContext of a container control that contains both the FlightSelectionMapView and the FlightSelectionFormView
- The FlightSelectionViewModel has the following properties
- City Cities
- City Origin
- City Destination
- Itinerary DepartingItineraries
- Itinerary ReturningItineraries
- Itinerary SelectedDepartingItinerary
- Itinerary SelectedReturningItinerary
- DateTime AvailableDateStart
- DateTime AvailableDateEnd
- Collection<DateTime> TravelDates
- DateTime? DepartingDate
- DateTime? ReturningDate
- The Map control has bindings to the following properties:
- Cities (one-way)
- Origin (two-way)
- Destination (two-way)
- SelectedDepartingItinerary (one-way)
- SelectedReturningItinerary (one-way)
- The Calendar control has been bound to the following properties:
- SelectionStartDate (one-way)
- SelectionEndDate (one-way)
- TravelDates (two-way)
- The departing flight ItinerarySelector control then has the following bindings:
- DepartingItineraries (one-way)
- SelectedDepartingItinerary (two-way)
- And the returning light ItinerarySelector control has these bindings:
- ReturningItineraries (one-way)
- SelectedReturningItinerary (two-way)
Note that there’s only one ItinerarySelector control, it’s just being used twice on the same form, once bound to the departing properties and once bound to the returning properties.
By refactoring the controls to work with these bound values, I was able to remove all references to the FlightService from the controls themselves. Only the ViewModel references the FlightService.
A by-product of this achievement is that we can now unit test the ViewModel to ensure that the view interacts with the flight service correctly. In the code download, you’ll notice that I have added a test project to demonstrate this. It has some tests for the ViewModel, the FlightService, and the Itinerary class.
For the form view, I didn’t go too far with it. I basically just proved that it could work. It’s really, really ugly, and there are a couple of bugs. But, there’s a button to switch to form view nonetheless. You can make selections on the form and switch back to map view, and you see that the map view has changed to reflect the data you modified in form view. This works because both views are sharing the same instance of the ViewModel, and they are both bound to its properties.
This shows how an existing application can be refactored into a ViewModel pattern to loosen up the coupling, offer better flexibility for new features, and also testability. There are some different techniques that could be used on the ViewModel, but the goals were met for the exercises I set out to accomplish.