34

我正在重新实现一个请求记录器作为 Owin 中间件,它记录所有传入请求的请求 url 和正文。我能够读取正文,但如果我这样做,我的控制器中的正文参数为空。

我猜它是空的,因为流位置在末尾,所以当它试图反序列化主体时,没有什么可读取的了。我在以前版本的 Web API 中遇到了类似的问题,但能够将 Stream 位置设置回 0。这个特定的流会引发This stream does not support seek operations异常。

在最新版本的 Web API 2.0 中,我可以Request.HttpContent.ReadAsStringAsync()在我的请求记录器中调用,并且主体仍会完整地到达控制器。

阅读后如何回退流?

或者

如何在不使用请求正文的情况下读取它?

public class RequestLoggerMiddleware : OwinMiddleware
{
    public RequestLoggerMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            context.Request.Body.Position = 0; // cannot set stream position back to 0
            Console.WriteLine(context.Request.Body.CanSeek); // prints false
            this.Next.Invoke(context);
        });
    }
}

public class SampleController : ApiController 
{
    public void Post(ModelClass body)
    {
        // body is now null if the middleware reads it
    }
}
4

3 回答 3

41

刚刚找到一个解决方案。用包含数据的新流替换原始流。

    public override async Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            byte[] requestData = Encoding.UTF8.GetBytes(body);
            context.Request.Body = new MemoryStream(requestData);
            await this.Next.Invoke(context);
        });
    }

如果您正在处理大量数据,我相信 aFileStream也可以作为替代品。

于 2014-02-16T00:21:09.073 回答
12

这是 Despertar 对第一个答案的一个小改进,这对我有很大帮助,但是在处理二进制数据时遇到了一个问题。将流提取为字符串,然后使用混淆二进制内容将其放回字节数组的中间步骤Encoding.UTF8.GetBytes(body)(除非它是 UTF8 编码的字符串,否则内容会改变)。这是我的修复方法Stream.CopyTo()

    public override async Task Invoke(IOwinContext context)
    {
        // read out body (wait for all bytes)
        using (var streamCopy = new MemoryStream())
        {
            context.Request.Body.CopyTo(streamCopy);
            streamCopy.Position = 0; // rewind

            string body = new StreamReader(streamCopy).ReadToEnd();
            // log body

            streamCopy.Position = 0; // rewind again
            context.Request.Body = streamCopy; // put back in place for downstream handlers

            await this.Next.Invoke(context);
        }
    }

此外,MemoryStream这很好,因为您可以在记录整个正文之前检查流的长度(如果有人上传了一个大文件,我不想这样做)。

于 2017-07-10T16:28:33.483 回答
-3

我知道这很旧,但只是为了帮助遇到此问题的任何人。您需要在流上寻找:context.Request.Body.Seek(0, SeekOrigin.Begin);

于 2014-08-22T20:09:01.027 回答