Access Request Body in a WCF RESTful Service
Asked Answered
R

10

21

How do I access the HTTP POST request body in a WCF REST service?

Here is the service definition:

[ServiceContract]
public interface ITestService
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "EntryPoint")]
    MyData GetData();
}

Here is the implementation:

public MyData GetData()
{
    return new MyData();
}

I though of using the following code to access the HTTP request:

IncomingWebRequestContext context = WebOperationContext.Current.IncomingRequest;

But the IncomingWebRequestContext only gives access to the headers, not the body.

Thanks.

Roseanneroseate answered 17/8, 2009 at 12:53 Comment(0)
T
11

Best way i think doesn't involve WebOperationContext

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "EntryPoint", BodyStyle = WebMessageBodyStyle.Bare)]
MyData GetData(System.IO.Stream pStream);
Thai answered 4/5, 2010 at 19:14 Comment(2)
The BodyStyle defaults to WebMessageBodyStyle.Bare anyway.Non
this work even if urltemplate has params. For me this works for raw xml post request, but solution with OperationContext.Current.RequestContext.RequestMessage.ToString() - doesn't work (result "...stream...")Wadewadell
C
9

Use

OperationContext.Current.RequestContext.RequestMessage

Catechism answered 9/9, 2010 at 12:14 Comment(1)
It'll give you message's xml and not POST bodyAriella
A
9

This code return body text. Need using System, System.Text, System.Reflection, System.ServiceModel

public string GetBody()
{
  var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
  var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
  var messageData = messageDataProperty.GetValue(requestMessage);
  var bufferProperty = messageData.GetType().GetProperty("Buffer");
  var buffer = bufferProperty.GetValue(messageData) as ArraySegment<byte>?;
  var body = Encoding.UTF8.GetString(buffer.Value.Array);
  return body;
}
Archbishop answered 12/3, 2021 at 7:52 Comment(3)
This is EXACTLY what I needed. I'd give you 20 upvotes if I could! Thanks so much!Manicotti
Any idea if this could be achieved through an annotated parameter? Like [FromBody] in WebApiIndefinite
This was working great until we moved our code to Production where there is a lot more traffic. Unfortunately, the body string contained parts from this WCF request combined with other WCF requests from other methods. Some sort of buffer overflow.Baffle
P
8

Sorry for the late answer but I thought I would add what works with UriTemplate parameters to get the request body.

[ServiceContract]
public class Service
{        
    [OperationContract]
    [WebInvoke(UriTemplate = "{param0}/{param1}", Method = "POST")]
    public Stream TestPost(string param0, string param1)
    {

        string body = Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>());

        return ...;
    }
}

body is assigned a string from the raw bytes of the message body.

Pridemore answered 8/1, 2015 at 8:34 Comment(0)
M
2

I was able to solve my issue cobbling together multiple answers on this thread. What I am trying to do is to receive a JSON payload in the body of the POST and not have anything done to it so that I can parse it as I will. This is important to us because the JSON coming in is not a single predetermined thing, but rather one of several possible. Yes, we could add a separate call for each new thing, but we are trying to allow the system to be extensible without code changes.

In previous attempts I have only been able to get this to work if the content type was 'text/plain', but then I'm sitting chewing on my tongue explaining why it can't be sent as 'application/json' when someone wants to call it.

So... from the answers on this page... the following signature:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "test/", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void TestCall();

and then getting the JSON from the body like so:

private string GetJSONFromBody()
{
    string json = "";
    string contentType = WebOperationContext.Current.IncomingRequest.ContentType;
    if (contentType.Contains("application/json"))
    {
        var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
        var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
        var messageData = messageDataProperty.GetValue(requestMessage);
        var bufferProperty = messageData.GetType().GetProperty("Buffer");
        var buffer = bufferProperty.GetValue(messageData) as ArraySegment<byte>?;
        json = Encoding.UTF8.GetString(buffer.Value.Array);
    }
    else if (contentType.Contains("text"))
    {
        json = Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>());
    }

    return json;
}

This way however someone tries to send the JSON it will work, but at last I was able to support 'application/json'. I still needed to support 'text/plain' since there are already apps calling that way.

Manicotti answered 5/8, 2021 at 14:53 Comment(0)
F
1

It seems that because WCF is designed to be transport protocol-agnostic, a service method doesn't provide access to HTTP-specific information by default. However, I just came across a nice article describing "ASP.Net Compatibility Mode" which essentially allows you to specify that your service is indeed intended to be exposed via HTTP.

Link

Adding the aspNetCompatibilityEnabled configuration to Web.config, combined with the AspNetCompatibilityRequirements attribute to the desired service operations, should do the trick. I'm about to try this myself.

Haw-Bin

Fulvi answered 11/6, 2010 at 18:41 Comment(1)
True but it takes away the self hosting capabilities of the service.Non
F
1

The above answers helped me come up with this solution. I am receiving json with name/value pairs. {"p1":7514,"p2":3412, "p3":"joe smith" ... }

