Handling Messages directly

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

My previous samples demonstrated using ServiceModel to dispatch messages to a strongly typed message handlers. There are times when instead of having a seperate implementation for every message you will want to have a centralised message handler (often this handler is called ProcessMessage). An early version of the Peer channel system code had a message handler called ProcessMessage where the Peer channel messages were dispatched.

A Strongly Typed contract

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh {

[OperationContract(IsOneWay = true)]

void Say(string message);

}

This is considered strongly typed because the method Say takes a string as an argument.

The Contract

This contract defines a a handler that recieves raw messages, it will recieve messages for any action.

public static class ProcessMessageActions

{

public const string SayAction = "Say";

}

[ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

interface IProcessMessage

{

[OperationContract(Action="*", IsOneWay = true)]

void ProcessMessage(Message message);

}

The Action="*" option on the OperationContract attribute is called the wildcard action it says that any value in the Headers.Action property of the message will be handled by the message handler specified by this contract, and the argument type is Message; Message is a new abstraction in the System.ServiceModel namespace that allows a developer to operate on raw messages when necessary, such as adding or retrieving Message Headers, generating or retrieving the raw Xml.

The ProcessMessageActions class defines the actions to be used in a convenient manner.

The Proxy

class HelloMesh : IProcessMessage

{

public void ProcessMessage(Message message)

{

if (message.Headers.Action == ProcessMessageActions.SayAction)

{

string data = message.GetBody<string>();

Console.WriteLine(data);

}

}

}

Again the proxy does little more than delegate to the base implementation. This time the Method is called ProcessMessage and it takes a a Message as its argument. It can be used to create and read the elements of the message including headers.

The InstanceContext

This is the class that is used to handle inbound messages. It has an Implementation IProcessMessage in this case it verifies that the action matches a specific value, if so it does the work associated with that action.

class HelloMesh : IProcessMessage

{

public void ProcessMessage(Message message)

{

if (message.Headers.Action == ProcessMessageActions.SayAction)

{

string data = message.GetBody<string>();

Console.WriteLine(data);

}

}

}

The Application

The application is still trivial to write, as before you create an InstanceContext for incoming messages, a proxy for outgoing messages, and then in a loop read what to send, and then use the proxy to send it.

In this case it is necessary to create a message object with the data.

class Program

{

static void Main(string[] args)

{

InstanceContext instanceContext = new InstanceContext(new HelloMesh());

using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint"))

{

string msg = "";

while (msg != "q")

{

msg = Console.ReadLine();

Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, ProcessMessageActions.SayAction, msg);

proxy.ProcessMessage(message);

}

proxy.Close();

}

}

}

The Configuration

The only changes necessary to the config are because we renamed the service contract and I also changed the mesh address:

To enable certain changes to be made after the application has been deployed for instance port, the endpoint and binding are usually specified in the app.config file. This is the config file for our application above.

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns="https://schemas.microsoft.com/.NetConfiguration/v2.0">

<system.serviceModel>

<client>

<endpoint name="HelloEndpoint"

address="net.p2p://hellomesh/processmessage"

binding="netPeerTcpBinding"

bindingConfiguration="Binding1"

contract="HelloMesh.IProcessMessage">

</endpoint>

</client>

<bindings>

<netPeerTcpBinding>

<binding name="Binding1"

port="5182">

<security mode="None"/>

</binding>

</netPeerTcpBinding>

</bindings>

</system.serviceModel>

</configuration>

Putting the code together in one piece leaves us witt:

The Complete Program:

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Text;

namespace HelloMesh

{

public static class ProcessMessageActions

{

public const string SayAction = "Say";

}

[ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

interface IProcessMessage

{

[OperationContract(Action="*", IsOneWay = true)]

void ProcessMessage(Message message);

}

partial class HelloProxy : System.ServiceModel.DuplexClientBase<IProcessMessage>, IProcessMessage

{

public HelloProxy(InstanceContext inputInstance, string configurationName) : base(inputInstance, configurationName)

{

base.InnerDuplexChannel.Open();

}

public void ProcessMessage(Message message)

{

base.InnerProxy.ProcessMessage(message);

}

}

class HelloMesh : IProcessMessage

{

public void ProcessMessage(Message message)

{

if (message.Headers.Action == ProcessMessageActions.SayAction)

{

string data = message.GetBody<string>();

Console.WriteLine(data);

}

}

}

class Program

{

static void Main(string[] args)

{

InstanceContext instanceContext = new InstanceContext(new HelloMesh());

using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint"))

{

string msg = "";

while (msg != "q")

{

msg = Console.ReadLine();

Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, ProcessMessageActions.SayAction, msg);

proxy.ProcessMessage(message);

}

proxy.Close();

}

}

}

}