Message.ToString() returning "…stream…"
A month ago, I wrote an post on how to send an XmlDocument trough Windows Communication Foundation. At the end of the post, I noted that the ToString method on the Message object might return "…stream…" instead of the SOAP content.
A recent development needed to have the full message content in a string, and we hit the "…stream…" issue again. The development was a message inspector which was to be used on various BizTalk WCF Adapters. The inspector had to work on service side and on client side, implementing AfterReceiveRequest of IDispatchMessageInspector and BeforeSendRequest of IClientMessageInspector. Strangely, Message.ToString returned the full message on the server side adapter, but returned "…stream…" in client side adapter.
Searching on that matter on Google didn’t provided me with lots of results, but I found a post with a solution to that particular issue: "Accessing the message inside of your WCF service operations". So, it seems that this is due to the fact that the Message object is streamed (yeah I know it’s quite obvious…) on the client side.
So, to get the message’s content as a string, here is what is needed:
StringWriter stringWriter = new StringWriter(); XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter); message.WriteMessage(xmlTextWriter); xmlTextWriter.Flush(); xmlTextWriter.Close(); String messageContent = stringWriter.ToString();
However, there is an issue with that code in a message inspector. Using it like this will throw some exception about the message already been written. According to the documentation, a Message object can only be read or written once:
As the body of a Message object is a stream, it can only be read or written once. This is enforced by maintaining the current state of the Message object. A Message object can be read/written/copied when in the Created state. Other states are Read, Written and Copied, which means that the respective action has been performed already once.
So, when calling Message.WriteMessage to write the message in the XmlTextWriter, the state of the message changes from "Created" to "Written". This operation is irreversible, and the message is pretty useless afterward.
To avoid this, we can make use of the MessageBuffer class. This can be used to make a copy of a message in a buffer, then you can use the object created to create as many Messages as you want. Here is how to use it:
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue); Message message = buffer.CreateMessage();
After that, the message variable holds a reference to a copy of the initial request, and the buffer object can be used to produce copies of the initial Message.
There is one last trick. When creating a MessageBuffer out of the request, we actually read the request Message object, changing its state to Read and making it unusable afterward (in this case, the application!). Notice that in IDispatchMessageInspector and IClientMessageInspector methods, Messages parameters are always given using the ref keyword, meaning that the reference can be modified.
Solution to that last issue is to make another copy of the initial request and assign it to the variable. Here’s how to do it:
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue); Message message = buffer.CreateMessage(); //Assign a copy to the ref received request = buffer.CreateMessage();
Now the request variable hold a reference to a fresh Message, and the message variable holds a reference to another copy of the Message object that you can mess with in your inspector.
Calling One Way WCF Service with BizTalk WCF Adapter, Part 2
As promised, here is the follow up of my previous post: Calling One Way WCF Service with BizTalk WCF Adapter, Part 1.
Let’s do a qui recap: we want to create a WCF proxy that will call a one way web service and return an empty message to BizTalk so the messaging process doesn’t timeout waiting for an answer.
The Interface
The first step is to create a service interface that is able to receive any message. This is done by
[ServiceContract(Namespace = "http://pvle.be/WcfOneWayProxy", Name = "OneWayProxy")] public interface IOneWayProxy { [OperationContract(Action = "*", ReplyAction = "*")] Message ProcessOneWayMessage(Message message); }
The operation has to be defined as a request/reply operation, as this is what BizTalk expects. Note the Action property of the OperationContractAttribute, its value is set to "*" meaning that all received messages will be dispatched to that operation.
The Implementation
Now that the interface is ready, let’s implement it.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ValidateMustUnderstand = false)] public class OneWayProxy : IOneWayProxy { private const String ConfigurationName = "ServiceEndpoint"; private static ChannelFactory<IOutputChannel> Factory; static OneWayProxy() { BasicHttpBinding binding = new BasicHttpBinding(); OneWayProxy.Factory = new ChannelFactory<IOutputChannel>(OneWayProxy.ConfigurationName); OneWayProxy.Factory.Open(); } #region IOneWayProxy Members public Message ProcessOneWayMessage(Message message) { IOutputChannel channel = OneWayProxy.Factory.CreateChannel(); try { //Send the one way message channel.Open(); channel.Send(message); channel.Close(); } catch (Exception) { //Do something, log the exception, whatever return Message.CreateMessage(message.Version, "http://pvle.be/EmptyReplyAction"); } //Whatever happens, send a response as this was "Fire and Forget" for the caller //Empty response send back to BizTalk so messaging process can terminate gracefully return Message.CreateMessage(message.Version, "http://pvle.be/EmptyReplyAction"); } #endregion }
A couple of remarks here:
- You need to have a valid client endpoint configuration in you App.config/Web.config that has the name defined in the ConfigurationName const.
- To create the message that is to be returned, we use the received message’s Version property, ensuring that there is no version mismatch.
Now that we have our service implementation, we can host it. I hosted it in IIS, as this is probably the best way to host WCF services and as this proxy should be running all the time.
BizTalk WCF Adapter Configuration
The last step is to make sure that your one way WCF adapter is calling the proxy. This is done by changing the address in the WCF Adapter configuration in your BizTalk project.
Once this is set (the SOAP Action header remains the same), you are ready to run you orchestration. You will see that the messaging process will no longer timeout, as our home made proxy will send an empty response message.
Improvements
Let’s face it, this implementation is fine for a proof of concept, but it’s hardly enough for a real application as only one endpoint is defined in the configuration.
If you have multiple one way endpoints and you want to reuse that proxy, you’ll probably need to have some lookup table in the proxy code to define the destination of the call, based on the To property of the Message object. This way, you can use a single one way proxy for all the actions needed in your orchestrations, as long as you are not using the same action for more than one endpoint.
Calling One Way WCF Service with BizTalk WCF Adapter, Part 1
If you ever tried to call a One Way web service operation with BizTalk, you might have read that BizTalk’s WCF Adapters don’t support One Way calls. The orchestration calls the service as expected and carries on running, but some Messaging process still hangs in the BizTalk Server Administration Console. Simply put: BizTalk 2006 R2 WCF Adapters don’t support "Fire and Forget" communication pattern (message is sent and reception is acknowledged by the listening service, but no response is sent back).
In the "Using the Windows Communication Foundation (WCF) Adapters in BizTalk Server" paper by Aaron Skonnard, it is clearly stated at page 22:
An important consideration is that the WCF send adapter is only compatible with request-reply operations even when used on one-way send ports. WCF service operations can return void but they shouldn’t be marked with IsOneWay=true if you want to call them from a WCF send port.
This means that if an orchestration calls a service operation marked as One Way (hence not returning anything), the orchestration will continue and terminate fine, but the port that called the service will hang there for a response.
With default settings, the process will try to call the service three more times before giving up raising some CommunicationException:
System.ServiceModel.CommunicationException: The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error.
This is not very nice.
This issues has been discussed many times on blogs. One of the solution proposed by Sonu Arora is to remove the IsOneWay operation attribute at the service, so the service sends an empty SOAP message as a response. This is the same solution that what is advised by Aaron. However, this means that you have some control on the service itself, which might not be the case. On top of this, if you have other client calling that service, should they all change because of BizTalk? I don’t think so.
An other solution proposed by Josh Twist, who’s encountering the same issue but the other way round, is to either modify the orchestration (hence making it aware of the post used, which is not good), or to build an intermediary service that will support the One Way operation (whichever direction you choose) and adapt to make sure BizTalk gets what it needs.
This is what I did and I will explain it is part 2 of this post, as I’m running out of time today
