Reputation: 51
I am implementing WCF service that exposes a method whose [OperationContract]
is [XmlSerializerFormat]
. I sometimes get request whose body is not valid XML. In such cases I want to log the original body, so I can know why it didn't constitute valid XML. However, I can't get it from the Message object, see my attempts (by implementing IDispatchMessageInspector interface):
public object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
request.ToString(); // "... Error reading body: System.Xml.XmlException: The data at the root level is invalid. Line 1, position 1. ..."
request.WriteBody(...); // Serialization Exception, also in WriteMessage and other Write* methods
request.GetReaderAtBodyContents(...); // Same
HttpRequestMessageProperty httpRequest = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]; // no body in httpRequest
}
When looking in watch, request.messageData appears to contain the body - but that's a private member.
How can I get the message buffer without trying to deserialize it?
Upvotes: 5
Views: 8053
Reputation: 172200
For the opposite direction (I am writing a WCF client and the server returns invalid XML), I was able to extract the raw reply message in IClientMessageInspector.AfterReceiveReply
by
MessageData
property of reply
via Reflection, and thenBuffer
property, which is an ArraySegment<byte>
.Something similar might be available for the request message on the server side; so it might be worth examining the request
variable in the debugger.
I'm aware that this is not exactly what you are asking for (since you are on the server side, not on the client side), and I'm also aware that using reflection is error-prone and ugly. But since the correct solution is prohibitively complex (see baur's answer for details) and this "raw dump" is usually only required for debugging, I'll share my code anyways, in case it is helpful to someone in the future. It works for me on .NET Framework 4.8.
public void AfterReceiveReply(ref Message reply, object correlationState)
{
object messageData = reply.GetType()
.GetProperty("MessageData",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(reply, null);
var buffer = (ArraySegment<byte>)messageData.GetType()
.GetProperty("Buffer")
.GetValue(messageData, null);
byte[] rawData =
buffer.Array.Skip(buffer.Offset).Take(buffer.Count).ToArray();
// ... do something with rawData
}
And here's the full code of the EndpointBehavior:
public class WcfLogger : IEndpointBehavior
{
public byte[] RawLastResponseBytes { get; private set; }
// We don't need these IEndpointBehavior methods
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new MessageCaptureInspector(this));
}
internal class MessageCaptureInspector : IClientMessageInspector
{
private WcfLogger logger;
public MessageCaptureInspector(WcfLogger logger)
{
this.logger = logger;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Ugly reflection magic. We need this for the case where
// the reply is not valid XML, and, thus, reply.ToString()
// only contains an error message.
object messageData = reply.GetType()
.GetProperty("MessageData",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(reply, null);
var buffer = (ArraySegment<byte>)messageData.GetType()
.GetProperty("Buffer")
.GetValue(messageData, null);
logger.RawLastResponseBytes =
buffer.Array.Skip(buffer.Offset).Take(buffer.Count).ToArray();
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
}
Usage:
var logger = new WcfLogger();
myWcfClient.Endpoint.EndpointBehaviors.Add(logger);
try
{
// ... call WCF method that returns invalid XML
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
File.SaveAllBytes(@"C:\temp\raw_response.bin", logger.RawLastResponseBytes);
// Use the exception message and examine raw_response.bin with
// a hex editor to find the problem.
Upvotes: 0
Reputation: 15569
UPDATE
Some others that have run into this issue appear to have created a Customer Message Encoder.
A message encoding binding element serializes an outgoing Message and passes it to the transport, or receives the serialized form of a message from the transport and passes it to the protocol layer if present, or to the application, if not present.
Upvotes: -1
Reputation: 2115
Yes, you need custom MessageEncoder, unlike message inspectors (IDispatchMessageInspector / IClientMessageInspector) it sees original byte content including any malformed XML data.
However it's not trivial how to implement this approach. You have to wrap a standard textMessageEncoding as custom binding element and adjust config file to use that custom binding.
Also you can see as example how I did it in my project - wrapping textMessageEncoding, logging encoder, custom binding element and config.
Upvotes: 2