【问题标题】:JToken.WriteToAsync does not write to JsonWriterJToken.WriteToAsync 不写入 JsonWriter
【发布时间】:2020-07-09 22:56:15
【问题描述】:

我正在尝试创建一个以某种方式更改请求的中间件。我能够阅读并更改内容,但我无法弄清楚如何正确设置流编写器以创建新正文。当我调用 normalized.WriteToAsync(jsonWriter) 时,MemoryStream 仍然为空,因此我收到 A non-empty request body is required. 异常。我在这里想念什么?这是我目前所拥有的:

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 等:

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

【问题讨论】:

  • 我想避免先将其转换为string
  • 既然您要写信给MemoryStream,为什么还要使用async
  • @dbc 我对此没有任何聪明的答案,但一致性。你会用别的东西代替吗?
  • 是的,我会做WriteTo(),宣布胜利并继续前进?
  • @t3chb0t 当我在jsonWriter 上调用Flush() 时,内存流会获取数据,因此您可能需要将编写器包装在using

标签: c# asp.net-web-api request json.net asp.net-core-middleware


【解决方案1】:

您需要正确刷新并关闭您的JsonTextWriterStreamWriter 才能完全填充memoryStream,如下所示:

var memoryStream = new MemoryStream();
// StreamWriter implements IAsyncDisposable
// Leave the underlying stream open
await using (var requestWriter = new StreamWriter(memoryStream, 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 here.

或者,由于您正在写信给MemoryStream,因此根本没有必要使用async,而是您可以这样做:

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

演示小提琴#2 here.

注意事项:

  • 注意await using 用于StreamWriter。此语法保证StreamWriter 将被异步刷新和关闭,并可用于任何实现IAsyncDisposable 的对象。 (这仅在您写入文件流或其他非内存流时才真正重要。)

  • 似乎JsonTextWriter 和基类JsonWriter 都没有实现IAsyncDisposable,所以我不得不手动异步关闭JSON 编写器,而不是通过using 语句。外部await using 应确保底层StreamWriter 在发生异常时不会保持打开状态。

  • JSON RFC 8259 指定实现不得在网络传输的 JSON 文本的开头添加字节顺序标记 (U+FEFF)。因此,在构造StreamWriter 时,建议传递一个编码,例如new UTF8Encoding(false),它不会预先添加 BOM。或者,如果您只想要 UTF-8,StreamWriter constructors创建一个带有 UTF-8 编码的 StreamWriter,没有字节顺序标记 (BOM) 如果您自己没有指定一个并保留默认值该参数的值如上面的代码所示。

【讨论】:

  • 等待、关闭、使用和保持打开的组合多么美妙! :-O
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-19
  • 2019-07-11
  • 1970-01-01
相关资源
最近更新 更多