[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
    [WebInvoke(Method = "POST",
        BodyStyle = WebMessageBodyStyle.Bare, 
        RequestFormat = WebMessageFormat.Json
        )]

public Stream getJsonRequest()
    {

        // Get the raw json POST content.  .Net has this in XML string..
        string JSONstring = OperationContext.Current.RequestContext.RequestMessage.ToString();

        // Parse the XML string into a XML document
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(JSONstring);

        foreach (XmlNode node in doc.DocumentElement.ChildNodes)
        {
                node.Name // has key
                node.InnerText;  // has value
Flite answered 14/6, 2012 at 23:57 Comment(2)
for xml post request it gives me "...stream...", Encoding.UTF8.GetString(OperationContext.Current.RequestContext.RequestMessage.GetBody<byte[]>()) doesn't work too. For me [Kasthor] solution works or the following: var inputStream = OperationContext.Current.RequestContext.RequestMessage.GetBody<Stream>(); var sr = new StreamReader(inputStream, Encoding.UTF8); var str = sr.ReadToEnd();Wadewadell
Instead of iterating over the nodes, you could create a XmlNodeReader from the document, then feed that into DataContractJsonSerializer.ReadObject(), which accepts an XmlReader. This way, the values that are not strings, such as numbers and booleans, are of the correct type!Time
I
0

My apologies for the previous answer, I stupidly assumed that I had just cast WebOperationContext to get at the OperationContext, unfortunately the real answer is much more ugly.

Let me preface this with, there must be a better way!

First I created my own context object, that could be attached to the existing OperationContext object.

public class TMRequestContext : IExtension<OperationContext>  {

    private OperationContext _Owner;

        public void Attach(OperationContext owner) {
            _Owner = owner;
        }

     public void Detach(OperationContext owner) {
            _Owner = null;
        }

    public static TMRequestContext Current {
            get {
                if (OperationContext.Current != null) {
                    return OperationContext.Current.Extensions.Find<TMRequestContext>();
                } else {
                    return null;
                }
            }
        }
}

In order to be able to access this new context object, you need to add it as an extension to the current one. I did that by creating a message inspector class.

public class TMMessageInspector : IDispatchMessageInspector {

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) {

            OperationContext.Current.Extensions.Add(new TMRequestContext());
            return null;
        }
}

In order for the message inspector to work you need to create a new "behaviour". I did this using the following code.

    public class TMServerBehavior : IServiceBehavior {

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
            //Do nothing
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) {

            foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers) {

                foreach (EndpointDispatcher epDisp in chDisp.Endpoints) {
                    epDisp.DispatchRuntime.MessageInspectors.Add(new TMMessageInspector());
                }
            }

        }
}

The behaviour you should be able to add in the config file, although I did it by creating a new host and adding the behaviour object manually in the OnOpening method. I ended up using these class for much more than just accessing the OperationContext object. I used them for logging and overriding the error handling and access to the http request object,etc. So, it is not quite as ridiculous solution as it seems. Almost, but not quite!

I really don't remember why I could not just access OperationContext.Current directly. I have a faint recollection that it was always empty and this nasty process was the only way I could get an instance that actually contained valid data.

Inanition answered 17/8, 2009 at 14:29 Comment(1)
Hi Darrel, I tried your suggestion and ran into a few problems. When I used your exact code, I got this error (at compile time): Cannot convert type 'System.ServiceModel.Web.WebOperationContext' to 'System.ServiceModel.OperationContext' And when I changed it to this code: string body = OperationContext.Current.RequestContext.RequestMessage.ToString(); The body was an empty string at runtime. Any ideas? Thanks, UriRoseanneroseate
D
0

Here is what I did:

using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;

namespace YourSpaceName
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class YourClassName
    {
        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "YourMethodName({id})", BodyStyle = WebMessageBodyStyle.Bare)]
        public Stream YourMethodName(Stream input, string id)
        {
            WebOperationContext ctx = WebOperationContext.Current;
            ctx.OutgoingResponse.Headers.Add("Content-Type", "application/json");

            string response = $@"{{""status"": ""failure"", ""message"": ""Please specify the Id of the vehicle requisition to retrieve."", ""d"":null}}";
            try
            {
                string response = (new StreamReader(input)).ReadToEnd();
            }
            catch (Exception ecp)
            {
                response = $@"{{""status"": ""failure"", ""message"": ""{ecp.Message}"", ""d"":null}}";
            }

            return new MemoryStream(Encoding.UTF8.GetBytes(response));
        }
    }
}

This code simply reads the input and writes it out. the body of the POST request is automatically assigned to input irrespect of the variable name. As you can see, you can still have variables in your your UriTemplate.

Dorey answered 22/8, 2019 at 13:1 Comment(0)
I
0

The above answer is nearly there except for a use case where the buffer doesn't quite fill up with the request body and accessing the internal array buffer gives you old data.

public string GetBody()
{
  var requestMessage = OperationContext.Current.RequestContext.RequestMessage;
  var messageDataProperty = requestMessage.GetType().GetProperty("MessageData", (BindingFlags)0x1FFFFFF);
  var messageData = messageDataProperty.GetValue(requestMessage);
  var bufferProperty = messageData.GetType().GetProperty("Buffer");
  var buffer = bufferProperty.GetValue(messageData) as IEnumerable<byte>;
  var body = Encoding.UTF8.GetString(buffer.ToArray());
  return body;
}

I attempted to perform the edit, but it's still in review and didn't want it to get lost.

Inulin answered 22/12, 2023 at 16:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.