How to get hold of Content that is already read

I have a class that inherits from ApiController. It has a Put-method like this:

[PUT("user/{UserId}")]
public HttpResponseMessage Put(string userId, PaymentRequest paymentRequest)
{
    // Calling business logic and so forth here
    // Return proper HttpResponseMessage here
}

The method works fine as it is above. Now I need to validate the signature of the method call, but here I run into a problem. The signature is essentially a combination of method + url + body. The method I can get by calling Request.Method and the url I can get by calling Request.RequestUri.ToString(), but I can't get hold of the body as it was before it was automatically deserialized into a PaymentRequest object by the asp.net MVC4 framework.

My first try: As I have now understood Request.Content.ReadAsStringAsync().Result returns nothing. This is because the content can only be read once.

My second try: I tried to serialize it back to a JSON string.

var serializer = new JavaScriptSerializer();
var paymentRequestAsJson = serializer.Serialize(paymentRequest);

The problem with this is that the formatting turns out slightly different than the body part of the signature. It has the same data, but some more spaces.

I can't change what the caller of my Put-method does, as this is a third party component. What should I do?


You could read from the underlying request:

using (var stream = new MemoryStream())
{
    var context = (HttpContextBase)Request.Properties["MS_HttpContext"];
    context.Request.InputStream.Seek(0, SeekOrigin.Begin);
    context.Request.InputStream.CopyTo(stream);
    string requestBody = Encoding.UTF8.GetString(stream.ToArray());
}

Don't include the body parameter in the signature and that will allow you to buffer the content and read the content as many times as you like.

[PUT("user/{UserId}")]
public HttpResponseMessage Put(string userId)
{
    Request.Content.LoadIntoBufferAsync().Wait();
    var paymentRequest = Request.Content.ReadAsAsync<PaymentRequest>().Result;
    var requestBody = Request.Content.ReadAsStringAsync().Result;
    // Calling business logic and so forth here
    // Return proper HttpResponseMessage here
}

A very delayed response, but recently I have had the same challenge to overcome.

I have approached that a bit different without having to get the data from the httpContext (can be quite costly for high volume transaction web application).

I created a simple interface and made each Controller implementing it:

public interface IBaseControllerData
{
    object Entity { get; set; }
}

I have then set the Controller's Entity property to the Json payload for each post and put action. Finally, I retrieved the Entity data within the ActionFilterAttribute.OnActionExecuted overridden method and serialised it to Json before injecting into MongoDB:

object entity = ((IBaseControllerData)actionExecutedContext.ActionContext.ControllerContext.Controller).Entity;
                    requestBody = Newtonsoft.Json.JsonConvert.SerializeObject(entity);

Hope that helps !

Cheers


My answer is a variation of Darin Dimitrov's above... variant answers should be expected with all the variants of C# (ASP.NET, Core, MVC, etc.)... and perhaps the year of Visual Studio (2017, 2019, etc.)... and if Visual Basic is used rather than C#....

Similar to Anders Arpi's comment, I was not able to use Darin's answer as provided (though obviously my answer is a direct derivation from his). In my case Request.Properties was not an option, but Request.InputStream was. I suspect this strongly depends on the project's type as mentioned in the first paragraph. My solution worked for me in VS2017.

Similar answers are also on the page: How to get raw request body in ASP.NET? However, these did not work for me. But, they did provide important clues such as 'The request object is not populated in the BeginRequest event. You need to access this object later in the event life cycle'...

Skip this next paragraph if you are experienced in HTTPRequests construction/analysis.

Though it's incredibly obvious in retrospect, Request Body content only appears for certain HTTPRequests. If you are just doing a GET (e.g. clicking on a navigation tab which takes you to a new page), you generally don't have a Request Body. So when testing solutions, make sure that you are looking at a HttpRequest that should contain a RequestBody. In my case, any Submit button that's actually receiving data from the UI will be a Post that contains a RequestBody, and therefore in existence (and of length greater than 0). If you don't have Fiddler or BurpSuite, or are working through Shibboleth (which adds complexity to Fiddler/BurpSuite testing), you can generally see the HTTPRequest in the Firefox developer tool (via F12).... where what I'm calling the Request Body would be called Params and/or the Request Payload.

N.B. Application_AuthenticateRequest is within Global.asax.cs.

(in addition to other using statements)
using System.IO;
using System.Text;
using System.Web;  

protected void Application_AuthenticateRequest(object sender, EventArgs e){

HttpApplication app = (HttpApplication)sender;
// Do a quick check to make sure that we are not on local. 
// I've had some odd errors on local, though I suspect general implementation will not have this issue.
// So if we're actually on a 'real' (a.k.a. non-local) website, I do the following.

      using (var stream = new MemoryStream())
                {
                    app.Request.InputStream.Seek(0, SeekOrigin.Begin);
                    app.Request.InputStream.CopyTo(stream);
                    string requestBody = Encoding.UTF8.GetString(stream.ToArray());
                    app.Request.InputStream.Seek(0, SeekOrigin.Begin); 
                    if (requestBody.Length > 0) Response.Write("requestBody: " + requestBody + "<br>");
                }

}

Please note that I am using the recommendation of 'Reinstate Monica Cellio' to reset the InputStream after copying.