多次调用 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
当我运行上面的代码时,X
是T
的非空实例,而Y
是空的。 如果我切换顺序,Y
将是一个非空字典,而X
将是空字典。 换句话说,对ReadAsAsync
的第二次和后续调用将始终返回 null,除非使用相同的泛型类型参数调用它们。 独立地,对ReadAsAsync
的任一调用都按预期工作(即使不必要地调用LoadIntoBufferAsync
)。
这对我来说是出乎意料的 - 似乎我应该能够根据需要多次读取不同类型的缓冲内容。如果我再添加一行:
var Z = await httpContent.ReadAsString();
结果是Z
将是一个非空字符串,无论分配给X, Y, Z
的顺序如何。
那么为什么会发生这种情况,为什么我不能使用具有多种类型的ReadAsAsync
从HttpContent
中读取?
@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);
异步读取内容两次没有任何意义。