【问题标题】:Using JSON.NET to serialize object into HttpClient's response stream使用 JSON.NET 将对象序列化为 HttpClient 的响应流
【发布时间】:2014-08-16 09:55:19
【问题描述】:

摘要

您好,我正在开展一个项目,需要通过 HttpClient 发送一些对象的潜在巨大 json,典型的大小为 10-20 mb 的 JSON。为了有效地做到这一点,我想使用流,两者都与 Json.Net 一起序列化对象以及流以使用 HttpClient 发布数据。

问题

这是使用 Json.net 进行序列化的 sn-p,为了使用流,Json.net 期望它会写入一个流:

public static void Serialize( object value, Stream writeOnlyStream )
{
    StreamWriter writer = new StreamWriter(writeOnlyStream);  <-- Here Json.net expects the stream to be already created
    JsonTextWriter jsonWriter = new JsonTextWriter(writer);
    JsonSerializer ser = new JsonSerializer();
    ser.Serialize(jsonWriter, value );
    jsonWriter.Flush();
}

虽然 HttpClient 期望它会读取一个流:

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:54359/");

    var response = await client.PostAsync("/api/snapshot", new StreamContent(readOnlyStream)); <-- The same thing here, HttpClient expects the stream already to exist

    ... 
}

所以最终这意味着两个类都希望流由其他人创建,但是对于 Json.Net 和 HttpClient 都没有流。所以这个问题似乎可以通过实现一个流来解决,该流将拦截对只读流的读取请求,并根据来自只写流的请求发出写入。

问题

也许有人已经偶然发现了这种情况,并且可能已经找到了已经实施的解决方案。如果有,请分享给我,

提前谢谢你!

【问题讨论】:

    标签: c# json json.net


    【解决方案1】:

    如果定义HttpContent 的子类:

    public class JsonContent:HttpContent
    {
        public object SerializationTarget{get;private set;}
        public JsonContent(object serializationTarget)
        {
            SerializationTarget=serializationTarget;
            this.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        }
        protected override async Task SerializeToStreamAsync(Stream stream, 
                                                    TransportContext context)
        {
            using(StreamWriter writer = new StreamWriter(stream))
            using(JsonTextWriter jsonWriter = new JsonTextWriter(writer))
            {
                JsonSerializer ser = new JsonSerializer();
                ser.Serialize(jsonWriter, SerializationTarget );
            }
    
        }   
    
        protected override bool TryComputeLength(out long length)
        {
            //we don't know. can't be computed up-front
            length = -1;
            return false;
        }
    }
    

    那么你可以:

    var someObj = new {a = 1, b = 2};
    var client = new HttpClient();
    var content = new JsonContent(someObj);
    var responseMsg = await client.PostAsync("http://someurl",content);
    

    序列化器将直接写入请求流。

    【讨论】:

    • 你有很好的答案,谢谢,但我使用了上面的那个,因为它已经是现有的实现......
    • 很好的答案,对于 .Net Core 2.1 及更高版本,您需要将 leaveOpen 设置为 true 以在处理 StreamWriter 后保持 Stream 打开。使用 (var writer = new StreamWriter(stream, new UTF8Encoding(false), 1024, true))
    • 关于 .Net Core 3.1 中 System.Text.Json 序列化程序的上述变化有何想法?
    【解决方案2】:

    使用PushStreamContent。它不是让 Web API 从流中“拉”,而是让您更直观地“推”入其中。

    object value = ...;
    
    PushStreamContent content = new PushStreamContent((stream, httpContent, transportContext) =>
    {
        using (var tw = new StreamWriter(stream))
        {
            JsonSerializer ser = new JsonSerializer();
            ser.Serialize(tw, value);
        }
    });
    

    请注意,JSON.NET 在序列化期间不支持异步,因此虽然这可能更节省内存,但它的可扩展性并不高。

    不过,我建议尽量避免使用如此大的 JSON 对象。例如,如果您要发送大量集合,请尝试将其分块。许多客户端/服务器会在没有特殊处理的情况下直接拒绝这么大的东西。

    【讨论】:

    • 棒极了,但是在dotnet core的httpClient nuget包里好像没有?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 2013-10-08
    • 2011-05-30
    • 1970-01-01
    • 2011-10-15
    相关资源
    最近更新 更多