Несколько вызовов для HttpContent ReadAsAsync

Используя 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 - null. Если я переключу порядок, Y будет не нулевым, а X будет null. Другими словами, второй и последующие вызовы ReadAsAsync всегда будут возвращать нулевое значение, если только они не вызываются с тем же общим параметром типа. Независимо, либо вызов ReadAsAsync работает, как и ожидалось (даже при ненужном вызове LoadIntoBufferAsync).

Это неожиданно для меня - кажется, что я должен иметь возможность читать буферизованный контент как разные типы столько раз, сколько хочу. Если я добавлю еще одну строку:

var Z = await httpContent.ReadAsString();

Результат Z будет непустой строкой, независимо от порядка присвоения X, Y, Z.

Итак, как это происходит, и почему я не могу читать из HttpContent с помощью ReadAsAsync с несколькими типами?

Ответ 1

Документация разрешена в вопросе, но мне не слишком удивительно, что HttpContent действует как поток, в котором вы можете прочитать его только один раз. Практически каждый метод .NET с "read" в имени действует таким образом.

Я не знаю, почему имеет смысл читать одни и те же данные несколько раз, каждый раз интерпретируя их по-разному, за исключением, возможно, для целей отладки. Ваш пример кажется ухищренным для меня. Но если вы действительно хотите это сделать, вы можете попробовать ReadAsStreamAsync(), после чего вы можете напрямую прочитать из Stream, сбросив свойство Position до 0 каждый раз, когда вы хотите его снова прочитать, или ReadAsByteArrayAsync(), предоставляя вам массив байтов, который вы можете читать столько раз, сколько захотите.

Конечно, вам нужно будет использовать форматировщики явно для преобразования в нужный тип. Но это не должно быть слишком большим препятствием.

Ответ 2

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

Ответ 3

У меня есть рабочее решение для этого, однако для этого требуется использовать перегрузку 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);

Я использовал это решение в специализированном связующем модуле, поэтому я получил сборку formatters из экземпляра HttpParameterDescriptor, переданного конструктору. Вероятно, у вас есть одна такая коллекция из где-то в контексте выполнения, но если нет, просто создайте коллекцию по умолчанию так же, как это делает ASP.NET:

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

Ответ 4

Вы должны прочитать содержимое в строку, а затем десериализовать это в любые типы данных, которые вам нужны:

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

нет смысла читать содержимое асинхронно дважды.