1

I need to read the content sent via a post/put to my Web API via an attribute to perform some additional validation but the content value is always empty even thought I can see the context-size is set a value i.e. 2067 and the content-type is set to application/json

I've tried different things but none seem to work:

  • ReadAsync
  • ReadAsByteArrayAsync
  • ReadAsStringAsync
  • etc...

My last attempt looks as follows:

public async override void OnActionExecuting(HttpActionContext actionContext)
{
    using (MemoryStream ms = new MemoryStream())
    {
        await actionContext.Request.Content.CopyToAsync(ms)
                           .ConfigureAwait(false);
        var str = System.Text.UTF8Encoding.
                  UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
    }
}

The reason I tried to use the CopyTo is that I noticed that when I called some of the above functions, that even though they returned an empty string, I could make that call once is after some googling, I believe this is by design.

Bottom line is that I need to access the body/content of my request so that I can inspect the json that was sent.

Thanks.

Thierry
  • 6,142
  • 13
  • 66
  • 117

1 Answers1

0

The problem here is that the middleware that executes the controller action reads the request body and thus consumes it in the process, taking away the opportunity for any subsequent middleware to access it. This is why you're getting an empty string back when attempting to parse the stream as you're effectively reading a stream with a length of zero.

It seems like the Action Filter middleware is run after the controller middleware, which is already too late in the course of the pipeline. A solution would be to setup a custom middleware that executes before the controller middleware, and parse the request body stream only if the current action endpoint is annotated with the given attribute.

The solution provided here is written for ASP.NET Core 3.0, so you might have to do some adjustments to make it work in your specific project.

In Startup.cs:

public void Configure(IApplicationBuilder app)
{
   app.UseRouting();   // Routes middleware


   // Request body-reading middleware must be put after UseRouting()

   app.Use(async (ctx, next) =>
   {
      var endpoint = ctx.GetEndpoint();

      if (endpoint != null)
      {
         var attribute = endpoint.Metadata.GetMetadata<ThierryAttribute>();

         if (attribute != null)
         {
            ctx.Request.EnableBuffering();

            using (var sr = new StreamReader(ctx.Request.Body, Encoding.UTF8, false, 1024, true))
            {
               var str = await sr.ReadToEndAsync();   // Store request body JSON string
            }

            ctx.Request.Body.Position = 0;
         }
      }

      await next();
   });

   app.UseEndpoints(erb => erb.MapControllers());   // Controller middleware
}

And make your attribute into an empty marker attribute:

[AttributeUsage(AttributeTargets.Method)]
public class ThierryAttribute : Attribute { }
silkfire
  • 24,585
  • 15
  • 82
  • 105
  • I tried what you suggested but unfortunately it didn't work. As this is in an attribute class, I have to use the actionContext of the attribute and also, in order to access the Request.Body, I had to use: actionContext.Request.GetOwinContext().Request.Body instead but unfortunately I'm still getting a blank value. – Thierry Nov 08 '19 at 15:53
  • Sorry, just saw your point below. As this is called in an attribute before the action of my controller is called, I would assume that my action is a second call to the request in order to get the deserialized object, so I guess I would need to make multiple read multiple times. – Thierry Nov 08 '19 at 15:54
  • I think this might be a duplicate question as I found: https://stackoverflow.com/questions/13226817/getting-raw-post-data-from-web-api-method which is what I'm looking for but my data is always an empty string, even though the content size and type are correct. I'm wondering, can anything in the web.config or in code block this data from being retrieved? – Thierry Nov 08 '19 at 16:09
  • Was my solution of any help? – silkfire Nov 14 '19 at 02:53
  • 1
    Sorry about the delay. Found a work-around for my original issue which was on how best to deal with (Anti)Xss attack but I'll check your suggestion over the weekend as I'd prefer using the raw data than having to serialized the model. I'll update you as soon as I have something. – Thierry Nov 15 '19 at 16:33