使用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
的顺序如何。
那怎么会发生这种情况,为什么我不能使用多种类型的HttpContent
从ReadAsAsync
阅读?
答案 0 :(得分:11)
关于这个问题的文档很少,但HttpContent
就像流一样,对我来说并不太令人惊讶,因为你只能阅读一次。几乎所有.NET中使用“read”的方法都是这样的。
我不知道为什么甚至有意义地多次读取相同的数据,每次都以不同的方式解释它,除了可能用于调试目的。你的例子似乎是为我设计的。但是如果您真的想这样做,可以尝试ReadAsStreamAsync()
,然后可以直接从Stream
读取Position
,每次再次阅读时将ReadAsByteArrayAsync()
属性重置为0或{{1}},为您提供一个字节数组,您可以根据需要多次阅读。
当然,您必须明确地使用格式化程序转换为所需的类型。但这不应该是一个太大的障碍。
答案 1 :(得分:11)
@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>>();
答案 2 :(得分:4)
我为此获得了一个有效的解决方案,但它需要使用显式获取媒体格式化程序列表的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()
};
答案 3 :(得分:2)
您应该将内容读入字符串,然后将其反序列化为您需要的任何数据类型:
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);
两次异步读取内容没有任何意义。