多次调用HttpContent ReadAsAsync

Mic*_*ito 22 .net c# asp.net-web-api

使用Web API 2.2,假设我想要读HttpContent两次,每次都是不同的类型.

await httpContent.LoadIntoBufferAsync(); //necessary to buffer content for multiple reads
var X = await httpContent.ReadAsAsync<T>(); //read as first type
var Y = await httpContent.ReadAsAsync<Dictionary<string, object>>(); //read as second type
Run Code Online (Sandbox Code Playgroud)

当我运行上面的代码时,while X的非null实例是null.如果我切换顺序,将是一个非空字典,而将为null.换句话说,第二次和后续调用将始终返回null,除非使用相同的泛型类型参数调用它们.独立地,要么按预期调用工作(即使在不必要地调用时).TYYXReadAsAsyncReadAsAsyncLoadIntoBufferAsync

这对我来说是意料之外的 - 似乎我应该能够根据需要多次读取缓冲内容作为不同的类型.如果我添加另一行:

var Z = await httpContent.ReadAsString();
Run Code Online (Sandbox Code Playgroud)

Z无论赋值顺序如何,结果都将是一个非空字符串X, Y, Z.

那么怎么来这种情况发生,为什么我不能读取HttpContent使用ReadAsAsync多种类型?

Pet*_*iho 14

关于这个问题的文档很少,但对于我来说,HttpContent像流一样的行为并不太令人惊讶,因为你只能阅读一次.几乎所有.NET中使用"read"的方法都是这样的.

我不知道为什么甚至有意义地多次读取相同的数据,每次都以不同的方式解释它,除了可能用于调试目的.你的例子似乎是为我设计的.但是如果你真的想这样做,你可以尝试ReadAsStreamAsync(),然后你可以Stream直接从中读取,Position每次想要再次读取它时将属性重置为0,或者ReadAsByteArrayAsync(),给你一个字节数组,你可以读取多次随你心意.

当然,您必须明确地使用格式化程序转换为所需的类型.但这不应该是一个太大的障碍.


Bad*_*dri 13

@Peter是对的.如果您想一次又一次地阅读,您可能希望读取为流并在每次读取流时寻求开始.但是如果你想现在做什么但是让第二次读取工作,你可以在第一次读取之后寻找流的开头,就像这样.

await httpContent.LoadIntoBufferAsync();
var X = await httpContent.ReadAsAsync<T>();

Stream stream = await httpContent.ReadAsStreamAsync();
stream.Seek(0, SeekOrigin.Begin);

var Y = await httpContent.ReadAsAsync<Dictionary<string, object>>();
Run Code Online (Sandbox Code Playgroud)

  • 对于那些担心可搜索性的人:"但是,如果您希望能够多次读取内容,那么您可以使用LoadIntoBufferAsync方法来执行此操作.这将导致内容被读入内部缓冲区以便可以使用多次没有通过网络再次检索它." (来源:https://blogs.msdn.microsoft.com/henrikn/2012/02/17/httpclient-downloading-to-a-local-file/) (5认同)

Kon*_*man 5

我为此得到了一个可行的解决方案,但是它需要使用ReadAsync显式获取媒体格式化程序列表的重载。它看起来很像一个讨厌的黑客,但它确实有效。

实际上,HttpContent它在幕后充当流,一旦被格式化程序读取,它就不会自动倒带。但是有一种方法可以进行手动倒带,这是如何做到的。

首先,为媒体类型格式化程序创建一个装饰器,如下所示:

public class RewindStreamFormatterDecorator : MediaTypeFormatter
{
    private readonly MediaTypeFormatter formatter;

    public RewindStreamFormatterDecorator(MediaTypeFormatter formatter)
    {
        this.formatter = formatter;

        this.SupportedMediaTypes.Clear();
        foreach(var type in formatter.SupportedMediaTypes)
            this.SupportedMediaTypes.Add(type);

        this.SupportedEncodings.Clear();
        foreach(var encoding in formatter.SupportedEncodings)
            this.SupportedEncodings.Add(encoding);
    }

    public override bool CanReadType(Type type)
    {
        return formatter.CanReadType(type);
    }

    public override Task<object> ReadFromStreamAsync(
        Type type,
        Stream readStream,
        HttpContent content,
        IFormatterLogger formatterLogger,
        CancellationToken cancellationToken)
    {
        var result = formatter.ReadFromStreamAsync
           (type, readStream, content, formatterLogger, cancellationToken);
        readStream.Seek(0, SeekOrigin.Begin);
        return result;
    }

    //There are more overridable methods but none seem to be used by ReadAsAsync
}
Run Code Online (Sandbox Code Playgroud)

其次,将格式化程序列表转换为装饰格式化程序列表:

formatters = formatters.Select(f => new RewindStreamFormatterDecorator(f)).ToArray();
Run Code Online (Sandbox Code Playgroud)

...现在您可以ReadAsAsync根据需要多次调用:

var X = await httpContent.ReadAsAsync<T>(formatters);
var Y = await httpContent.ReadAsAsync<Dictionary<string, object>>(formatters);
Run Code Online (Sandbox Code Playgroud)

我在自定义模型绑定器中使用了这个解决方案,所以我从HttpParameterDescriptor传递给构造函数的实例中获取了格式化程序集合。您可能会在执行上下文的某个地方手头有一个这样的集合,但如果没有,只需像 ASP.NET 一样创建一个默认集合:

formatters = new MediaTypeFormatter[]
{
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter(),
    new FormUrlEncodedMediaTypeFormatter()
};
Run Code Online (Sandbox Code Playgroud)