Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
463 views
in Technique[技术] by (71.8m points)

asp.net - Why is the body of a Web API request read once?

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...