Earlier this week I wrote a post detailing the application framework that I’ve been working on, which I’m calling AgFx. I wanted that post to be a bit of an introductory overview,. Now I’m going to dig into detail a bit more, as well as show a bit about how you can use the free Windows Phone 7 Developer tools to quickly create a great application. AgFx does a lot, and one of the apps I used to generate requirements and testing, is Jeff Wilcox’s gorgeous new foursquare™ client: 4th & Mayor, which is built on AgFx.
Jeff and I, along with some others, have written several apps on top of AgFx over the last few months, and have really sharpened it to be exactly what you need when building a connected phone application. And the steps that you use to do it are almost always the same, and I’ll detail them here.
If you’re thinking about writing an app that talks to a web service of any kind, go ahead and follow along with that example in your head.
Below is pretty long and comprehensive, but it is not only a basic tutorial, it is also a summary of some of the broader set of features in AgFx. So it may give you some ideas about how to use AgFx for your next Phone application, or even your current one! We have moved several existing data-connected applications to AgFx and in every case the code becomes smaller, simpler, and better performing.
The first step is to go take a look at the data that the web service offers and how you can process it. In this example case we’ll be using the NOAA National Weather Service REST Web Service.
So we go to the website above an look at what the web service offers. Looking through at their service operations, I see this one:
Which sounds about right. Give it a zip code, get back weather data. And there is an example for a query there as well:
http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?zipCodeList=98052&format=12+hourly&numDays=7
We like things that we can easily build a query string from. AgFx supports more complex operations, but these GET queries are a slam dunk. If you click on that link, you’ll get a bunch of XML data. It’s a little hairy, but the data is there so all we need to do is parse it, which is easy enough. OK, weather data: check.
One thing it does not give us, though, is the name of the city for the zip code. It gives us the latitude and longitude, but that’s not that helpful really. We want to be able to display the name of the city for a given zip code, and we can get that from WebServiceX.NET, here.
WebServiceX.net provides an API for converting a zip code into a city and state name.
Basically we call this: http://www.webservicex.net/uszip.asmx/GetInfoByZIP?USZip=98052, and we get this:
<?xml version="1.0" encoding="utf-8"?> <NewDataSet> <Table> <CITY>Redmond</CITY> <STATE>WA</STATE> <ZIP>98052</ZIP> <AREA_CODE>425</AREA_CODE> <TIME_ZONE>P</TIME_ZONE> </Table> </NewDataSet>
Perfect! City name: check.
Now it’s time to start writing the code to talk to the service.
The first thing to think about is the shape of the objects coming back from the service, and what is their unique identifier. In database terms, this would be the primary key. Given that key, I expect to get the correct data back, and that data should be discreet from calls using different keys.
In the weather case, the weather data is basically a set of weather forecast objects. They always come together as a group and this app will never talk to them individually – it’s the collection of objects that makes up the data we care about. We don’t care about addressing the individual forecasts, so the zip code works great as the identifier for our forecast.
For the zip code information, it’s even easier, the data is tied directly to the zip code, so that’s also the unique identifier here.
Before we go on, let’s think of some other examples.
Imagine you were talking to the Flickr service. Flickr has identifiers on almost every object. For example the identifier for my user profile on Flickr is “78467353@N00”. If I was to write a model for my Flickr user profile, I’d use that as my identifier. The same goes for a model that represents my PhotoStream, because that’s tied to my user account. But a model for an individual album or photo would use the id associated with that item. And so on.
This identifer is how you’ll ask for data from AgFx, so that’s why it’s important, we’ll see more about this later:
DataManager.Current.Load<ZipCodeVm>(txtZipCode.Text);
So, back to LoadContext what’s going on with that guy? Good question.
The LoadContext is where I store data that will be used to make a request for my data. For example, if I want to request a photos on Flickr from a photo album, I may want to pass in paging information, or information about how many items to return. The LoadContext is where I would put that information, the reason why will be clear later. LoadContext really exists for more sophisticated cases and in the case of simple requests, like those for the weather app, you won’t need to bother with it much. But you’ll see it in the API so you should understand what it’s for.
Even in simple cases, it also gives you a chance to make things strongly typed.
If you look at the base LoadContext:
public class LoadContext { public object Identity { get; } public string UniqueKey {get;} public LoadContext(object identity) {} // .. other stuff }
you’ll see the Identity property there is of type object. So in this case, we’re just going to derive and give ourselves a nice strongly-typed property and constructor:
public class ZipCodeLoadContext : LoadContext { public string ZipCode { get { return (string)Identity; } } public ZipCodeLoadContext(string zipcode) : base(zipcode) { } }
One important thing to note here is that the parameter on the constructor should match the type of object you’ll be passing in. In this case, the zip is going to be a string, so there should be a 1-parameter ctor that takes a string. AgFx will look for this to automatically create the LoadContext.
We can reuse this LoadContext for the zip code service as well. Handy!
Now that we’ve got the LoadContext, we can create our ViewModel. ViewModels should derive from ModelItemBase<T>, which you’ll want to be familiar with. So let’s look at some of the members you’ll be using:
ModelItemBase derives from NotifyPropertyChangedBase, which is a default implementation of INotifyPropertyChanged and also adds some helpful functions:
Okay, that’s it for ModelItemBase. Let’s create our ViewModel for the weather forecast:
First, let’s create the constructors:
public class WeatherForecastVm : ModelItemBase<ZipCodeLoadContext> {
public WeatherForecastVm() { } public WeatherForecastVm(string zipcode): base(new ZipCodeLoadContext(zipcode)) { } //... }
This is pretty simple, but it’s important to note two things:
Okay, now we add some properties to the ViewModel.
Most properties look like this:
private string _city; public string City { get { return _city; } set { if (_city != value) { _city = value; RaisePropertyChanged("City"); } } }
Note the “RaisePropertyChanged” call at the bottom, which calls down to NotifyPropertyChangedBase declared above. This let’s the UI know that it should update this value.
For collection properties, things are just a little bit different. When you databind to collection properties, you typically use an ObservableCollection<T> so that any databound UI will automatically update when you make changes to your items. That still works fine here, too, but with a small tweak
For collection properties in your ViewModel, that you should do the following:
The reason for this is because AgFx knows which instance of your object is bound to your UI, and always updates that instance (it uses the same instance across your application). So when an update happens, it “copies” the property values from the fresh instance into the one currently held by the system. Rather than doing this automatically, because you may want to manage how the copying happens, you’ll typically just do this:
private ObservableCollection<WeatherPeriod> _wp = new ObservableCollection<WeatherPeriod>(); public ObservableCollection<WeatherPeriod> WeatherPeriods { get { return _wp; } public set {
if (value == null) throw new ArgumentNullException(); if (_wp != null) { _wp.Clear(); foreach (var wp in value) { _wp.Add(wp); } } RaisePropertyChanged("WeatherPeriods"); } }
So now you can add all of your properties to fill out the shape of your ViewModel.
When you fetch data from your service, how long is it good for? AgFx defines a very simple way to define how long data should be cached for in your object.
The way this is done is with the CachePolicyAttribute. The constructor for CachePolicyAttribute takes two parameters:
So we just apply this to our ViewModels as in the following:
[CachePolicy(CachePolicy.ValidCacheOnly, 60 * 15)] public class WeatherForecastVm : ModelItemBase<ZipCodeLoadContext>{…} // this basically never changes, but we'll say // it's valid for a year. [CachePolicy(CachePolicy.CacheThenRefresh, 3600 * 24 * 365)] public class ZipCodeVm : ModelItemBase<ZipCodeLoadContext>{…}
That’s it.
Okay, now is when the real party starts. The DataLoader is the beating heart of your ViewModel objects.
The DataLoader has two primary jobs:
Fortunately both of these are pretty easy.
Your DataLoader is an object that implements IDataLoader<T> where T is your LoadContext type from earlier. Your DataLoader should be a public nested type in your view model. This isn’t strictly necessary (see AgFx.DataLoaderAttribute) but I haven’t yet found a case where I need to do it otherwise. If it’s a nested type, AgFx will automatically find and instantiate it.
IDataLoader<T> It has two methods:
Implementing these is also simple. Here’s how GetLoadRequest usually looks:
private const string ZipCodeUriFormat = "http://www.webservicex.net/uszip.asmx/GetInfoByZIP?USZip={0}"; public LoadRequest GetLoadRequest(ZipCodeLoadContext loadContext, Type objectType) { // build the URI, return a WebLoadRequest. string uri = String.Format(ZipCodeUriFormat, loadContext.ZipCode); return new WebLoadRequest(loadContext, new Uri(uri)); }
Make sense? It’s pretty simple. If you needed more parameters on the request, then you’d add more properties to your LoadContext and then use that to build out the URI.
AgFx will take that object and it will result in a network request. When that network request returns, the stream that it provided will be passed along back to the DataLoader for processing.
Processing the network data is typically straight-forward. I’m doing manual XML walking here. If your web service exposes a WSDL that the Windows Phone Developer Tools can handle, you can use that to make parsing a snap, usually using DataContractSerializer,, but for clarity I’ll just do the brute force method below.
public object Deserialize(ZipCodeLoadContext loadContext, Type objectType, System.IO.Stream stream) { // the XML will look like the following, so we parse it. //<?xml version="1.0" encoding="utf-8"?> //<NewDataSet> // <Table> // <CITY>Kirkland</CITY> // <STATE>WA</STATE> // <ZIP>98033</ZIP> // <AREA_CODE>425</AREA_CODE> // <TIME_ZONE>P</TIME_ZONE> // </Table> //</NewDataSet> var xml = XElement.Load(stream); var table = ( from t in xml.Elements("Table") select t).FirstOrDefault(); if (table == null) { throw new ArgumentException("Unknown zipcode " + loadContext.ZipCode); } ZipCodeVm vm = new ZipCodeVm(loadContext.ZipCode); vm.City = table.Element("CITY").Value; vm.State = table.Element("STATE").Value; vm.AreaCode = table.Element("AREA_CODE").Value; vm.TimeZone = table.Element("TIME_ZONE").Value; return vm; }
So what we do here is get into the XML stream with XLINQ, then just create a new ViewModel object (of type ZipCodeVm) and populate it’s properties with the values that we got out of XML.
Note the objectType property. The returned object MUST be an instance of this type (derived types are OK). AgFx will enforce this. You can take a look at the Deserialize method in the WeatherForecastVm as well, it’s pretty much the same, but it handles much more complex XML from the NOAA service via a helper class.
Now we just compile to make sure it’s all building.
Believe it or not, our application is just about done. Using the great sample data features of Expression Blend, it’s really easy to create our UI.
We’ll walk through a simple case of just the zip code information that we deserialized above. To make this easy to understand, what we want to do is create a UserControl that shows the city and state, along with the LastUpdated date. For the whole weather application, it’s the same process with a little more detail.
For this, I add a new Windows Phone User Control to my project, right click on the XAML file in Solution Explorer and choose “Open in Expression Blend…”.
Once Blend opens with the UserControl showing on the design surface, the first thing we’ll do is create sample data for our ViewModel:
Now we choose “Create Sample Data From Class”:
This will show us the public types in our project assemblies. We’re looking for “ZipCodeVm”, which is one of the VM objects we created up in step 3.
Hit OK, and now just drag the ZipCodeVm item onto the design surface:
Now we just build out our UI like so by dragging on some TextBlock objects from the Blend toolbar:
Now, for each of the items that says “TextBlock” we can just databind to our data fields by dragging them onto the object on the design surface, as demonstrated by the red arrow below. We’ll see sample (bogus) data that lets us just visualize how the UI will look at runtime.
Remember, it’s bogus sample data., it just allows us to see the layout. This prevents the tweak-F5-tweak-F5 method that many of use are used to.
After a little cleanup, here is what the XAML looks like. Notice the Binding statements to the properties we added in our ViewModel.
<StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding City}" FontWeight="Bold"/> <TextBlock Text=", "/> <TextBlock Text="{Binding State}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Updated: " /> <TextBlock Text="{Binding LastUpdated}" /> </StackPanel> </StackPanel>
Okay now we’ve got our View Models built, our data processing done, and our databinding done. Now we just have to load the data.
The primary object for data access and management in AgFx is DataManager. DataManager is a singleton class that is accessed via the DataManager.Current property.
DataManager only exposes a few operations, but they really are key to tying the whole thing together.
As a starter example, we’d load data into the UserControl that we built above as follows:
zipCodeUserControl1.DataContext = DataManager.Current.Load<ZipCodeVm>("98052");
Which gives us this at runtime:
Notice we are passing the zip code as a string. Remember form our ZipCodeLoadContext, that we added a constructor with just a single string parameter. AgFx will take that “98052” and automatically pass it to that constructor. Also notice that we don’t have to do anything to manage caching or networking here. AgFx figures out if the value is already available in the cache, if the cache is valid, or if it needs to be refreshed from the network.
It’s important to keep in mind that the instance returned from the Load call will be the same instance that is returned from any future Load or Refresh calls. This identity-tracking is one of the key features of AgFx. What this means, is that once I’ve databound to a value in my UI, future calls to Refresh or Load will update this same instance.
In other words, if in a button click you simply do this:
private void btnRefreshZipcode_Click(object sender, RoutedEventArgs e) { DataManager.Current.Refresh<ZipCodeVm>("98052"); }
then the UI above will update. Note we never touch the user control or it’s DataContext. It just works.
DataManager gives you a broad set of operations to interact with your data.
Remember these are all accessed via DataManager.Current:
For Load and Refresh, there are overloads that allow you to get handle both completion and error scenarios in code. The signatures look like this:
public T Load<T>(object id, Action<T> completed, Action<Exception> error) ;
which allows you to pass in a lambda that will be called when the operation completes or fails, like so:
this.DataContext = DataManager.Current.Load<WeatherForecastVm>(txtZipCode.Text, (vm) => { // upon a succesful load, show the info panel. // this is a bit of a hack, but we can't databind against // a non-existant data context... info.Visibility = Visibility.Visible; }, (ex) => { MessageBox.Show("Error: " + ex.Message); });
Note that if you pass in a handler for error, then the DataManager.UnhandledError event will not be invoked, regardless of what happens in the handler. You either handle via the lambda, or the global event, not both.
The “vm” parameter in the success handler will be the same instance as the object returned from the Load<> function as well, so any operations on this object will also be reflected in the UI. These handlers will invoke on the UI thread.
Finally, as I’ve mentioned before, it’s best to let DataManager do all the instance management for you. It does a lot of caching so don’t cache instances yourself unless you have to. For ViewModels that have properties that are other ViewModels (a pattern I really like because it encourages on-demand network activity as a user moves through an app), use this pattern:
/// <summary> /// ZipCode info is the name of the city for the zipcode. This is a separate /// service lookup, so we treat it separately. /// </summary> public ZipCodeVm ZipCodeInfo { get { return DataManager.Current.Load<ZipCodeVm>(LoadContext.ZipCode); } }
Just use the Load method to return the right value.
So back to our weather example, here is the parts of the UI that are serviced by the various objects we’ve been discussing:
If you build a debug version of AgFx, you’ll find some useful debugging output that will help you determine what AgFx is up to. In the case of a first-time run of the weather application, here’s the output you’ll seee:
No cache found for NWSWeather.Sample.ViewModels.WeatherForecastVm (1) 3/17/2011 2:22:35 PM: Queuing load for WeatherForecastVm (2) No cache found for NWSWeather.Sample.ViewModels.ZipCodeVm 3/17/2011 2:22:35 PM: Queuing load for ZipCodeVm 3/17/2011 2:22:35 PM: Checking cache for ZipCodeVm 3/17/2011 2:22:37 PM: Deserializing live data for NWSWeather.Sample.ViewModels.WeatherForecastVm (3) Writing cache for WeatherForecastVm, IsOptimized=False, Will expire 3/17/2011 2:37:37 PM (4) 3/17/2011 2:23:43 PM: Deserializing live data for NWSWeather.Sample.ViewModels.ZipCodeVm Writing cache for ZipCodeVm, IsOptimized=False, Will expire 3/16/2012 2:23:43 PM
Because these operations all happen off of the UI thread, you get interleaving between the call to the weather service and the zip code service. That’s good!
I’ve added the numbers in (bold) to the right to describe that this output means:
If you’re writing an app and it’s not doing what you think it should, this output will really help you understand what the problem might be. If you see a lot of this output when you don’t expect to, that means something is going on that may be resulting in extra data loads that you don’t need. So you can use this as a perf tuning input as well.
As a comparison, here’s the output from a second run, after the app has been exited and restarted a few minutes later. Note there are no “queuing load” or “writing cache” entries. This shows that cached data is actually being loaded, when it was last updated, and when it will expire.
3/17/2011 2:35:12 PM: Checking cache for WeatherForecastVm 3/17/2011 2:35:12 PM: Checking cache for ZipCodeVm 3/17/2011 2:35:12 PM: Loading cached data for ZipCodeVm, Last Updated=3/17/2011 2:23:43 PM, Expiration=3/16/2012 2:23:43 PM 3/17/2011 2:35:12 PM: Deserializing cached data for NWSWeather.Sample.ViewModels.ZipCodeVm, IsOptimized=False 3/17/2011 2:35:12 PM: Loading cached data for WeatherForecastVm, Last Updated=3/17/2011 2:22:37 PM, Expiration=3/17/2011 2:37:37 PM 3/17/2011 2:35:12 PM: Deserializing cached data for NWSWeather.Sample.ViewModels.WeatherForecastVm, IsOptimized=False
Phew, that’s a lot of stuff! Believe it or not, there’s more but I’ll leave it at that for now. I’ve had a lot of fun putting this framework together, with help from Jeff among others. And I’ve had even more fun writing apps on top of it. Hopefully you will too, please let me know how it goes!
Download AgFx with the NOAA Weather sample here. Note when you open this project, you'll get a few warnings about Silverlight project. These are expected and harmless because the base code is an Silverlight class library (so it can be used on Silverlight too). Just hit OK.
Shawn
This looks just awesome. Need to try it out with my app :)
Please tell me you'll be creating a NuGet package for AgFx?
I also vote for getting a NuGet package. Btw the download link to the example doesn't work for me (says that the element has not yet been published)
Yep, it's on NuGet now. Thanks!
Hi shawn, how are you?
Awesome work with AGFX, i was wonderig how one goes with sync data with a service..
I m using AGFX specially for getting the app running without Web connection.. and i want to cache the data, and when i m going online i will push this to thecloud.
My issue is that i don't find easy way to store the data that isn't sync ...
For now i created another collection on my vm where i store the NEWItems, but i could one persist this new object to the cache?! since this only local without any web request to get the stream..
Maybe i should go increating a diffrent VM for the new items?
Does this make sense ?
Hope u can give me a hand.
Thanks
Rui