WCF + REST: Where is the request data?

2020-01-28 08:30发布

问题:

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.

回答1:

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;
}


回答2:

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



回答3:

Try this,

OperationContext.Current.RequestContext.RequestMessage


回答4:

Here's how you do it without reflection:

using (var reader = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents ()) {
    if (reader.Read ())
        return new string (Encoding.ASCII.GetChars (reader.ReadContentAsBase64 ()));
                return result;
    }
}

If the reader is a HttpStreamXmlDictionaryReader (as it was in my case), the class's implementation of the method ReadContentAsBase64(byte[] buffer, int index, int count) simply passes these parameters to the Stream.Read method.

Once I have the byte[] I convert the bytes to a string via ASCII encoding. For a proper implementation, you could use the content type & encoding from the message's headers to do per HTTP spec.



回答5:

You could arrest the HttpApplication.Request.InputStream in a custom HttpModule of the WCF Service, read the stream and again set its position to 0 in the custom HttpModule's event handler. Then store it in session and access it further in the actual OperationContract.

For example:

public class CustomModule : IHttpModule
{
    public void Dispose()
    {

    }

    public void Init(HttpApplication context)
    {
        context.AcquireRequestState +=context_AcquireRequestState;
    }

    void context_AcquireRequestState(object sender, EventArgs e)
    {
        HttpApplication application = sender as HttpApplication;
        Stream str = application.Request.InputStream;
        StreamReader sr = new StreamReader(str);
        string req = sr.ReadToEnd();
        str.Position = 0;
        application.Session["CurrentRequest"] = req;
    }
 }


标签: wcf rest request