3

I need to process the raw request body in and MVC core controller that has route parameters

[HttpPut]
[Route("api/foo/{fooId}")]
public async Task Put(string fooId)
{
   reader.Read(Request.Body).ToList();
   await _store.Add("tm", "test", data);
}

but It seems like the model binder has already consumed the request stream by the time it gets to the controller.

If I remove the route parameters, I can then access the request stream (since the framework will no longer process the stream to look for parameters). How can I specify both route parameters and be able to access Request Body without having to manually parse request URI etc.?

I have tried decorating my parameters with [FromRoute] but it had no effect.

  • Please note I cannot bind the request body to an object and have framework handle the binding, as I am expecting an extremely large payload that needs to be processed in chunks in a custom manner.
  • There are no other controller, no custom middle-ware, filters, serialzier, etc.
  • I do not need to process the body several times, only once
  • storing the stream in a temp memory or file stream is not an options, I simply want to process the request body directly.

How can I get the framework to bind paramters from Uri, QueryString, etc. but leave the request body to me?

Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
hnafar
  • 612
  • 8
  • 19
  • Possible duplicate of [Getting hold of raw POST data when using \[FromBody\]](https://stackoverflow.com/questions/37839907/getting-hold-of-raw-post-data-when-using-frombody) – Set Aug 21 '17 at 15:23
  • Have a look at [Uploading large files with streaming](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming) it will give you a good starting point on how to disable Model Bindings (which is important, because the Request stream is read only and can only be read once and if the model binder reads it, you can't within the body) and have a look at how the Upload method is implemented to stream the file directly rather than saving it into a temporary file – Tseng Aug 21 '17 at 19:29
  • it is a bit specific to multiform and file upload, but once you disabled the model validation, you can read from `HttpContext.Request.Body` using the typical `Stream` methods and do whatever you want with it – Tseng Aug 21 '17 at 19:30
  • Likely duplicate of of [How to read request body in asp.net core webapi controller](https://stackoverflow.com/questions/40494913/how-to-read-request-body-in-a-asp-net-core-webapi-controller) – RonC Aug 23 '17 at 13:04
  • Possible duplicate of [How to read request body in a asp.net core webapi controller?](https://stackoverflow.com/questions/40494913/how-to-read-request-body-in-a-asp-net-core-webapi-controller) – RonC Aug 23 '17 at 13:05
  • Not a duplicate, that question (same link on both comments) deals with trying to process the request stream several time, and rewinding it, where as in my case I only wanted to read the stream once, but couldn't get to it before the framework did, and could use middle-ware to achieve what I wanted as clearly noted in the question. – hnafar Aug 23 '17 at 21:35

2 Answers2

2

Define this attribute in your code:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
   public void OnResourceExecuting(ResourceExecutingContext context)
   {
       var factories = context.ValueProviderFactories;
       factories.RemoveType<FormValueProviderFactory>();
       factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}    

If you're targeting .Net Core 3 you also need to add

factories.RemoveType<FormFileValueProviderFactory>();

Now decorate your action method with it:

[HttpPut]
[Route("api/foo/{fooId}")]
[DisableFormValueModelBinding]
public async Task Put(string fooId)
{
  reader.Read(Request.Body).ToList();
  await _store.Add("tm", "test", data);
}

The attribute works by removing Value Providers which will attempt to read the request body, leaving just those which supply values from the route or the query string.

HT @Tseng for the link Uploading large files with streaming which defines this attribute

Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
1

As I suspected the root cause was MVC inspecting the request body in order to try to bind route parameters. This is how model binding works by default for any routes that are not parameter-less, as per documentation.

The framework however does this only when the request content type is not specified, or when it is form data (multipart or url-encoded I assume).

Changing my request content-type to any thing other than form data (e.g. application/json) I can get the framework to ignore the body unless specifically required (e.g. with a [FromBody] route parameter). This is an acceptable solution for my case since I am only interested accepting JSON payloads with content-type application/json.

Implementation of DisableFormValueModelBindingAttribute in Uploading large files with streaming pointed out by @Tseng seems to be a better approach however, so I will look into using that instead, for complete

hnafar
  • 612
  • 8
  • 19