Lets continue the discussion of the .NET Compact Framework new WCF features by discussing the new EMAIL transport for store and forward messaging.  This BLOG builds off my previous BLOG on WCF deliverables included in NETCF 3.5.  I suggest you read my previous BLOG  before reading this BLOG, I also suggest you read Roman's BLOG as background.  Let’s take a closer look at this new feature by describing some of the key features discuss a few best practices and look at a HelloWorld example.

The EMAIL transport permits device addressability so that messages can be sent to a device and messages from the device can be stored up and forwarded on the next synchronization.  This allows developers to create applications for devices in the field to communicate their data to a local store.  The data is then transferred using ActiveSync's Always Up to Date passing the data through Exchange 2007 directly to the data center.  For more information on AUDT see: http://www.microsoft.com/technet/prodtechnol/exchange/2003/autd.mspx.  This feature also allows data to be pushed from the data center down to one or more devices; previously this was not an easy task! 

This communication is one way, the device can send a message to the data center or the data center can send a message to the device.  Each listener is limited to a specific channel name and will block while waiting for a message to be delivered.  For this reason, we suggest you spin up a separate thread for each channel listener allowing the data from the thread to be processed as needed and not block the entire application.

Using the email local store to store messages, allows the device to reboot or the application to be restarted while not interrupting the flow of messages.  To address this type of message delivery, your program will need to use a stateless programming model.

The sample below is an iteration of the http hello world example from my previous post.  It demonstrates the new EMAIL transport features in an easy to read format  Use this sample to help you start your investigation into Store and Forward Messaging, do not copy and paste it into production code.  This code will only run with the new EMAIL transport which ships with the .NET Compact Framework v3.5 beta1 which is being released with Orcas Beta1.

Configuring Your PC to run the Store and Forward Messaging Hello World.

First you will need the Beta2 or later version or later of the next Visual Studio code named Orcas.  Check http://msdn2.microsoft.com/en-us/vstudio/default.aspx for the latest updates.

Next you will need the Windows Mobile 6.0 SDK as it includes the ActiveSync Always up to Date features used by the EMAIL transport on device to keep the inbox up to date, see: http://www.microsoft.com/windowsmobile/default.mspx

The EMAIL transport on the data center side will only run on Exchange 2007.  If you have an Exchange 2007 server running on your network you can skip this section.  The Exchange team has made a trial in VPC form available, its a quick place to start. See http://www.microsoft.com/exchange/default.mspx for more details.   Before you move on, I suggest you validate your connection to the new Exchange server by running Outlook Web Access (OWA) from your PC.

