Why is the body of a Web API request read once?
asp.net, asp.net-web-api
Solution
The answer you pointed to is not entirely accurate.
You can always read as string (`ReadAsStringAsync`)or as byte[] (`ReadAsByteArrayAsync`) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.
Problem
My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this ``` var task = _message.Content.ReadAsAsync<Credentials>(); task.Wait(); if (task.Result != null) { // check if credentials are valid } ``` The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers. Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing? POSTMORTEM: I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this: ``` message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait(); ``` The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct: ``` var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor()); var formatters = _message.GetConfiguration().Formatters; var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType); var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials; ``` With an extension method (I'm in .NET 4.0 with no await keyword) ``` public static class TaskExtensions { public static T WaitFor<T>(this Task<T> task) { task.Wait(); if (task.IsCanceled) { throw new ApplicationException(); } if (task.IsFaulted) { throw task.Exception; } return task.Result; } } ``` One last catch, HttpContent has a hard-coded max buffer size: ``` internal const int DefaultMaxBufferSize = 65536; ``` So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.