HttpRequestMessage.Content is lost when it is read

2019-03-12 21:30发布

When trying to an object in an Action in a Controller it sporadically seems to be null. I discovered that it is due to the ReadAsStringAsync() in the SendAsync() override of the DelegatingHandler. The issue is with the content. When my client sends a content body and it is read in the logger it is never read by the Controller Action Invoker (or may be somewhere in the JsonFormatter). I suspect the subsequent call to Content.ReadAsStringAsync() doesnt throw an exception but also doesnt not return the expected content body (some info is returned stating that the async read is completed).

But my problem remains since I want to read a [FromBody] parameter in an action and it is null when the RaceCondition of Content.ReadStringAsync is won by the DelegatingHandler. When JsonFormatter wins it though, I get the object but that is rare (only at service startup).

Here is my DelegatingHandler code:

public class LogHandler : DelegatingHandler
{

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var apiRequest = new WebApiUsageRequest(request);
        WriteLog(apiRequest);
        request.Content.ReadAsStringAsync().ContinueWith(t =>
        {
            apiRequest.Content = t.Result;
            WriteLog(apiRequest);
        });

        return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            var apiResponse = new WebApiUsageResponse(task.Result);
            apiResponse.Content = task.Result.Content != null ? task.Result.Content.ReadAsStringAsync().Result : null;
            WriteLog(apiResponse);
            return task.Result;
        });
    }
}

Does anyone have a clue to the solution of this issue?

5条回答
仙女界的扛把子
2楼-- · 2019-03-12 21:39

ReadAsStreamAsync method returns the body content.

var body = string.Empty;
using (var reader = new StreamReader(request.Content.ReadAsStreamAsync().Result))
{
    reader.BaseStream.Seek(0, SeekOrigin.Begin);
    body = reader.ReadToEnd();
}
查看更多
疯言疯语
3楼-- · 2019-03-12 21:44

This worked for me:

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

Returned for me the json representation of my parameter object, so I could use it for exception handling and logging.

Found as accepted answer here

查看更多
相关推荐>>
4楼-- · 2019-03-12 21:46

Here's what I ended up doing:

public string SafelyReadContent(HttpRequestMessage request)
{
    var stream = request.Content.ReadAsStreamAsync().Result;
    var reader = new StreamReader(stream);
    var result = reader.ReadToEnd();
    stream.Seek(0, SeekOrigin.Begin);

    return result;
}

@pirimoglu's answer of using a "using" block didn't work for me, since when the reader was disposed, the underlying stream was also closed.

查看更多
Animai°情兽
5楼-- · 2019-03-12 21:57

This is by design. In ASP.NET Web API the body content is treated a forward-only stream that can only be read once.

You might try utilising ASP.NET Web API Tracing but I haven't test it with POST request yet so I'm not sure how/if it is tracing the request body (it is tracing parameters for GET request for sure). You can read more here:

查看更多
闹够了就滚
6楼-- · 2019-03-12 21:58

but if you use the code below in the SendAsync it works properly

        if (request.Content != null)
        {
            request.Content.ReadAsByteArrayAsync().ContinueWith
                (
                    (task) =>
                    {

                            var xxx = System.Text.UTF8Encoding.UTF8.GetString(task.Result);
                    });
        }
        return base.SendAsync(request, cancellationToken) //than call the base

. . .

查看更多
登录 后发表回答