Next you need to provide your emulator with network capabilities.  (ActiveSync's Always up to Date Feature does not run when ActiveSycn is providing the network connection.) For more on enabling networking with the emulator see: http://blogs.msdn.com/anandba/archive/2007/03/13/networking-support-on-device-emulator-in-vista.aspx

Once you have the Emulator up and running, next you need to configure ActiveSync to communicate with your Exchange Server.  See the Connecting to an Exchange Server by Using a Phone or a Wireless Network at: http://www.microsoft.com/technet/solutionaccelerators/mobile/deploy/msfp_8.mspx

I've updated the code below to reflect the changes found in Orcas Beta2.

 Store and Forward Messaging Hello World, the Device:

To begin with, you will need to create a new Smart Device project in Orcas.  You can use the Windows Mobile 5.0 or 6.0 SDK.  I used WM 6.0 Professional.

To start you need to add a reference to the device EMAIl transport Microsoft.ServiceModel.Mail.dll and Microsoft.ServiceModel.Mail.WindowsMobile.dll

Next lets add the needed using statements.

using System.Runtime.Serialization; using Microsoft.ServiceModel.Channels.Mail; using Microsoft.ServiceModel.Channels.Mail.WindowsMobile; using System.ServiceModel; using System.ServiceModel.Channels; using System.Xml.Serialization; using System.Xml;

Lets now add the TransmittedObject and XmlSerializerWrapper class from the previous post.

[System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://Microsoft.ServiceModel.Samples")] public class TransmittedObject { [System.Xml.Serialization.XmlElementAttribute(Order = 0)] public string str; [System.Xml.Serialization.XmlElementAttribute(Order = 1)] public int i; }
public sealed class XmlSerializerWrapper : XmlObjectSerializer { XmlSerializer serializer; string defaultNS; Type objectType; public XmlSerializerWrapper(Type type) : this(type, null, null){ } public XmlSerializerWrapper(Type type, string name, string ns) { this.objectType = type; if (!String.IsNullOrEmpty(ns)) { this.defaultNS = ns; this.serializer = new XmlSerializer(type, ns); } else { this.defaultNS = ""; this.serializer = new XmlSerializer(type); } } public override bool IsStartObject(XmlDictionaryReader reader) { throw new NotImplementedException(); } public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName) { throw new NotImplementedException(); } public override void WriteEndObject(XmlDictionaryWriter writer) { throw new NotImplementedException(); } public override void WriteObjectContent(XmlDictionaryWriter writer, object graph) { throw new NotImplementedException(); } public override void WriteStartObject(XmlDictionaryWriter writer, object graph) { throw new NotImplementedException(); } public override void WriteObject(XmlDictionaryWriter writer, object graph) { this.serializer.Serialize(writer, graph); } public override object ReadObject(XmlDictionaryReader reader) { string readersNS; readersNS = (String.IsNullOrEmpty(reader.NamespaceURI)) ? "" : reader.NamespaceURI; if (String.Compare(this.defaultNS, readersNS) != 0) { this.serializer = new XmlSerializer(this.objectType, readersNS); this.defaultNS = readersNS; } return (this.serializer.Deserialize(reader)); } }

Now we have enough information to build the message.

            TransmittedObject to = new TransmittedObject();
            to.str = "Hello";
            to.i = 5;

            XmlSerializerWrapper wrapper = 
                new XmlSerializerWrapper(typeof(TransmittedObject));

            Message m = Message.CreateMessage
                (MessageVersion.Default, "urn:test", to, wrapper);

Next let’s setup a few variables used in the rest of the program, the device and server e-mail addresses and the channel name.

string channelName = "StoreandFowardMessageHelloWorld"; string serverAddress = "ServerMailAddress@ExchangServer.com"; string clientAddress = "DeviceMailAddress@ExchangServer.com";

Let’s build the output channel and send the message to the server.

            WindowsMobileMailBinding binding = new WindowsMobileMailBinding();
            BindingParameterCollection parameters = new BindingParameterCollection();

            //Create and open the Channel Factory
            IChannelFactory<IOutputChannel> channelFactory =
                                            binding.BuildChannelFactory<IOutputChannel>(parameters);
            channelFactory.Open();
            IOutputChannel outChannel = channelFactory.CreateChannel(
                                new EndpointAddress(MailUriHelper.Create(channelName, serverAddress)));
            outChannel.Open();

 Time to send the message.

      outChannel.Send(m);

Now the message has been sent, we need to listen for the response.  The listener will block so I would suggest in your production code to run any listener on a separate thread.  For this example just place it below the sender.

            IChannelListener<IInputChannel> listner = binding.BuildChannelListener<IInputChannel>
                                      (MailUriHelper.CreateUri(channelName, clientAddress), parameters);

            listner.Open();

            IInputChannel inputChannel = listner.AcceptChannel();
            inputChannel.Open();

            Message reply = inputChannel.Receive();

Now that we have the responce, lets Deserialize it and present the results to the user.

            TransmittedObject to1 = 
                reply.GetBody<TransmittedObject>
                (new XmlSerializerWrapper(typeof(TransmittedObject)));

            MessageBox.Show(to1.str + " " + to1.i.ToString());

Finally lets clean up the EMAIL channel objects.

            outChannel.Close();
            channelFactory.Close();

            listner.Close();
            inputChannel.Close();
            binding.Close();

Store and Forward Messaging Hello World, the Server:

Let’s look at the code needed to respond to the devices requests.

Create a new Windows Console Application,

To start you need to add a reference to the device EMAIl transport Microsoft.ServiceModel.Mail.dll and Microsoft.ServiceModel.Channels.Mail.ExchangeWebService.dll

Next lets add the needed using statements.

using System.ServiceModel; using System.ServiceModel.Channels; using Microsoft.ServiceModel.Channels.Mail.ExchangeWebService; using Microsoft.ServiceModel.Channels.Mail; using System.Runtime.Serialization; using System.Xml; using System.Xml.Serialization;

We need the TransmittedObject and XmlSerializerWrapper class from the device code above, copy and paste that section to your new project.

Next let’s set a few global variables from the device project.

            string serverAddress = "ServerMailAddress@ExchangServer.com";
            string serverPWD = "MyPassword";
            string clientAddress = "DeviceMailAddress@ExchangServer.com";
            string exchageServerLocation = "http://ExchagneServer";

Let now create the listener, again the input channel will block so in your production code you should spin up a new thread for each listener.  In this sample as with the device sample we will place it in line. If you are using windows credentials just pass null value as the second argument to the ExchangeWebServiceMailBinding.

            ExchangeWebServiceMailBinding binding = new ExchangeWebServiceMailBinding(
                                            new Uri(exchageServerLocation),
                                            new System.Net.NetworkCredential(serverAddress, serverPWD));
            BindingParameterCollection parameters = new BindingParameterCollection();

            IChannelListener<IInputChannel> listner = binding.BuildChannelListener<IInputChannel>
                                                  (MailUriHelper.CreateUri(channelName, ""), parameters);
            listner.Open();
            IInputChannel inputChannel = listner.AcceptChannel();
            inputChannel.Open(TimeSpan.MaxValue);
            Console.WriteLine("Channel Open");
            Message reply = inputChannel.Receive(TimeSpan.MaxValue);
            Console.WriteLine("Message Received");
            XmlSerializerWrapper wrapper = new XmlSerializerWrapper(typeof(TransmittedObject));
            TransmittedObject to = reply.GetBody<TransmittedObject>(wrapper);

Processing the message.

            to.str = to.str + " World";
            to.i = to.i + 1;

            Console.WriteLine("Responce: " + to.str + " " + to.i.ToString());

Sending the response through an output channel.

            Message m = Message.CreateMessage(binding.MessageVersion, "urn:test", to, wrapper);

            IChannelFactory<IOutputChannel> channelFactory = binding.BuildChannelFactory<IOutputChannel>(parameters);

            channelFactory.Open();

            IOutputChannel outChannel = channelFactory.CreateChannel(new EndpointAddress(
                                                MailUriHelper.CreateUri(channelName, clientAddress)));
            outChannel.Open();

            Console.WriteLine("Out Channel Open");

            outChannel.Send(m);

            Console.WriteLine("Message Sent");

Finally lets clean up the EMAIL channel objects.

            outChannel.Close();
            channelFactory.Close();

            listner.Close();
            inputChannel.Close();
            binding.Close();

Time to give it a try, start the server first then run the client code to the emulator you configured to sync with your exchange server.