Share via


Multicasting Messages with AppFabric – Publish / Subscribe Sample Application – Part 2 of X

Make sure to visit Part 1. This is a continuation.

https://blogs.msdn.com/brunoterkaly/archive/2010/01/19/multicasting-messages-the-world-opens-up-spreading-the-message-part-1-of-10.aspx

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.

 image_thumb53

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.

image

MulticastClient is WeatherStationClient

image

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.

image_thumb1

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.”

 image_thumb4

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.

image_thumb6 

This is what a blank solution looks like.

image_thumb7

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.

image_thumb8

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.

image_thumb9

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. 

image_thumb10

 

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.

image_thumb11

Note the MulticastHost (3rd project being added). It will be the receiver of messages sent by MulticastClient.

image_thumb12

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.

image_thumb13

Here is the finished solution. It should match just as you see it here.

image_thumb14

References are very important. Getting your references wrong can result in difficult to debug scenarios.

image_thumb15

Notice the we set System.Runtime.Serialization and System.ServiceModel.

image_thumb16

 

**Installation Issue

Have you installed the Windows Azure platform AppFabric SDK? If not, then do so now. Just “bing” it.

image_thumb18

You must use this dll in this exact folder.

image_thumb17

If you installed the Windows Azure platform AppFabric SDK you should see the folder structure below.

image_thumb19

 

 Get your references straight - Repeat references so that all three projects have the same references seen above.

image_thumb20

Same thing with MulticastHost.

image_thumb21

All 3 projects should set references as seen below.

image65_thumb

At this stage your project should look like this:

image_thumb27

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.”

image_thumb28

Add the class module.

image_thumb29

 

Call it “DataContracts.cs”

image_thumb30

Add some code snippets.

image_thumb33

The code snippet below is pretty big. But it isn’t too complicated. Let’s break it down.

  1. class WeatherData
  2. class MyLatLon
  3. class WeatherInfo
  4. interface IMulticastContract
  5. interface IMulticastChannel

 image_thumb34

class WeatherData

This a .NET Object that holds weather information.

image_thumb35

This class holds our basic data for weather conditions, including high, low, urls for icons, and so on.

image_thumb36

WeatherData has an interesting function:

image_thumb37

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.

image_thumb39

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.

image

Notice that we have ordinary string[] arrays. The job of the Load() method is to convert XML to string[] data.

image_thumb44

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.

image_thumb45

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.

image_thumb48

As with all the classes, notice the fact that all objects are serializable with the “DataContract” attribute and the “DataMember” attribute:

 image_thumb51

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.

image_thumb54

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.

image

 

 

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.

image3_thumb

We can talk more about ReportWeather() once we get to building MulticastHost (WeatherStationBillboard) .

So paste in the code below into this spot:

image_thumb52

Code Copy Instruction

Copy this code snippet into DataContracts.cs. You should compile successfully afterwords.

