12

I am using ActionFilterAttribute to get the request before hitting the controller as below :

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

The above method is working for small request but for a large json it is giving me this error :

Either BinaryRead, Form, Files, or InputStream was accessed before the internal storage was filled by the caller of HttpRequest.GetBufferedInputStream.

And the input stream gives this error

context.Request.InputStream threw an exception of type System.InvalidOperationException System.IO.Stream {System.InvalidOperationException}

As I found in my research that it is an issue with the timeout but I am unable to change the timeout in the code. I tried changing the values in the web.config file maxRequestLength="102400000" and maxAllowedContentLength="209715100" but still I am facing the same error.
If I read the GetBufferedInputStream but still same issue it is reading just a part of the buffer, not the entire stream.

I also tried the below :

 Stream InStream;
 int Len;
 InStream = HttpContext.Current.Request.InputStream;
 Len = System.Convert.ToInt32(InStream.Length);
 byte[] ByteArray = new byte[Len + 1];
 InStream.Seek(0, SeekOrigin.Begin);
 InStream.Read(ByteArray, 0, Len);
 var jsonParam = System.Text.Encoding.UTF8.GetString(ByteArray); 

Note that if I set the content type application/xml or application/x-www-form-urlencoded it works, but if I set it to application/json it gives me this error!!

Please advise!

User7291
  • 1,095
  • 3
  • 29
  • 71
  • Did you try to use [DelegatingHandler](https://stackoverflow.com/questions/23660340/need-to-log-asp-net-webapi-2-request-and-response-body-to-a-database) to get a request body? What size of the large json? I tried your first variant with json with 195077 characters and it works OK – AlbertK Aug 31 '18 at 15:37
  • 3
    Well, there's only one input stream, and you're reading it, so ASP.NET cannot do its work. The stream is in general forward/readonly, you can't seek and read it multiple times. Why do you want to read that stream? ASP.NET is supposed to read it and then you use ASP.NET features to do what you have to do – Simon Mourier Sep 02 '18 at 07:12
  • @SimonMourier because the request is encrypted so i need to decrypt it and then pass it as json to the controller – User7291 Sep 07 '18 at 06:27
  • @Albert are you sending the json with a content type `application/json` ? because if i send it `application/x-www-form-urlencoded` it works – User7291 Sep 07 '18 at 07:04
  • Do you have a model defined for the action? – Aamir Masood Sep 07 '18 at 10:25
  • @Aamir yes i have a model for the controller – User7291 Sep 07 '18 at 14:11
  • @User7291 did you try my answer below? – Aamir Masood Sep 08 '18 at 15:03

3 Answers3

3

There are couple of points:

First, if you try and read 0 bytes from a stream, then it will throw a System.InvalidOperationException exception. So, I will change your code like below and add a check for ContentLength > 0.

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

Also, I once experienced the same issue and increasing the maxRequestLength in web.config seems to have resolved the issue. This link further provides more info here

Gauravsa
  • 6,330
  • 2
  • 21
  • 30
2

This is how I do it inside my Model binder but I'm not sure how it will work with your Action filter. I checked online and there's conflicting information; Some say you cannot read the input stream since it's not seekable and ASP.NET would need to read it to bind the model. Some say it's indeed seekable, and use the method you shared above. So the only way to figure out what would really work is to test.

I hope my code sample helps you figure it out.

object request = null;
if (actionContext.Request.Method == HttpMethod.Post && "application/json".Equals(actionContext.Request.Content.Headers.ContentType.MediaType))
{
    var jsonContentTask = actionContext.Request.Content.ReadAsStringAsync();
    Task.WaitAll(jsonContentTask);
    string jsonContent = jsonContentTask.Result;
    //... other stuff
}
Sal
  • 5,129
  • 5
  • 27
  • 53
  • i tried it, if i send in the body a large string it works , but if i send a large json it does not work, i get `jsonContent ` as empty string – User7291 Sep 07 '18 at 07:03
1

I may be wrong but here is what I found. The action filters are executed after the model binding, which means the request stream has already been read. In your case i am not sure what that means. https://exceptionnotfound.net/the-asp-net-web-api-2-http-message-lifecycle-in-43-easy-steps-2/ explains the lifecycle in detail. Changing the content type wouldn't change the lifecycle events but would rather change the request content which in turn might affect the model binding. If you have a model set for the action, then How to get current model in action filter should help. So the solution would be to get model object from the actionContext and then modify it accordingly.

Aamir Masood
  • 321
  • 2
  • 9