WCF - Handling Generic Messages

Published Saturday, January 19, 2008 5:22 PM

I had a requirement for a client where we were upgrading a web site that had a business web service exposed, and we had to create a new service that was identical in operation to the old (from a consumer perspective).

The situation we had though, was somewhat of a reversal on what I normally do (and I suspect what most people do). Typically, I would create a service, and the consumer would query its interface, and generate the appropriate client code to consume it. In this scenario, we had a service and consumer, and we had to mimic the old service so that the consumer could continue "consuming" as if nothing had changed. Furthermore, because the consumer was using a much older technology set, the messages being sent by that consumer were not quite compliant with the latest standards. So I had to create a service that essentially allowed a non compliant consumer to operate unchanged.

Initially, I tried generating interfaces using the SVCUTIL tool in WCF from the existing WSDL and then exposed those interfaces using a basic http binding. This sort of worked. Firstly, the generated interface had an incorrect soap action attribute (as I detailed in a previous post here). This was relatively easy to overcome with a little bit of code.

Once this was done however, calls from the consumer would get to the service ok, but the object being passed in by the consumer would always come through as NULL. It seemed that the serialised data from the consumer, was not getting deserialised correctly on the service end.

The original service definition looked something like:

public void SubmitOrder(Order orderRequest);

The orderRequest object was always coming through as NULL.

As with a lot of projects, time was short and the pressure was on.

I didn't have to do much processing of the orderRequest, simply get its contents as one big XML blob and pass it downstream to a legacy processing component so I didn't really have to do much with the data. Get the object, serialise it as XML, send it on. It was quite frustrating, and attempting to find out what exactly the differences were in terms of serialisation seemed not only a painful task, but a rather time consuming one.

So, I decided to implement a generic WCF service that simply accepted whatever data was sent to an endpoint, and pass it on to the legacy component. The more I thought about it, the better an idea it seemed because:

  • Deserialising the incoming data into an object, and the serialising it again to pass it downstream was an excessive waste of time and cycles.
  • The business service exposed was only one method end the expectations was this is how it would remain for sometime.
  • The level of validation on the data being passed downstream was quite high so even though it could accept almost anything, only the correctly structured and formatted data would be accepted and processed.

So with that in mind, I created a generic service that accepted any service calls to a particular endpoint, extracted out the body of the SOAP message, and passed it on downstream.

The code looked like this:

Interface:

[MatchAllEndpoints]
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface ICatchAll
{
    [OperationContract(IsOneWay = false, Action = "*", ReplyAction = "*")]
    Message ProcessMessage(Message message);
}

Notice the Action parameter of the OperationContract attribute specifies '*' to indicate that any SoapAction is acceptible for this method.

Secondly, notice the [MatchAllEndpoints] attribute. Lets look at its code:

class MatchAllEndpoints : Attribute, IContractBehavior
{
    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {     }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {     }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.EndpointDispatcher.AddressFilter = new System.ServiceModel.Dispatcher.MatchAllMessageFilter();
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

}

The only method we really implement is the ApplyDispatchBehavior. In this we set the AddressFilter to a MatchAllMessageFilter which determines the criteria upon which we want to deal with messages and ensures we get all the messages directed at a specific endpoint. This means it is irrelevant what service is actually called. If its directed at the endpoint exposed by this service, then we will get it.

Next comes the service itself:

public class CatchAllService : ICatchAll
{
  public Message ProcessMessage(Message message)
  {
    // Create a buffered copy of the message in memory so we can read it AND take a another copy
    MessageBuffer buffer = message.CreateBufferedCopy(8192);

    // Get a copy of the original message. This will be used to read and extract the body.
    Message msgCopy = buffer.CreateMessage();

    // Take another copy of the same message. This will be used to return to the service. Returning an identical message forms part of
    // the acknowledgement in this case.

    Message returnMsg = buffer.CreateMessage();

    // Use the msgCopy to get an XML Dictionary reader to extract the body contents. Once this message has been read and consumed,
    // it can no longer be consumed again (this is why we took a second copy above

    System.Xml.XmlDictionaryReader xrdr = msgCopy.GetReaderAtBodyContents();
    string bodyData = xrdr.ReadOuterXml();

    // Send the body of the message, which is the order, to be processed.
    DomainObject.ProcessOrder(bodyData);

    // Return the second copy of the message we took previously.
    return returnMsg;
  }
}

This little puppy took me a while to get right. All I wanted to do was get the body of the SOAP message as a big string. There are a myriad of methods on the Message object which seem to suggest you can use them to do this (such as Message.GetBody<T>() ) but it took sometime to experiment and get the right one.

