Serialization, ObservableCollection and WPF Avalon Databinding
Most of the data binding samples in the SDK show binding to an object data source where the objects are populated in code, such as:
Employees e = new Employees();
This is all well and good for samples, but not necessarily how real applications work, which go to a database, a web service or use some kind of static XML. Now, with static XML, you can use the XMLDataProvider, but then you are having to get into all kinds of XPath trickness and you don't have the handiness of .NET objects. As such, I wanted to play with populating objects from an XML file using the .NET serializer. I put together a sample, riffing off the SDK Masterdetail sample that uses baseball as its context, that combines the use of ObservableCollections with the serializer.
I want to briefly go over how I build the sample and some gotchas to watch out for. The first thing I did was serialize the objects so I had an XML file to work with:
leaguelist = (LeagueList) dpMain.DataContext;
XmlSerializer serializer = new XmlSerializer(typeof(LeagueList));
StreamWriter writer = new StreamWriter("leaguelist.xml");
This provided me with XML that looked like this:
<?xml version="1.0" encoding="utf-8"?>
<ArrayofLeague xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
I wasn't too keen on the "ArrayofLeague" syntax so I added the following XML attribute to the object in code so that it said Leagues instead:
public class LeagueList : ObservableCollection<League>
Once I had my XML, I then needed to add code to do the deserialization and set the data context. The existing code did the object instantiation and data context setting in XAML, but I had to do all of this in code. What I ended up doing was putting the deserialization logic in the application logic, adding the deserialized object to the application's resource collections, like this:
serializer = new XmlSerializer(typeof(LeagueList));
StreamReader reader = new StreamReader("leaguelist.xml");
LeagueList l = (LeagueList)serializer.Deserialize(reader);
Then, I set the data context in the Loaded event of the window. I could have used the Initialized event as well, if I wanted to insure nothing was painted to the screen too quickly.
LeagueList leagueLB= (LeagueList)Application.Current.Resources["LeaguesDSO"];
rootPanel.DataContext = leagueLB;
I hit a few issues on the way that I wanted to share. When I first tried to do the deserialization, it failed and I had to revisit the SDK to remember the rules required for deserializing collection. The first rule, that the object support ICollection or IEnumerable, was covered because the ObservableCollection class implements these. However, with some of the other rules, I was busted. For example, the colleciton classes must have a parameterless constructor, which mine didn't Also, my classes didn't have setters on the properties, which I had to add. Lastly, I actually had to new up the collection classes in order for the deserializer to actually populate them. Lastly, remember the deserialized collections can't support any additional properties or fields and will only serialize its child objects.
But with those changes (which you can see in the Data.cs file) I was good to go! Now, I can add/change the XML file to my heart's content (well, assuming I stick to the schema) and the code will handle the deserialization.
A few things I could/should do to this sample:
- Support the PropertyChanged and CollectionChanged events to get more Avalon databinding goodness.
- Play with SGen.exe to optimize my data class for deserialization.
- Create an .XSD with xsd.exe so that my XML stays valid and doesn't fail deserialization