Implementing a WCF Message Contract

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Windows Communication Foundation (WCF) is still the way to do SOAP integration with products like SharePoint 2010.  WCF implementations normally take two different approaches; a Document style or an API style.  Document style implementations are more flexible and often easier to extend and version.  Also, Document style or rather, Message Contract service implementations, work well between systems with a shared message assembly like, for example, a group of SharePoint 2010 applications that share common message classes.  The following paragraphs guide a developer through architecting a WCF Message Contract implementation.

The Document Approach

As stated earlier WCF services frequently employ one of two approaches:

  • API style that works with functions and function parameters.  Parameters may be primitives like ints, strings, or decimals or parameters may be separate classes.
  • Document style implemented with a Message class parameter.  This approach gives a developer complete control over the data payload.  A Message parameter allows any .NET serialization compatible format.

Implementing the Document approach using a shared assembly has the following advantages.

The Service contract never changes.  A Message class goes in and a Message class response is returned.  There is no need to regenerate a new proxy every time a new parameter is added or a new function is created.  Whenever a data payload changes; rather than changing parameters on a method the shared assembly can add a new class.  Change centers on new data rather than data and new function.  Often the result is that a developer thinks more about the data, reuses classes more frequently, and finds it easier to refactor code.

Understanding a WCF Message Contract implementation requires understanding the WCF Message class. 

Message Class

Whether a WCF service takes the API or Document approach; ultimately WCF packages data in some sort of serialized message format.  With SOAP, that means a special XML format.  Some Methods and Properties on the Message class follow.

    public abstract class Message : IDisposable
       public abstract MessageHeaders Headers { get; }
        public abstract MessageVersion Version { get; }
        public void Close();
        public MessageBuffer CreateBufferedCopy(int maxBufferSize);
        public static Message CreateMessage(MessageVersion version, MessageFault fault, string action);
        public static Message CreateMessage(MessageVersion version, string action, BodyWriter body);
        public static Message CreateMessage(MessageVersion version, string action, object body);
        public static Message CreateMessage(MessageVersion version, string action, XmlDictionaryReader body);
        public static Message CreateMessage(MessageVersion version, string action, XmlReader body);
        public T GetBody<T>();
        public T GetBody<T>(XmlObjectSerializer serializer);
        public string GetBodyAttribute(string localName, string ns);
        public XmlDictionaryReader GetReaderAtBodyContents();
        public override string ToString();
        public void WriteBody(XmlDictionaryWriter writer);
    }
 

The CreateMessage static function builds an empty WCF message.  Headers property will be discussed later in the article.  GetBody deserializes a message into a class.  Deserialization requires knowing a class before it’s deserialized.  All of the mentioned methods and properties will come together in a Factory class discussed later in this article; first, though, take a look at a WCF Message Contract. 

WCF Message Contract

The following is a WCF Message Contract Implementation.

    [ServiceContract(Namespace = "http://Test.com/services", Name="ServiceRequest" )]
    public interface IMessageWCFService
    {
        [OperationContract(Name = "ProcessRequest", Action = "http://Test.com/services/ProcessRequest", ReplyAction = "http://Test.com/services/ProcessRequestReply")]
        Message ProcessRequest(Message msg);
    }
 

Returning a Message is not required; but without returning a Message it is difficult to design a Fault Message format.  Namespaces and Action URLs must be compliant or auto mapping doesn’t work; though there will be no problem with the raw functionality.  The example above includes all the basic contract information.  Neglecting to fill in all the attributes in the example will force WCF to default these values to .NET class information.

As stated earlier the advantage to this approach is that any message can go in and any message can come out.  The approach could create chaos if measures are not taken to control how messages are created and dispatched.  This is where a Message Factory becomes important.

Factory Implementation Details

A Message Factory fulfills two important roles to avoiding message chaos.

  • It controls how messages are created so special information is injected in the headers
  • It controls how messages are deserialized so a contract implementation need not parse XML to view message contents.

The Factory Message creation implementation follows.

    public static class TestMsgWCFMessageFactory
    {
 
        public static Message CreateWCFMessage(TestMsgWCFMessageBase TestMsgMessage, WCFServiceMessageAction action = WCFServiceMessageAction.Process)
        {
            Message msg = null;
            if (action == WCFServiceMessageAction.Process)
                msg = Message.CreateMessage(MessageVersion.Soap11, "http://Test.com/services/ProcessRequest", TestMsgMessage);
            else
                msg = Message.CreateMessage(MessageVersion.Soap11, "http://Test.com/services/ProcessRequestReply", TestMsgMessage);
 
            msg.Headers.Add(MessageHeader.CreateHeader("MessageType", "TestMsg", TestMsgMessage.MessageType));
            return msg;
        }
 
 

Notice how MessageType is injected into the message header.  In the example, MessageType is an Enum converted to a string.  One quirk with serializing Enum values is that all Enum options must be serialized along with the Enum.  Messages can be designated an Action or an ActionReply.  It’s important to assign the ReplyAction when creating a return message.

The example will work with the BasicHttpBinding.  Other bindings may require other SOAP formats if, for example, more advanced SOAP security features are required.

Incoming messages also utilize the Factory approach.  The following is how the Factory decodes a message.

        public static T CreateWCFMessage<T>(Message msg)
        {
            object obj = null;
 
            //Pull the message type from the header
            //Make GetBody call according to the messagetype
            switch (msg.GetWCFMessageType())
            {
                case TestMsgMessage.Undefined:
                    obj = null;
                    break;
 
                case TestMsgMessage.MyMessageClass:
                    obj = msg.GetBody<MyMessageClass>();
                    break;
 
            }
 
            return (T)obj;
        }
 

Injecting MessageType in the header makes it easy to determine the payload.  This saves time and resources scanning the incoming XML for the correct type.  An extension function on the Message class like the following makes retrieving message header information feel like the property is part of the Message class. 

        public static TestMsgMessage GetWCFMessageType(this Message msg)
        {
            TestMsgMessage messageType = TestMsgMessage.Undefined;
 
            try
            {
                messageType = EnumConvert.TextToEnum<TestMsgMessage>
                    (msg.Headers.
                    GetHeader<string>("MessageType", "TestMsg"));
            }
            catch
            { messageType = TestMsgMessage.Undefined; }
          
            return messageType;
        }
 

Sharing a common base class also makes handling easier.  Common properties should live in this base class.

Other characteristics can be injected into the header.  For example: if a developer is storing messages in a database, including a unique Id means messages can be stored by Id without first decoding to a class.

Like all WCF message classes; DataMember should adorn the Message contract.  However, as stated earlier exclude the MessageType enum from serialization.  Instead expose a DataMember property that converts the enum to a string.  A common base class implementation may look like the following class.

    [DataContract]
    public abstract class TestMsgWCFMessageBase
    {
        public TestMsgWCFMessageBase()
        {
            var assm = Assembly.GetExecutingAssembly().GetName();
            this.InternalVersion = assm.FullName;
        }
 
        abstract public TestMsgMessage WCFMessageType { get; set; }
 
        [DataMember]
        public string MessageType
        {
            get
            { return this.WCFMessageType.ToString(); }
            set
            { this.WCFMessageType = EnumConvert.TextToEnum<TestMsgMessage>(value); }
        }
 
    }
 

Conclusion

A WCF Document style contract implementation will simplify messaging between systems sharing a common message class assembly.  Properly implementing the approach leverages Message class Headers and a custom Message Factory.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read