Firstly, I grab a copy of the message in a MessageBuffer. If I dont do this, once I have read the message, I can do anything with it again. That is, once consumed, its a done deal. I needed the message again to return to the client as they compare what they sent with what is returned to act as an acknowledgement (I didn't come up with this method, I just had to make it work the same...)

So from the buffered message, I create a new message which I can read and consume. The second one is what we return to the consumer/client.

Next I construct an XmlDictionaryReader from the message itself by calling GetReaderAtBodyContents on the message object. From this I can read the OuterXml to get the body as a string and pass it on.

Lastly I return a copy of the message.

Finally, the configuration file to expose this service is pretty basic:

<services>
  <service name="GlavsStuff.CatchAllService">
    <endpoint binding="basicHttpBinding" name="GlavsStuff.CatchAllServiceEndpoint" contract="GlavsStuff.ICatchAll" />
  </service>
</services>

Nothing special here.

And thats it. A generic message handler that can accept pretty much any service call at that endpoint.

One more thing. This service was hosted within Internet Information Server so there was an accompanying .SVC file. If your still reading this, then I figure listing the contents of that file is probably unecessary.

Note: Parts of this were taken from the Generic router example on the netfx3 site. I'd put a direct link here but things have moved around a bit and i cannot directly find it again.

This is currently working very well. Now I know about the inability to expose a decent set of metadata from this endpoint, but the client was not concerned about this. As long as the service call worked. In the end, I took a copy of the original WSDL and XSD documents, and placed them within the site so that they could query against that (with some minor modifications).

I hope this has been helpful.

by Glav
Filed under: , ,

Comments

# Rick Strahl said on Saturday, January 19, 2008 8:14 AM

Glav - great idea and thanks for posting this here. I think this will come in handy.

I've had a few similar situations like this - I handled those scenarios with an HttpHandler at the time and manually deserializing messages (or parsing the raw HTML), but this is much cleaner because you can hopefully take advantage of the WCF infrastructure.

I suppose you're feeding the client the original WSDL though right?

# Glav said on Saturday, January 19, 2008 9:41 AM

Hi rick,

yep, thats right re: the original WSDL. Since i already had a copy of that, I just expose that file itself (again with a little manual modification to ensure everything is pointing at the right spot).

Glad you like it.

# dbam987 said on Wednesday, April 23, 2008 5:47 PM

I have a slight "gotchya" that I'm still trying to work around. What if the incoming message's body is an XML document that has directives in it, such as <?test?>? Creating the buffered copy causes an exception because it assumes the body is valid XML. Any way around this one?

# dbam987 said on Wednesday, April 23, 2008 5:58 PM

My mistake. I meant to say that the XML document that is in the message body part contains XML directives which cause the parsing of that section to fail because its not expecting them there. Is there a way around that? Or will the payload have to be encoded before hand?

# Glav said on Thursday, April 24, 2008 12:58 AM

Hi dbam987,

As soon as I get time I'll have a bit of a play with it. Encoding the payload before sending is kinda defeating the purpose as it forces the client to change and this approach is ideally to NOT have the client change at all.

again, as soon as I get some time, I will have a look. At a guess, you'll probably have to use one of the GetBody derivatives and some funky casting :-)

# dbam987 said on Thursday, April 24, 2008 2:35 PM

Hi Glav, thanks in advance for taking a look. I think I'm on to something to resolve this. What I'm thinking is to write a custom message encoder that will encode the incoming data into what I can use. See the following link for what I'm following: msdn2.microsoft.com/.../ms751486.aspx

However, I can't shake the feeling that there is an easier way...

# Glav said on Thursday, May 8, 2008 7:38 AM

Hi again dbam987,

I had a bit of time to try re-writing some of this code and cleaned it up a little. In my implementation, I haven't had any problems with the XML directives as they all get escaped/encoded so it all goes through. I am able to accept a document with XML directives and write out to disk without issue.

If you mail me directly at glav@aspalliance.comREMOVETHISBIT then I'll send it your way to test it.

# Benjy said on Saturday, June 6, 2009 7:22 AM

Interesting post. Did you ever find out why the object was coming through as NULL? Did it have something to do with the"Wrapping"? I had a similar problem when I was migrating a regular ASP.NET service to one hosted in Biztalk. The WSDL seemed fine and the calls came through but I had accidentally left the Biztalk option to "wrap" the messages (because BTS deals with multi-part message so wrapping is the default) and so i was getting nulls. Changing it to unwrapped worked fine.

Cheers

Benjy

# Mohamed Abdeen said on Thursday, August 13, 2009 8:40 AM

hello

thanks for your post, but can you tell me about the DomainObject and go more through the code of the ProcessMessage

# Tom McDonald said on Tuesday, March 8, 2011 8:09 AM

Here is some code that you can put in your DomainObject.ProcessData method to deserialize the testRequest.

// Create an instance of the XmlSerializer specifying type and namespace.

           XmlSerializer serializer = new XmlSerializer(typeof(testRequest));

           System.Xml.XmlReader reader = System.Xml.XmlReader.Create(new System.IO.StringReader(xmlString));

           // Declare an object variable of the type to be deserialized.

           testRequest tr;

           // Use the Deserialize method to restore the object's state.

           tr = (testRequest)serializer.Deserialize(reader);

Leave a Comment

(required) 
(required) 
(optional)
(required) 

This Blog

Syndication