Multicasting Messages with AppFabric – Publish / Subscribe Sample Application – Part 2 of X
Make sure to visit Part 1. This is a continuation.
This blog entry is about building the application
This blog entry is devoted to building our application. We will start from the very beginning. We will add each piece of the application slowly until we are done. I am obviously giving you all the source anyway so you can run it for yourself.
Create a blank solution to hold 3 projects
There are 3 projects for us to add. The way Visual Studio works is that one “solution” can contain many “projects.” The whole solution is explained below.
When we are done, the projects will appear like this.
Solution Name = MulticastSample
DataContract
Type = Class library
Purpose = To hold contracts and data structures shared by both applications.
MulticastClient (WPF Application)
Type = WPF Application
Purpose = The publisher that gets weather reports and sends to listeners
Multicasthost (WPF Application)
Type = WPF Application
Purpose = The listener that gets weather reports sent by MulticastClient
Note that I use these terms interchangeably. I say MulticastClient, I am talking about WeatherStationClient.
My rule is this – I leave the blog with a good sign.
The goal is for you to see that window above me when we are done. Email me when you are at bterkaly@microsoft.com and I’ll write the next entry.
MulticastClient is WeatherStationClient |
MulticastHost is a billboard app that pops up weather windows based on user entered zip codes. |
Here is what we are trying to get to. We are a long ways off now. But if you follow me, we’ll get there.
New Project Solution
I am using Visual Studio 2010 Beta 2 and everything works excellent. So to kick things off, close any projects you have and be in the ready state with Visual Studio. Start with a “File / New Project.”
This is the part where we are adding a “Blank Solution.” The solution will hold our other 3 projects. The 3 projects are added next.
This is what a blank solution looks like.
Adding 3 Projects
The first project that we want to add we'll be a class library Anne will hold our DataContracts and our interfaces.
Our project type is class library. The name will be DataContracts.
Our basic object model will be stored here. In addition, the contracts that are shared between MulticastClient and MulticastHost will be stored in this class library.
References to DataContracts will be made from MulticastClient and MulticastHost.
MulticastClient is the publisher. It is also the broadcaster, the sender. The pattern was described in previous blog posts.
The next step is to add MulticastClient, which will be of project type “WPF Application.” Later I may consider converting this to a Silverlight application.
The third application is also a “WPF Application” and will be called MulticastHost.
All we’re doing here is adding 3 new project to our solution. In sum we have one project solution to the individual projects. Two of the projects are WPF applications. The third application is of type class library and holds the shared data structures and messaging constructs.
The second project that we are adding is a WPF application. This is the client application that sounds weather to listeners. We will build this project out in the subsequent blog entry. But for now it will serve as a project placeholder.
Note the MulticastHost (3rd project being added). It will be the receiver of messages sent by MulticastClient.
We are now ready to add our third application to the solution. This application is MulticastHost, which will listen and receive messages sent by MulticastClient using the service bus in the AppFabric as the relay mechanism.
Here is the finished solution. It should match just as you see it here.
References are very important. Getting your references wrong can result in difficult to debug scenarios.
Notice the we set System.Runtime.Serialization and System.ServiceModel.
**Installation Issue
Have you installed the Windows Azure platform AppFabric SDK? If not, then do so now. Just “bing” it.
You must use this dll in this exact folder.
If you installed the Windows Azure platform AppFabric SDK you should see the folder structure below.
Get your references straight - Repeat references so that all three projects have the same references seen above.
Same thing with MulticastHost.
All 3 projects should set references as seen below.
At this stage your project should look like this:
The next step is the building of our data model. These are just some crude objects, nothing fancy. I could have segregated things better and spent more time making it more generic. But sometimes I like to make it as literal and simple as possible, minimizing special characters in my code like ~, ^, &, <, >, and so on. I wanted to focus on the AppFabric part. For a great list of AppFabric code or WCF code in general, go see Juval Lowy’s master book.
The next step is to delete “class1.cs” in project = “DataContracts.” Then you will add a new class module called “DataContracts.”
Add the class module.
Call it “DataContracts.cs”
Add some code snippets.
The code snippet below is pretty big. But it isn’t too complicated. Let’s break it down.
- class WeatherData
- class MyLatLon
- class WeatherInfo
- interface IMulticastContract
- interface IMulticastChannel
class WeatherData
This a .NET Object that holds weather information.
This class holds our basic data for weather conditions, including high, low, urls for icons, and so on.
WeatherData has an interesting function:
Remember, the National Weather Service provides data to us in XML format. The code above reads that XML data and reconstructs it as an array of strings. I like the way that function looks and it is effective at converting XML data into arrays embedded in my business objects, which get sent from client(publisher) to host (subscriber). I’ve talked about Pub/Sub before.
The Load() function helps us parse the XML Data. Load() is useful because it converts data that we can package into .NET objects. Load() is needed because once we get the data from talking to the National Weather Service, we need to parse it out into something different than XML. The data that MulticastClient gets is in XML format.
MulticastClient below gets data back that we need to parse with the code above. Eventually, MulticastClient will serialize objects and send them to the cloud.
Notice that we have ordinary string[] arrays. The job of the Load() method is to convert XML to string[] data.
Another noteworthy point is our derivation of IExtensibleDataObject. Provides a data structure to store extra data encountered by the XmlObjectSerializer during deserialization of a type marked with the DataContractAttribute attribute. There may be other ways to do this, but this worked for me. In terms of performance, I never trust anyone’s pre-conceived notions of what is efficient until you run some performance numbers using PerfMon or other similar performance tools.
class MyLatLon
This is a .NET object that holds latitude and longitude information. The weather service likes to use latitude and longitude. But the end user wants to enter zip code. The solution to this problem is to use a separate web service call that converts a zip code into latitude and longitude.
class WeatherInfo
This is an important class for taking the user input and orchestrating the data into a WeatherData object. It does all the work of getting the data and extracting what is needed so that Weather Billboards can display the data.
As with all the classes, notice the fact that all objects are serializable with the “DataContract” attribute and the “DataMember” attribute:
These attributes make it possible for you to send weather data out to consuming billboards.
The constructor for “WeatherInfo “ does the work of calling “WeatherData” to do the load, by using “weather_data.Load(doc, number_of_days.toString());”
class IMulticastContract
This is a very important interface that both MulticastClient and MulticastHost both use. It is the agreed upon way messages get communicated back and forth.
ReportWeather(WeatherInfo weatherInfo) is a very important method.
Essentially, the client calls “ReportWeather()”, passing a weather_info object. MulticastHost. The code running in “Billboard” is going to read the weather_info object and display that data in a nice little WPF window.
WeatherStationBillboard will display this popup window. The ReportWeather() method will build this window using code behind. Yes, I should have used some data templates. I had a MS code snippet that had a similar look and feel that I wanted so I used it.
We can talk more about ReportWeather() once we get to building MulticastHost (WeatherStationBillboard) .
So paste in the code below into this spot:
Code Copy Instruction
Copy this code snippet into DataContracts.cs. You should compile successfully afterwords.
Code Snippet
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace DataContracts
{
using System;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.Xml;
using System.Collections;
//------------------------------------------------------------
//
//------------------------------------------------------------
[DataContract(Name = "WeatherData", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
public class WeatherData : IExtensibleDataObject
{
[DataMember]
public string[] prob_precip;
[DataMember]
public MyLatLon latlon;
[DataMember]
public string zip;
[DataMember]
public DateTime dateTime;
[DataMember]
public string[] lowTempF;
[DataMember]
public string[] highTempF;
[DataMember]
public string[] cloudIconURL;
//------------------------------------------------------------
//
//------------------------------------------------------------
public string[] GetStringsFromNodeLists(XmlNodeList xmlnodelist)
{
string[] arr = new string[xmlnodelist.Count];
int i = 0;
System.Xml.XmlNode xmlNode = null;
while (i < xmlnodelist.Count)
{
xmlNode = xmlnodelist[i];
arr[i] = xmlNode.Value.ToString();
i++;
}
return arr;
}
//------------------------------------------------------------
//
//------------------------------------------------------------
public void Load(XmlDocument doc, string number_of_days)
{
DateTime weather_time = DateTime.Now;
XmlNodeList highs = doc.SelectNodes("/dwml/data/parameters/temperature[@type='maximum']/value/text()");
this.highTempF = GetStringsFromNodeLists(highs);
XmlNodeList lows = doc.SelectNodes("/dwml/data/parameters/temperature[@type='minimum']/value/text()");
this.lowTempF = GetStringsFromNodeLists(lows);
XmlNodeList local_prob_prec = doc.SelectNodes("/dwml/data/parameters/probability-of-precipitation/value/text()");
this.prob_precip = GetStringsFromNodeLists(local_prob_prec);
XmlNodeList cloudIcon = doc.SelectNodes("/dwml/data/parameters/conditions-icon/icon-link/text()");
this.cloudIconURL = GetStringsFromNodeLists(cloudIcon);
}
//------------------------------------------------------------
//
//------------------------------------------------------------
private ExtensionDataObject extensionData_Value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionData_Value;
}
set
{
extensionData_Value = value;
}
}
}
//------------------------------------------------------------
//
//------------------------------------------------------------
[DataContract(Name = "MyLatLon", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
public class MyLatLon : IExtensibleDataObject
{
[DataMember()]
public decimal Lat;
[DataMember()]
public decimal Lon;
public MyLatLon()
{
Lat = Convert.ToDecimal(0);
Lon = Convert.ToDecimal(0);
}
public MyLatLon(string _Lat, string _Lon)
{
Lat = Convert.ToDecimal(_Lat);
Lon = Convert.ToDecimal(_Lon);
}
private ExtensionDataObject extensionData_Value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionData_Value;
}
set
{
extensionData_Value = value;
}
}
}
//------------------------------------------------------------
//
//------------------------------------------------------------
[DataContract(Name = "WeatherInfo", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
public class WeatherInfo : IExtensibleDataObject
{
[DataMember()]
public string ZipCode;
[DataMember()]
public int NumberOfDays;
[DataMember()]
public WeatherData weather_data = new WeatherData();
[DataMember()]
public MyLatLon LatLon;
public WeatherInfo()
{
}
public WeatherInfo(XmlDocument doc, MyLatLon latlon, string zipcode, int number_of_days)
{
weather_data.zip = ZipCode = zipcode;
NumberOfDays = number_of_days;
weather_data.latlon = LatLon = latlon;
SetupWeatherInfo(doc, number_of_days);
}
private void SetupWeatherInfo(XmlDocument doc, int number_of_days)
{
weather_data.Load(doc, number_of_days.ToString());
}
private ExtensionDataObject extensionData_Value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionData_Value;
}
set
{
extensionData_Value = value;
}
}
}
//------------------------------------------------------------
//
//------------------------------------------------------------
[ServiceContract(Name = "IMulticastContract", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]
public interface IMulticastContract
{
[OperationContract(IsOneWay = true)]
void ReportWeather(WeatherInfo weather_info);
}
//------------------------------------------------------------
//
//------------------------------------------------------------
public interface IMulticastChannel : IMulticastContract, IClientChannel { }
}
You should be able to compile at this point. We are on our way! Thanks for reading. More to come.