简体   繁体   English

多次调用HttpContent ReadAsAsync

[英]Multiple Calls to HttpContent ReadAsAsync

Using Web API 2.2, suppose I want to read from HttpContent twice, each time as a different type. 使用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

When I run the above code, X is a non-null instance of T while Y is null. 当我运行上面的代码时, XT的非null实例,而Y是null。 If I switch the order, Y will be a non-null dictionary while X will be null. 如果我切换顺序, Y将是非空字典,而X将为空。 In other words, the second and subsequent calls to ReadAsAsync will always return null unless they're called with the same generic type parameter. 换句话说,对ReadAsAsync的第二次和后续调用将始终返回null,除非使用相同的泛型类型参数调用它们。 Independently, either call to ReadAsAsync works as expected (even when needlessly calling LoadIntoBufferAsync ). 独立地,对ReadAsAsync调用按预期工作(即使在不必要地调用LoadIntoBufferAsync )。

This is unexpected to me - it seems that I should be able to read buffered content as differing types as many times as I want. 这对我来说是意料之外的 - 似乎我应该能够根据需要多次读取缓冲内容作为不同的类型。 If I add another line: 如果我添加另一行:

var Z = await httpContent.ReadAsString();

The result is Z will be a non-null string, no matter the order of assignment to X, Y, Z . 结果是Z将是非空字符串,无论分配给X, Y, Z的顺序如何。

So how come this happens, and why can't I read from HttpContent using ReadAsAsync with multiple types? 那么为什么会发生这种情况,为什么我不能使用多种类型的ReadAsAsyncHttpContent读取?

@Peter is correct. @Peter是对的。 If you want to read again and again, you would probably want to read as stream and seek to beginning every time you read the stream. 如果您想一次又一次地阅读,您可能希望读取为流并在每次读取流时寻求开始。 But then if you want to do what do you now but get the second read working, you can seek to the beginning of the stream, after the first read, like this. 但是如果你想现在做什么但是让第二次读取工作,你可以在第一次读取之后寻找流的开头,就像这样。

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

The documentation is sparse on the question, but it's not too surprising to me that HttpContent acts like a stream, in that you can read it just once. 文档在这个问题上很少,但对我而言, HttpContent就像流一样,并不是太令人惊讶,因为你只能阅读一次。 Pretty much every method in .NET with "read" in the name acts this way. 几乎所有.NET中使用“read”的方法都是这样的。

I don't have any idea why it even makes sense to read the same data multiple times, interpreting it differently each time, except possibly for debugging purposes. 我不知道为什么甚至有意义地多次读取相同的数据,每次都以不同的方式解释它,除了可能用于调试目的。 Your example seems contrived to me. 你的例子似乎是为我设计的。 But if you really want to do that, you can try ReadAsStreamAsync() , which you can then read from the Stream directly, resetting the Position property to 0 each time you want to read it again, or ReadAsByteArrayAsync() , giving you a byte array you can read from as many times as you like. 但是如果你真的想这样做,你可以尝试ReadAsStreamAsync() ,然后你可以直接从Stream读取,每次想要再次读取时将Position属性重置为0,或者ReadAsByteArrayAsync() ,给你一个字节您可以根据自己的喜好阅读数组。

Of course, you'll have to use the formatters explicitly to convert to the desired type. 当然,您必须明确地使用格式化程序转换为所需的类型。 But that shouldn't be too much of an impediment. 但这不应该是一个太大的障碍。

I got a working solution for this, however it requires to use the overload of ReadAsync that explicitly takes a list of media formatters. 我得到了一个有效的解决方案,但它需要使用ReadAsync的重载,它明确地获取媒体格式化程序列表。 It looks pretty much as a nasty hack but it works. 它看起来几乎是一个讨厌的黑客,但它的工作原理。

Indeed, HttpContent acts as a stream under the hoods, and once it's read by the formatter, it is not automatically rewinded. 实际上, HttpContent充当引擎盖下的流,一旦格式化程序读取它,它就不会自动重新启动。 But there is a way to do a manual rewind, and here's how this can be done. 但是有一种方法可以进行手动倒带,这就是如何做到的。

First, create a decorator for media type formatters as follows: 首先,为媒体类型格式化程序创建一个装饰器,如下所示:

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
}

Second, convert the list of formatters to a list of decorated formatters: 其次,将格式化程序列表转换为装饰格式化程序列表:

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

...and now you can invoke ReadAsAsync as many times as you want: ...现在您可以根据需要多次调用ReadAsAsync

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

I used this solution in a custom model binder so I got the formatters collection from the instance of HttpParameterDescriptor passed to the constructor. 我在自定义模型绑定器中使用了此解决方案,因此我从传递给构造函数的HttpParameterDescriptor实例中HttpParameterDescriptor了formatters集合。 You will probably have one such collection at hand from somewhere in the execution context, but if not, just create a default collection the same way as ASP.NET does: 你可能会在执行上下文的某个地方有一个这样的集合,但如果没有,只需像ASP.NET一样创建一个默认集合:

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

You should read the contents into a string, then deserialize that into whatever datatypes you need: 您应该将内容读入字符串,然后将其反序列化为您需要的任何数据类型:

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

it doesn't make any sense to read the contents asynchronously twice. 两次异步读取内容没有任何意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM