JToken.WriteToAsyn c не пишет в JsonWriter - PullRequest
3 голосов
/ 29 марта 2020

Я пытаюсь создать промежуточное программное обеспечение, которое изменяет запрос определенным образом. Я могу прочитать его и изменить содержимое, но не могу понять, как правильно настроить потоковые записи для создания нового тела. Когда я звоню normalized.WriteToAsync(jsonWriter), MemoryStream остается пустым, и, следовательно, я получаю Требуется непустое тело запроса. Исключение. Что мне здесь не хватает? Вот что у меня есть:

public async Task Invoke(HttpContext context)
{
    if (context.Request.ContentType == "application/json" && context.Request.ContentLength > 0)
    {
        using var scope = _logger.BeginScope("NormalizeJson");
        try
        {
            using var requestReader = new HttpRequestStreamReader(context.Request.Body, Encoding.UTF8);
            using var jsonReader = new JsonTextReader(requestReader);

            var json = await JToken.LoadAsync(jsonReader);
            var normalized = _normalize.Visit(json); // <-- Modify json and return JToken

            // Create new Body
            var memoryStream = new MemoryStream();
            var requestWriter = new StreamWriter(memoryStream);
            var jsonWriter = new JsonTextWriter(requestWriter);
            await normalized.WriteToAsync(jsonWriter); // <-- At this point the MemoryStream has still 0 length.

            var content = new StreamContent(memoryStream.Rewind()); // <-- Use helper extension to Seek.Begin = 0                 
            context.Request.Body = await content.ReadAsStreamAsync();
        }
        catch (Exception e)
        {
            _logger.Scope().Exceptions.Push(e);
        }
    }

    await _next(context);
}


Демо для LINQPad et c.:

async Task Main()
{
    var token = JToken.FromObject(new User { Name = "Bob" });

    var memoryStream = new MemoryStream();
    var requestWriter = new StreamWriter(memoryStream);
    var jsonWriter = new JsonTextWriter(requestWriter);
    await token.WriteToAsync(jsonWriter); 
    memoryStream.Length.Dump(); // <-- MemoryStream.Length = 0
}

public class User
{
    public string Name { get; set; }
}

1 Ответ

2 голосов
/ 29 марта 2020

Вам необходимо правильно заразить sh и закрыть свои JsonTextWriter и StreamWriter, чтобы полностью заполнить memoryStream, вот так:

var memoryStream = new MemoryStream();
// StreamWriter implements IAsyncDisposable
// Leave the underlying stream open
await using (var requestWriter = new StreamWriter(memoryStream, Encoding.UTF8, 4096, leaveOpen: true)) 
{
    var jsonWriter = new JsonTextWriter(requestWriter); // But JsonTextWriter does not implement IAsyncDisposable, only IDisposable!
    try
    {
        await token.WriteToAsync(jsonWriter); 
    }
    finally
    {
        await jsonWriter.CloseAsync();
    }
}

Демонстрационная скрипка # 1 здесь .

Или, поскольку вы пишете в MemoryStream, на самом деле нет нужды вообще использовать async, и вместо этого вы можете сделать:

var memoryStream = new MemoryStream();
using (var requestWriter = new StreamWriter(memoryStream, Encoding.UTF8, 4096, leaveOpen: true)) // Leave the underlying stream open
using (var jsonWriter = new JsonTextWriter(requestWriter))
{
    token.WriteTo(jsonWriter); 
}

Демонстрационная скрипка № 2 здесь .

Примечания:

  • Обратите внимание на использование await using для StreamWriter. Этот синтаксис гарантирует, что StreamWriter будет сбрасываться и закрываться асинхронно и может использоваться для любого объекта, который реализует IAsyncDisposable. (Это действительно имеет значение, только если вы выполняете запись в файловый поток или другой поток, не связанный с памятью.)

  • Кажется, что ни JsonTextWriter, ни базовый класс JsonWriter реализовать IAsyncDisposable, поэтому мне пришлось асинхронно закрывать модуль записи JSON вручную, а не с помощью оператора using. Внешний await using должен гарантировать, что базовый StreamWriter не останется открытым в случае исключения.

...