Code Snippet

  1. using System;

  2. using System.Collections.Generic;

  3. using System.Linq;

  4. using System.Text;

  5. using System.Diagnostics;

  6.  

  7.  

  8. namespace DataContracts

  9. {

  10.     using System;

  11.     using System.ServiceModel;

  12.     using System.Runtime.Serialization;

  13.     using System.Xml;

  14.     using System.Collections;

  15.     //------------------------------------------------------------

  16.     //

  17.     //------------------------------------------------------------

  18.     [DataContract(Name = "WeatherData", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]

  19.     public class WeatherData : IExtensibleDataObject

  20.     {

  21.         [DataMember]

  22.         public string[] prob_precip;

  23.         [DataMember]

  24.         public MyLatLon latlon;

  25.         [DataMember]

  26.         public string zip;

  27.         [DataMember]

  28.         public DateTime dateTime;

  29.         [DataMember]

  30.         public string[] lowTempF;

  31.         [DataMember]

  32.         public string[] highTempF;

  33.         [DataMember]

  34.         public string[] cloudIconURL;

  35.  

  36.         //------------------------------------------------------------

  37.         //

  38.         //------------------------------------------------------------

  39.         public string[] GetStringsFromNodeLists(XmlNodeList xmlnodelist)

  40.         {

  41.             string[] arr = new string[xmlnodelist.Count];

  42.             int i = 0;

  43.             System.Xml.XmlNode xmlNode = null;

  44.             while (i < xmlnodelist.Count)

  45.             {

  46.                 xmlNode = xmlnodelist[i];

  47.  

  48.                 arr[i] = xmlNode.Value.ToString();

  49.                 i++;

  50.             }

  51.             return arr;

  52.         }

  53.         //------------------------------------------------------------

  54.         //

  55.         //------------------------------------------------------------

  56.         public void Load(XmlDocument doc, string number_of_days)

  57.         {

  58.  

  59.             DateTime weather_time = DateTime.Now;

  60.             XmlNodeList highs = doc.SelectNodes("/dwml/data/parameters/temperature[@type='maximum']/value/text()");

  61.             this.highTempF = GetStringsFromNodeLists(highs);

  62.  

  63.             XmlNodeList lows = doc.SelectNodes("/dwml/data/parameters/temperature[@type='minimum']/value/text()");

  64.             this.lowTempF = GetStringsFromNodeLists(lows);

  65.  

  66.             XmlNodeList local_prob_prec = doc.SelectNodes("/dwml/data/parameters/probability-of-precipitation/value/text()");

  67.             this.prob_precip = GetStringsFromNodeLists(local_prob_prec);

  68.  

  69.             XmlNodeList cloudIcon = doc.SelectNodes("/dwml/data/parameters/conditions-icon/icon-link/text()");

  70.             this.cloudIconURL = GetStringsFromNodeLists(cloudIcon);

  71.  

  72.         }

  73.         //------------------------------------------------------------

  74.         //

  75.         //------------------------------------------------------------

  76.         private ExtensionDataObject extensionData_Value;

  77.         public ExtensionDataObject ExtensionData

  78.         {

  79.             get

  80.             {

  81.                 return extensionData_Value;

  82.             }

  83.             set

  84.             {

  85.                 extensionData_Value = value;

  86.             }

  87.         }

  88.     }

  89.  

  90.     //------------------------------------------------------------

  91.     //

  92.     //------------------------------------------------------------

  93.     [DataContract(Name = "MyLatLon", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]

  94.     public class MyLatLon : IExtensibleDataObject

  95.     {

  96.         [DataMember()]

  97.         public decimal Lat;

  98.         [DataMember()]

  99.         public decimal Lon;

  100.         public MyLatLon()

  101.         {

  102.             Lat = Convert.ToDecimal(0);

  103.             Lon = Convert.ToDecimal(0);

  104.         }

  105.  

  106.         public MyLatLon(string _Lat, string _Lon)

  107.         {

  108.             Lat = Convert.ToDecimal(_Lat);

  109.             Lon = Convert.ToDecimal(_Lon);

  110.         }

  111.  

  112.         private ExtensionDataObject extensionData_Value;

  113.  

  114.         public ExtensionDataObject ExtensionData

  115.         {

  116.             get

  117.             {

  118.                 return extensionData_Value;

  119.             }

  120.             set

  121.             {

  122.                 extensionData_Value = value;

  123.             }

  124.         }

  125.     }

  126.  

  127.  

  128.     //------------------------------------------------------------

  129.     //

  130.     //------------------------------------------------------------

  131.     [DataContract(Name = "WeatherInfo", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]

  132.     public class WeatherInfo : IExtensibleDataObject

  133.     {

  134.         [DataMember()]

  135.         public string ZipCode;

  136.         [DataMember()]

  137.         public int NumberOfDays;

  138.         [DataMember()]

  139.         public WeatherData weather_data = new WeatherData();

  140.         [DataMember()]

  141.         public MyLatLon LatLon;

  142.  

  143.         public WeatherInfo()

  144.         {

  145.         }

  146.         public WeatherInfo(XmlDocument doc, MyLatLon latlon, string zipcode, int number_of_days)

  147.         {

  148.             weather_data.zip = ZipCode = zipcode;

  149.             NumberOfDays = number_of_days;

  150.             weather_data.latlon = LatLon = latlon;

  151.  

  152.             SetupWeatherInfo(doc, number_of_days);

  153.  

  154.         }

  155.         private void SetupWeatherInfo(XmlDocument doc, int number_of_days)

  156.         {

  157.             weather_data.Load(doc, number_of_days.ToString());

  158.         }

  159.  

  160.         private ExtensionDataObject extensionData_Value;

  161.  

  162.         public ExtensionDataObject ExtensionData

  163.         {

  164.             get

  165.             {

  166.                 return extensionData_Value;

  167.             }

  168.             set

  169.             {

  170.                 extensionData_Value = value;

  171.             }

  172.         }

  173.     }

  174.  

  175.  

  176.     //------------------------------------------------------------

  177.     //

  178.     //------------------------------------------------------------

  179.     [ServiceContract(Name = "IMulticastContract", Namespace = "https://samples.microsoft.com/ServiceModel/Relay/")]

  180.     public interface IMulticastContract

  181.     {

  182.         [OperationContract(IsOneWay = true)]

  183.         void ReportWeather(WeatherInfo weather_info);

  184.     }

  185.  

  186.     //------------------------------------------------------------

  187.     //

  188.     //------------------------------------------------------------

  189.     public interface IMulticastChannel : IMulticastContract, IClientChannel { }

  190.  

  191. }

You should be able to compile at this point. We are on our way! Thanks for reading. More to come.

 image_thumb53