In this section, you will use a publicly available weather web service located at www.webservicex.net/WCF/Default.aspx
to retrieve and display current weather for a given zip code within the
United States. In addition to weather services, there are many other
useful web services available at this location, including zip code
validation and currency conversion. As an exercise in the usage of
Rx.NET, you are encouraged to build useful, functional applications that
take advantage of these services.
From the development point of
view, the weather application will consist of (1) asynchronously
capturing user input—zip code, and (2) asynchronously calling the web
service and then displaying the current weather for a given zip code.
Let's go ahead and create the application.
1. Creating a Windows Phone Project
First, you will create a
new project, import all of the libraries and create service references
necessary to make the weather application work.
Launch Visual Studio 2010 Express for Windows Phone and create a new Windows Phone Application project. Name it "WeatherRx."
In MainPage.xaml, change the name of the application to "WeatherRx" and change the page title to "Weather App" (you are also certainly welcome to name the application and the page according to your preference).
Since you will be using Rx.NET to build this application, add a reference (by right-clicking and selecting Add Reference) to Microsoft.Phone.Reactive and System.Observable assemblies.
You need to add a Service Reference to the weather service already mentioned. The weather service is an .asmx web service hosted at www.webservicex.net.
To
add a reference to this service, right-click the Project Name and
select Add Service Reference. In the dialog that comes up, enter the
following value in the Address Textbox: http://www.webservicex.net/WeatherForecast.asmx. Press the "Go" button.
The WeatherForecast service should appear on the left. Click the arrow next to it, make sure to select WeatherForecastSoap service, and then rename the namespace to "svcWeather."
Your final Add Service screen should look like Figure 1.
Press the OK button.
2. Creating a User Interface
For the application, your goal is to create a screen that looks like the one shown in Figure 2.
To assist in that objective, the XAML for visual elements that appear
after the page title is pasted here.
Open MainPage.xaml and add the following code:
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentGrid" Grid.Row="1">
<TextBox Height="72" HorizontalAlignment="Left" Margin="0,51,0,0"
Name="txtZipCode" Text="" VerticalAlignment="Top" Width="480" />
<TextBlock Height="53" HorizontalAlignment="Left" Margin="6,13,0,0"
Name="lblLegend" Text="Enter Zip Code Below for Current Weather" VerticalAlignment="Top"
Width="462" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="6,129,0,0"
Name="lblWeatherFahrenheit" Text="Current Weather, Fahrenheit " VerticalAlignment="Top"
Width="435" />
<Image Height="150" HorizontalAlignment="Left" Margin="241,213,0,0"
Name="imgWeather" Stretch="Fill" VerticalAlignment="Top" Width="200" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="6,162,0,0"
Name="lblCelsius" Text="Current Weather, Celsius" VerticalAlignment="Top" Width="435" />
<TextBlock Height="30" Margin="6,379,39,0" Name="lblStatus" Text="" VerticalAlignment="Top" />
</Grid>
</Grid>
Notice that in addition to the
textblocks that will hold the current weather information, the XAML also
creates an image control that will show a visual representation of the
current weather (e.g., sunny, raining, snowing, etc.). Notice also that
the last </Grid>statement closes the LayoutGrid element, not shown in the preceding fragment.
3. Adding Logic to Get Weather Information
With design elements and proper
references in place, you are ready to add code to the application. In
this walkthrough, you will split the code into multiple functions for
enhanced readability.
Open MainPage.xaml.cs (by clicking MainPage.xaml and selecting View Code) and add the following using statements to the top of the page:
using Microsoft.Phone.Reactive;
using System.Windows.Media.Imaging;
Add the following code after the InitializeComponent() statement of the MainPage() constructor:
WireUpWeatherEvents();
WireUpKeyEvents();
Create the WireUpWeatherEvents function and its supporting GetWeatherSubject function by pasting the following code. Note how you have created a separate function (GetWeatherSubject) to return an Observable collection from the weather web service event.
private void WireUpWeatherEvents()
{
var weather = GetWeatherSubject();
weather.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
if (evt.EventArgs.Result.Details != null)
{
lblWeatherFahrenheit.Text = "Current Weather, Fahrenheit: " +
evt.EventArgs.Result.Details[0].MinTemperatureF.ToString() + " - " +
evt.EventArgs.Result.Details[0].MaxTemperatureF.ToString();
lblCelsius.Text = "Current Weather, Celsius: " +
evt.EventArgs.Result.Details[0].MinTemperatureC.ToString() + " - " +
evt.EventArgs.Result.Details[0].MaxTemperatureC.ToString();
imgWeather.Source = new BitmapImage(new
Uri(evt.EventArgs.Result.Details[0].WeatherImage, UriKind.Absolute));
}
},
ex => { lblStatus.Text = "Sorry, we encountered a problem: " + ex.Message; }
);
}
private IObservable<IEvent<svcWeather.GetWeatherByZipCodeCompletedEventArgs>>
GetWeatherSubject()
{
var weather = Observable.FromEvent<svcWeather.GetWeatherByZipCodeCompletedEventArgs>(weatherClient,
"GetWeatherByZipCodeCompleted");
return weather;
}
Create the WireUpKeyEvents function that will create an Observable collection from the KeyUp events and create subscription to that collection by adding the following code:
private void WireUpKeyEvents()
{
var keys = Observable.FromEvent<KeyEventArgs>(txtZipCode,
"KeyUp").Throttle(TimeSpan.FromSeconds(1)).DistinctUntilChanged();
keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
if (txtZipCode.Text.Length >= 5)
{
weatherClient.GetWeatherByZipCodeAsync(txtZipCode.Text);
}
});
}
Press
F5 to run the application. You should see a screen prompting you to
enter the US zip code to retrieve the current weather for. If you enter
your zip code, you should get a reasonable estimate of your current
weather, both on the Fahrenheit and Celsius scales. You should also see a
small picture with a visual representation of the current weather
conditions. Figure 4 shows sample output for the Jacksonville, FL area (zip code of "32202").
Let's spend some more time
dissecting the tools you used to build this application. First, you used
Rx.NET to create an Observable collection from the asynchronous
responses to the weather web service calls. You used the following
statement to create that collection:
var weather =
Observable.FromEvent<svcWeather.GetWeatherByZipCodeCompletedEventArgs>(weatherClient,
"GetWeatherByZipCodeCompleted");
You then defined an Observer for this data source, so that when the data is pushed from the web service to Observers, you take action by displaying that data in the User Interface.
Next, you created an Observable collection of the KeyUp events in the txtZipCode text box and created an Observer for that collection. As a result, whenever users pause their typing for one second, the Observer
on the keys data source will validate whether five or more digits have
been entered in the Zip Code field. Then, it goes ahead and calls the
function GetWeatherByZipCodeAsync, which in turn invokes an asynchronous request to the weather web service.
It's important to note
the asynchronous nature of all these calls—if you had other
functionality built into the application, you could continue using it
while the asynchronous request completes. The asynchronous processing is
an area that Rx.NET was specifically designed to address.
If you have done some form of
asynchronous programming prior to Rx.NET, you can certainly appreciate
that foregoing single code line. Prior to Rx.NET, in an asynchronous
method design pattern in .NET, two methods were provided. The first
method started the computation, and the second method acquired the
results of the computation. If there was more than one asynchronous
operation, even just the simple ones we have illustrated in the weather
example, the management of those multiple methods quickly became a
headache. The fact that Rx.NET also attempts to parallelize asynchronous
requests across all available cores is a hefty bonus to an already
generous benefits package of clarity and powerful querying of Observers.