January 2008 - Posts

WCF - Handling Generic Messages
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 | 12 comment(s)
Filed under: , ,
Reminder - WPF Training with Ian Griffiths
Wednesday, January 16, 2008 9:26 PM

Just a reminder, readify are hosting another WPF industrial strength training workshop with Ian Griffiths from Pluralsight as your host. If you want to get up speed with WPF and learn to make it really work for you, this is a great course.

Some of the items he will cover include;

  • WPF Framework Architecture
  • Using Controls – a new approach to UI components
  • Layout
  • Data Binding
  • Styling and Templates
  • Graphics
  • Resource Management
  • Building Custom Controls
  • Text, Typography, and Documents
  • Printing
  • Building Connected WPF Applications

More info can be found here.

by Glav | with no comments
Filed under: , ,
ASP.NET Podcast Show #107 - Paul and Wally talk about 2007 & look forward to 2008
Tuesday, January 8, 2008 4:43 PM

Here is the annual year end podcast that Wally and myself try and put out as a kind of summary of the year and to also highlight some of the things that may play a big part in the future.

Subscribe <-- What every good developer should do!

Original Url: http://aspnetpodcast.com/CS11/blogs/asp.net_podcast/archive/2008/01/05/asp-net-podcast-show-107-paul-and-wally-talk-about-2007-look-forward-to-2008.aspx

Download

Show Notes:

  • Paul looks back on 2007
  • Paul looks forward into 2008
  • Wally just rambles................
by Glav | with no comments
MVP Again - woohoo!
Wednesday, January 2, 2008 12:51 PM

Like probably many MVP's who get re-assessed this time of year, this post is an announcement of me being re-awarded MVP status for another year.

Big thanks to everyone who helped me get it but special thanks to Microsoft (obviously), my company, readify and also a friend and colleague who actually helps me more than he probably realises, Wally McClure (yes, that's Sir Wally for the un-initiated).

More Posts

This Blog

Syndication