多次调用 HttpContent ReadAsAsync

本文关键字:ReadAsAsync HttpContent 调用 | 更新日期: 2023-09-27 18:30:31

使用 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

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

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

var Z = await httpContent.ReadAsString();

结果是Z将是一个非空字符串,无论分配给X, Y, Z的顺序如何。

那么为什么会发生这种情况,为什么我不能使用具有多种类型的ReadAsAsyncHttpContent中读取?

多次调用 HttpContent ReadAsAsync

@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>>();

关于这个问题的文档很少,但对我来说,HttpContent就像一个流,因为你只能阅读一次。名称中带有"read"的 .NET 中几乎每个方法都以这种方式运行。

我不知道为什么多次读取相同的数据,每次都以不同的方式解释它,除了可能出于调试目的。你的例子对我来说似乎是人为的。但是如果你真的想这样做,你可以试试 ReadAsStreamAsync() ,然后你可以直接从Stream读取,每次你想再次读取它时将 Position 属性重置为 0,或者 ReadAsByteArrayAsync() ,给你一个字节数组,你可以从中读取任意次数。

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

我为此得到了一个可行的解决方案,但是它需要使用显式获取媒体格式化程序列表的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
}

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

formatters = formatters.Select(f => new RewindStreamFormatterDecorator(f)).ToArray();

。现在,您可以根据需要多次调用ReadAsAsync

var X = await httpContent.ReadAsAsync<T>(formatters);
var Y = await httpContent.ReadAsAsync<Dictionary<string, object>>(formatters);

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

formatters = new MediaTypeFormatter[]
{
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter(),
    new FormUrlEncodedMediaTypeFormatter()
};

您应该将内容读入字符串,然后将其反序列化为所需的任何数据类型:

var content = await httpContent.ReadAsString();
// read as first type
var X = JsonConvert.DeserializeObject<T>(content);
// read as second type
var Y = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
异步

读取内容两次没有任何意义。