WCF Extensibility part 3: Formatting operations

Time to get serious and drill down into the first area of the big picture. We are going to take a look at “operation formatting”, which is roughly located in the highlighted area.

image

One of the crucial parts of the WCF channel layer is the way that method invocations get transformed into WCF messages. This capability allows the end-programmer to work with the object-oriented paradigm. This means you call methods on proxy objects, instead of having to fiddle with the messages going back and forth between a client and service yourself.

Let’s say we have a proxy that was created like so:

 

[ServiceContract]
public interface IRcon
{
    [OperationContract(Action = "kick")]
    string Kick(string steamID);
}

public interface IRconProxy : IRcon, IClientChannel { }

class Program
{
    static void Main(string[] args)
    {
        SourceRconTcpBinding binding = new SourceRconTcpBinding("abc123");
        string uri = "raw.tcp://192.168.100.101:27015";
        IRconProxy proxy = null;
        ChannelFactory<IRconProxy> factory = new ChannelFactory<IRconProxy>(binding, new EndpointAddress(uri));
        factory.Endpoint.Behaviors.Add(new SourceRconEndpointBehavior());
        factory.Open();

        proxy = factory.CreateChannel();

        string reply = proxy.Kick("1:0:1337");

 

Take a look at the last line. There you can see a method call being made on the proxy that was created with a call to ChannelFactory<IRconProxy>.CreateChannel. We want to influence the way this method call gets transformed into a specialized System.ServiceModel.Messaging.Message class, called SourceRconMessage. The regular transformation would change a method call into normal Message objects. Our message object holds the spcifics for the Source rcon protocol. Here is the shape of the class, corresponding to the elements of the rcon protocol:

public class SourceRconMessage : Message
{
    public int RequestID { get { ... } }
    public ServerData ServerData { ... } }
    public string String1 { get { ... } }
    public string String2 { get { ... } }
}

We will get back to the specifics of the message in a later post.

The process of changing a method invocation to a message and vice versa is called operation formatting. The key interfaces for all classes that performs this task are System.ServiceModel.Dispatcher.IClientMessageFormatter and IDispatchMessageFormatter. They are essentially the same but mirrored, because they represent the formatting that will happen at the client and service respectively.

public interface IClientMessageFormatter
{
	object DeserializeReply(Message message, object[] parameters);
	Message SerializeRequest(MessageVersion messageVersion, object[] parameters);
}

 

We will only look at the client message formatter, since we are building a client-side only implementation of the channel stack. The service’s side would have DeserializeRequest and SerializeReply, mirroring the direction requests and replies are sent and received.

Let’s look at each of the implementations of the two methods of the IClientMessageFormatter interface:

// Changes a list of parameters and operation description into a SourceRconMessage
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
    SourceRconMessage message = new SourceRconMessage(operationDescription.Messages[0].Action, parameters);
    message.Headers.MessageId = new UniqueId(message.RequestID.ToString());

    return message;
}

No rocket science here. The process of changing the action and parameters of the method call into a message is performed by a custom-built constructor on the SourceRconMessage class. The one thing to remember for later though is the mysterious property of MessageId in the Headers of the message that we set. This will be revisited at a later stage.

 

// Goes from SourceRconMessage to return value and parameter list
public object DeserializeReply(Message rawMessage, object[] parameters)
{
    // Deserialize message and return value
    object returnValue = null;
    SourceRconMessage message = (SourceRconMessage)rawMessage;

    // TODO: Use returnParameter.ParameterType to get the type of return parameter
	// and deserialize to that type from string
    returnValue = message.String1;

    return returnValue;
}

 

The DeserializeReply method will get a deserialized reply message that was handed from the channel stack. This should be a SourceRconMessage object (which we should probably check for to be sure it really is). Right now we only extract the single valuable piece of information from the message: the String1 property. It is passed back as the return value, which will work as long as the return value is of type string. Notice how there is no type information available for the return value and parameters.

The two methods above go into the SourceRconOperationFormatter class.

 

public class SourceRconOperationFormatter: IClientMessageFormatter
{
    // Goes from SteamRconMessage to return value and parameter list
    public object DeserializeReply(Message rawMessage, object[] parameters) { ... }

    // Changes a list of parameters and operation description into a SteamRconMessage
    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) { ... }
}

 

The class can contain much more. A later and improved version will have a special constructor to hold the operation description details to be able to have type information on the parameters and return value for the sake of DeserializeReply.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s