Monday, February 11, 2008

Making WCF "Behave" - Part Two

If you read my last post, you should have a pretty good idea of how Behaviors fit into the WCF stack and know that they have the ability to effect the runtime behavior (clever, huh) of your WCF host and/or client runtime by effecting the communication between dispatchers. If you missed this post, go ahead a read it now, I'll wait.

Done? Good.

In this post we are going to focus on service side behaviors (I'll cover client side behaviors soon).

Before we start slingin' code, it important to understand where the extensibility points for WCF behaviors are. On the service side there are two classes you can attach behaviors to. That are the System.ServiceModel.Dispatcher.DispatchOperation and the System.ServiceModel.Dispatcher.DispatchRuntime. Within each of these are properties that take a specific kind of interface which will be implemented by our custom behavior. Here is a quick guide to which property of which class takes which kind of interface.

DispatchRuntime

  • MessageInspectors takes IDispatchMessageInspector
  • InstanceContextProvider takes IInstanceContextProvider
  • InstanceProvider takes IInstanceProvider
  • OperationSelector takes IDispatchOperationSelector

DispatchOperation

  • ParameterInspector takes IParameterInspector
  • OperationInvoker takes IOperationInvoker
  • Formatter takes IDispatchMessageFormatter

Each property/interface pair performs a specific function in the WCF plumbing. Some are pretty obvious based on their names, others are not. I'll cover these in more detail in my upcoming seven part series "Better Know A Dispatcher Behavior."

If that doesn't make a whole lot of sense yet don't worry. As we work with behaviors just refer back to it and it will start to make sense

OK, enough theory, lets write some code!

Here we have an extremely basic WCF service:

[ServiceContract]
    public interface IHelloWCF
    {
        [OperationContract]
        string Hello(string name);
    }

    public class HelloWCF : IHelloWCF
    {
        public string Hello(string name)
        {
            return String.Format("Hello {0}", name);
        }
    }

This is hosted in a console application:

class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host =
                new ServiceHost(typeof(HelloWCF)))
            {
                host.Open();

                Console.WriteLine("Service is available.");

                Console.ReadLine();

                host.Close();
            }
        }
    }

As you would imagine, you call this service by passing in a name, like "James" and get a warm greeting from the service ("Hello James") as you can see below:

image

We're gonna have a little fun here with a behavior based on the IDispatchMessageInspector. We are going to write a behavior to return a different message. The first step is to create a new class which implements this interface:

public object AfterReceiveRequest(ref Message request,
    IClientChannel channel,
    InstanceContext instanceContext)
{
    return null;
}

public void BeforeSendReply(ref Message reply,
    object correlationState)
{
    return;
}

As you can see, there are two methods where we need to provide implementations for. AfterRecieveRequest gets fired when a message is coming into your service. You can take this opportunity to examine and change the message if need. Today we are more concerned with the BeforeSendReply method. As the name suggests this method gives you an opportunity to change the outgoing message. The above implementation will compile and work, but it doesn't do much. Let's change that:

public void BeforeSendReply(ref Message reply,
    object correlationState)
{
    MemoryStream memoryStream = new MemoryStream();
    XmlDictionaryWriter xmlDictionary =
        XmlDictionaryWriter.CreateBinaryWriter(
        memoryStream);
    xmlDictionary.WriteStartElement("HelloResponse",
        "http://tempuri.org/");
    xmlDictionary.WriteStartElement("HelloResult",
        "http://tempuri.org/");
    xmlDictionary.WriteString
        ("Ha ha! I stole your message!");
    xmlDictionary.WriteEndElement();
    xmlDictionary.WriteEndElement();
    xmlDictionary.Flush();

    memoryStream.Position = 0;

    XmlDictionaryReaderQuotas quotas =
        new XmlDictionaryReaderQuotas();

    XmlDictionaryReader xmlReader =
        XmlDictionaryReader.CreateBinaryReader
        (memoryStream, quotas);

    Message newMessage =
        Message.CreateMessage(reply.Version,
        null, xmlReader);
    newMessage.Headers.CopyHeadersFrom(reply.Headers);
    newMessage.Properties.CopyProperties
        (reply.Properties);
    reply = newMessage;
}

The first section of code uses an XmlDictionaryWriter to create the new message body we are going to be sending back. It creates our message text, which is wrapped in a Result, which in turn is wrapped in a Response. The next section creates an XmlDictionaryReader which is used in the last section to "read" the XML we just created into a new blank WCF message. We copy the headers and properties from the original, then set the "reply" reference parameter to our new message. That's it.

Well, that's not it. He have our behavior, but right now we don't have any way to bind it to our service runtime. To do that we have to have our class implement the System.ServiceModel.Description.IEndpointBehavior. There is an interface for operation behaviors as well, but we'll worry about those at another time.

Here is how we implement the IEndpointBehavior:

class MyCustomMessageFormatter : IDispatchMessageInspector,
    IEndpointBehavior
{

    public void AddBindingParameters(
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    {
        //Not implemented
    }

    public void ApplyClientBehavior(
        ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        //Not implemented
    }

    public void ApplyDispatchBehavior(
        ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.
            MessageInspectors.Add(this);
    }

    public void Validate(
        ServiceEndpoint endpoint)
    {
        //Not implemented
    }
}

Of the four methods added the one we are really only concerned with right now is the ApplyDispatchBehavior. This method is called when this endpoint behavior is applied by the runtime and it is being used here to add our message inspector (which just happens to be the same class) to the stack. You could have this endpoint behavior implemented in a separate class and have it add multiple dispatch behaviors all at once. This allows you to aggregate several dispatch behaviors into one endpoint behavior.

We're almost there now, we just need to bind our new behaior to our endpoint. First we'll do it in the hosting logic (below in bold):

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host =
            new ServiceHost(typeof(HelloWCF)))
        {
            host.Description.Endpoints[0].
                Behaviors.Add(
                new MyCustomMessageFormatter());

            host.Open();

            Console.WriteLine("Service is available.");

            Console.ReadKey();

            host.Close();
        }
    }
}

Now, when we run the service we can see the effects of our new behavior:

image

Pretty easy! In part three I'll show how to add this behavior to the service through configuration instead of the hosting code shown above.

3 comments:

D. Lambert said...

Check this out:

http://manoli.net/csharpformat/

Or, if you use Windows Live Writer:

http://gallery.live.com/liveItemDetail.aspx?li=d8835a5e-28da-4242-82eb-e1a006b083b9&l=8

James Bender said...

Thanks David! I was kinda of unhappy with the way the code snippets looked.

In fact, I'm considering moving to Subtext sometime soon. I've seen some people do some pretty sick stuff with it that I don't seem to be able to get away with on Blogspot.

Anonymous said...

I like to read your blog oftenly and I am getting more news and secret in this. Also Please visit my blog for free, and forum discussion in our online friends community. All the friends online entertainment in our online community website.

http://www.chokut.com
http://www.chokut.blogspot.com
http://www.jaya-jobsonline.blogspot.com