tags:

views:

586

answers:

4

I'm currently developing a WCF RESTful service. Within the validation of the POST data, I am throwing exceptions if the request XML does not conform to our business rules.

The goal is to send an e-mail to the appropriate staff if a request comes in that considered invalid. But, along with the incoming request headers, method and URI, I'd like to also send the XML that was posted.

I have not been able to find a way to access this data. Is WCF actually destroying the request body/data before I have a chance to access it or am I missing something?

Your help is appreciated as I'm confused as to why I can't access the request data.

A: 

In the answer to this question I provide a horrible solution to the problem. I'm still waiting for someone to suggest a better way.

Darrel Miller
Thanks for the link to your solution. It does not look horrible! In fact, you did a great job explaining the steps required for implementation.
RossG
+1  A: 

This unfortunately isn't supported- we had a similar need, and did it by calling internal members with reflection. We just use it in an error handler (so we can dump the raw request), but it works OK. I wouldn't recommend it for a system you don't own and operate though (eg, don't ship this code to a customer), since it can change at any time with a service pack or whatever.

    public static string GetRequestBody()
    {
        OperationContext oc = OperationContext.Current;

        if (oc == null)
            throw new Exception("No ambient OperationContext.");

        MessageEncoder encoder = oc.IncomingMessageProperties.Encoder;
        string contentType = encoder.ContentType;
        Match match = re.Match(contentType);

        if (!match.Success)
            throw new Exception("Failed to extract character set from request content type: " + contentType);

        string characterSet = match.Groups[1].Value;

        object bufferedMessage = operationContextType.InvokeMember("request",
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField,
            null, oc, null);

        //TypeUtility.AssertType(bufferedMessageType, bufferedMessage);

        object messageData = bufferedMessageType.InvokeMember("MessageData",
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty,
            null, bufferedMessage, null);

        //TypeUtility.AssertType(jsonBufferedMessageDataType, messageData);

        object buffer = jsonBufferedMessageDataType.InvokeMember("Buffer",
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
            null, messageData, null);

        ArraySegment<byte> arrayBuffer = (ArraySegment<byte>)buffer;

        Encoding encoding = Encoding.GetEncoding(characterSet);

        string requestMessage = encoding.GetString(arrayBuffer.Array, arrayBuffer.Offset, arrayBuffer.Count);

        return requestMessage;
    }
nitzmahone
Holy cow! That's even worse than my solution :-)
Darrel Miller
Agreed- the benefit is that we don't have to smuggle a second copy from the transport layer with a message inspector on every request. This way we can get at the original buffer directly from the service code, and only when there's a problem. Hence my original caution. :) I wish they'd just expose it off the WebOperationContext, but having taken it apart, I see why they don't (especially when you consider streamed requests of arbitrary size).
nitzmahone
Thanks for replying. I now understand why you are taking this approach. It is interesting that in order to understand why WCF works the way it does you have to dig into the implementation. It sort of defeats the purpose of trying to abstract away the complexity!
Darrel Miller
Nitzmahone, Thank you for providing this example. Accessing the Request body is something that I'm only wanting to do when handling errors. I appreciate the answer!!!
RossG
So "accept" it so I get some luv! ;)
nitzmahone
+2  A: 

So, if you declare your contract something like:

[WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)]
 int CreateItem(Stream streamOfData);

(you can use XML instead) The streamOfData should be the body of an HTTP POST. You can deserialize it using something like:

 StreamReader reader = new StreamReader(streamId);
 String res = reader.ReadToEnd();
 NameValueCollection coll = HttpUtility.ParseQueryString(res);

It's working like that for us, at least. You may want to use a different approach to get the string into an XMLDocument or something. This works for our JSON posts. Might not be the most elegant solution, but it is working.

I hope this helps.

Glenn

Ruprict
Glenn, Thanks for your response. I currently have an operation contract that is immediately deserializing the posted xml into an object. Even though I have access to the new object for processing, I would still like the raw request to be available. Just a simple string representation of the body. Thanks!
RossG
A: 

Try this,

OperationContext.Current.RequestContext.RequestMessage

sajith