在这篇文章,我将介绍一个名为 System.Net.Http.Json 的扩展库,它最近添加到了 .NET 中,我们看一下这个库能够给我们解决什么问题,今天会介绍下如何在代码中使用。

在此之前我们是如何处理

JSON是一种普遍和流行的串行化格式数据来发送现代web api,我经常在我的项目中使用HttpClient 调用外部资源, 当 content type 是 “application/json”, 我拿到Json的响应内容后,我需要手动处理响应,通常会验证响应状态代码是否为200,检查内容是不是为空,然后再试图从响应内容流反序列化

如果我们使用 Newtonsoft.Json, 代码可能是像下边这样

private static async Task<User> StreamWithNewtonsoftJson(string uri, HttpClient httpClient)
{
using var httpResponse = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); httpResponse.EnsureSuccessStatusCode(); // throws if not 200-299 if (httpResponse.Content is object && httpResponse.Content.Headers.ContentType.MediaType == "application/json")
{
var contentStream = await httpResponse.Content.ReadAsStreamAsync(); using var streamReader = new StreamReader(contentStream);
using var jsonReader = new JsonTextReader(streamReader); JsonSerializer serializer = new JsonSerializer(); try
{
return serializer.Deserialize<User>(jsonReader);
}
catch(JsonReaderException)
{
Console.WriteLine("Invalid JSON.");
}
}
else
{
Console.WriteLine("HTTP Response was invalid and cannot be deserialised.");
} return null;
}

虽然上面没有大量的代码, 但是我们从外部服务接收JSON数据需要都编写这些,在微服务环境中,这可能是在很多地方,不同的服务。

大家可能通常也会把 Json 序列化成 String,在 HttpClient 的 HttpContent 中调用GetStringAsync ReadAsStringAsync,可以直接使用 Newtonsoft.Json 和 System.Text.Json,现在的一个问题是我们需要多分配一个包含整个Json 数据的 String,这样会存在浪费,因为我们看上面的代码已经有一个可用的响应流,可以直接反序列化到实体,通过使用流,也可以进一步提高性能,在我的另一篇文章里, 可以利用HttpCompletionOption来改善HttpClient性能。

如果您在过去在项目中使用过 HttpClient 来处理返回的Json数据,那么您可能已经使用了Microsoft.AspNet.WebApi.Client。我在过去使用过它,因为它提供了有用的扩展方法来支持从HttpResponseMessage上的内容流进行高效的JSON反序列化,这个库依赖于Newtonsoft.Json文件并使用其基于流的API来支持数据的高效反序列化,这是一个方便的库,我用了几年了

如果我们在项目中使用这个库,上面的代码可以减少一些

private static async Task<User> WebApiClient(string uri, HttpClient httpClient)
{
using var httpResponse = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); httpResponse.EnsureSuccessStatusCode(); // throws if not 200-299 try
{
return await httpResponse.Content.ReadAsAsync<User>();
}
catch // Could be ArgumentNullException or UnsupportedMediaTypeException
{
Console.WriteLine("HTTP Response was invalid or could not be deserialised.");
} return null;
}

最近.NET 团队引入了一个内置的JSON库 System.Text.Json,这个库是使用了最新的 .NET 的性能特性, 比如 Span, 低开销, 能够快速序列化和反序列化, 并且在.NET Core 3.0 集成到了 BCL(基础库), 所以你不需要引用一个额外的包在项目中

今天,我更倾向于使用 System.Text.Json,主要是在流处理,代码跟上面 Newtonsofe.Json 相比更简洁

private static async Task<User> StreamWithSystemTextJson(string uri, HttpClient httpClient)
{
using var httpResponse = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); httpResponse.EnsureSuccessStatusCode(); // throws if not 200-299 if (httpResponse.Content is object && httpResponse.Content.Headers.ContentType.MediaType == "application/json")
{
var contentStream = await httpResponse.Content.ReadAsStreamAsync(); try
{
return await System.Text.Json.JsonSerializer.DeserializeAsync<User>(contentStream, new System.Text.Json.JsonSerializerOptions { IgnoreNullValues = true, PropertyNameCaseInsensitive = true });
}
catch (JsonException) // Invalid JSON
{
Console.WriteLine("Invalid JSON.");
}
}
else
{
Console.WriteLine("HTTP Response was invalid and cannot be deserialised.");
} return null;
}

因为我在项目中减少了第三方库的依赖,并且有更好的性能,我更喜欢用 System.Text.Json,虽然这块代码非常简单,但是还有更好的方案,从简洁代码的角度来看,到现在为止最好的选择是使用 Microsoft.AspNet.WebApi.Client 提供的扩展方法。

System.Net.Http.Json 介绍

我从今年2月份一直在关注这个库,以及首次在 github 显示的设计文档和问题,这些需求和建议的API都可以在设计文档中找到。

客户端从网络上对 JSon 内容序列化和反序列化是非常常见的操作,特别是即将到来的Blazor环境,现在,发送数据到服务端,需要写多行繁琐的代码,对使用者来说非常不方便,我们想对 HttpClient 扩展,允许做这些操作就像调用单个方法一样简单

你可以在github阅读完整的设计文档,团队希望构建一个更加方便的独立发布的库,来在 HttpClient 和 System.Text.Json 使用,也可以在Blazor 中使用这些API。

这些初始化的工作已经由微软的 David Cantu 合并到项目,准备接下来的 Blazor,现在已经是.NET 5 BCL(基础库)的一部分,所以这是我为什么一直在提 System.Net.Http.Json,现在你可以在 Nuget 下载安装,接下来,我会探讨下支持的主要的API和使用场景。

使用 HttpClient 发送和接收Json数据

下边的一些代码和示例我已经上传到了这里 https://github.com/stevejgordon/SystemNetHttpJsonSamples

这第一步是包添加到您的项目,你可以使用NuGet包管理器或者下边的命令行安装

dotnet add package System.Net.Http.Json

使用 HttpClient 获取Json数据

让我们先看一个扩展方法HttpClient,这很简单

private static async Task<User> GetJsonHttpClient(string uri, HttpClient httpClient)
{
try
{
return await httpClient.GetFromJsonAsync<User>(uri);
}
catch (HttpRequestException) // Non success
{
Console.WriteLine("An error occurred.");
}
catch (NotSupportedException) // When content type is not valid
{
Console.WriteLine("The content type is not supported.");
}
catch (JsonException) // Invalid JSON
{
Console.WriteLine("Invalid JSON.");
} return null;
}

在代码第5行,传入泛型调用 GetFromJsonAsync 来反序列化 Json 内容,方法传入一个uri地址,这是我们所需要的,我们操作了一个 Http Get请求到服务端,然后获取响应反序列化到 User 实体,这很简洁,另外上边有详细的异常处理代码,在各种条件下来抛出异常

跟最上面的代码一样,使用 EnsureSuccessStatusCode 来判断状态码是否成功,如果状态码在 200-299 之外,会抛出异常

并且这个库还会检查是不是有效的媒体类型,比如 application/json, 如果媒体类型错误,将抛出 NotSupportedException,这里的检查比我上边手动处理的代码更加完整,如果媒体类型不是 application/json,则会对值进行基于Span的解析, 所以 application/<something>+json 也是有效的格式

这种格式是现在经常使用的,另外一个例子,可以发现这个库对于标准和细节的处理,RFC7159 标准 定义一种携带机器可读的HTTP响应中的错误,比如 application/problem+json, 我手写的代码没有处理和匹配这些,因为 System.Net.Http.Json 已经做了这些工作

在内部,ResponseHeadersRead HttpCompletionOption 用来提升效率,我最近的文章有这个的介绍,这个库已经处理好了 HttpResponseMessage,使用这个Option是必需的

转码

最后这个库的实现细节, 包括支持代码转换返回的数据,如果不是utf-8,utf-8应该在绝大多数情况下的标准,然而,如果 content-type 报头中包含的字符集标识不同的编码,将使用TranscodingStream 尝试反序列化成 utf-8

从HttpContent 处理Json

在某些情况下,您可能想要发送请求的自定义 Header , 或者你想反序列化之前检查 Response Header,这也可以使用 System.Net.Http.Json 提供的扩展方法

private static async Task<User> GetJsonFromContent(string uri, HttpClient httpClient)
{
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.TryAddWithoutValidation("some-header", "some-value"); using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); if (response.IsSuccessStatusCode)
{
// perhaps check some headers before deserialising try
{
return await response.Content.ReadFromJsonAsync<User>();
}
catch (NotSupportedException) // When content type is not valid
{
Console.WriteLine("The content type is not supported.");
}
catch (JsonException) // Invalid JSON
{
Console.WriteLine("Invalid JSON.");
}
} return null;
}

发送Json数据

最后一个示例我们使用 HttpClient 来发送Json数据,看一下下边我们的两种实现

private static async Task PostJsonHttpClient(string uri, HttpClient httpClient)
{
var postUser = new User { Name = "Steve Gordon" }; var postResponse = await httpClient.PostAsJsonAsync(uri, postUser); postResponse.EnsureSuccessStatusCode();
}

第一个方法是使用 PostAsJsonAsync 扩展方法,把对象序列化成 Json 请求到服务端,内部会创建一个 HttpRequestMessage 和 序列化成内容流

还有一种情况需要手动创建一个 HttpRequestMessage, 也许包括自定义请求头,你可以直接创建 JsonContent

private static async Task PostJsonContent(string uri, HttpClient httpClient)
{
var postUser = new User { Name = "Steve Gordon" }; var postRequest = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = JsonContent.Create(postUser)
}; var postResponse = await httpClient.SendAsync(postRequest); postResponse.EnsureSuccessStatusCode();
}

在上边的代码中,我们创建了一个 JsonContent, 传入一个对象然后序列化,JsonContent 是 System.Net.Http.Json 库中的类型,内部它会使用 System.Text.Json 来进行序列化

总结

在这篇文章中,我们回顾了一些传统的方法,可以用来从HttpResponseMessage 来反序列化对象,我们看到,当手动调用api来解析JSON, 我们首先需要考虑比如响应状态是成功的, 并且是我们需要的媒体类型, Microsoft.AspNet.WebApi.Client 提供的 ReadAsAsync 方法,内部是使用 Newtonsoft.Json 来基于流的反序列化

我们的结论是使用新的 System.Net.Http.Json, 它会使用 System.Text.Json 来进行Json的序列化和反序列化,不依赖于第三方库 Newtonsoft.Json, 使用这个库提供的扩展方法,通过很简洁的代码就可以通过HttpClient 来发送和接收数据,并且有更好的性能表现,最后,你可以在这里找到本文的一些代码 https://github.com/stevejgordon/SystemNetHttpJsonSamples

最后

欢迎扫码关注我们的公众号,专注国外优秀博客的翻译和开源项目分享,也可以添加QQ群 897216102

「译」使用 System.Net.Http.Json 高效处理Json的更多相关文章

  1. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  2. jvm系列(七):如何优化Java GC「译」

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...

  3. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  4. iOS 9,为前端世界都带来了些什么?「译」 - 高棋的博客

    2015 年 9 月,Apple 重磅发布了全新的 iPhone 6s/6s Plus.iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9(是的,这货居然是第 9 版),加上已经 ...

  5. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  6. 「译」JavaScript 的怪癖 1:隐式类型转换

    原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 ...

  7. 「译」forEach循环中你不知道的3件事

    前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in ...

  8. 「译」JUnit 5 系列:环境搭建

    原文地址:http://blog.codefx.org/libraries/junit-5-setup/ 原文日期:15, Feb, 2016 译文首发:Linesh 的博客:环境搭建 我的 Gith ...

  9. 「译」如何正确学习JavaScript

    原文:How to Learn JavaScript Properly 目录 不要这样学习JavaScript 本课程资源 1-2周(简介,数据类型,表达式和操作符) 3~4周(对象,数组,函数,DO ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:QDockWidget停靠部件的dockWidgetArea和docked属性

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 dockWidgetArea和docked属性这两个属性在Design ...

  2. PyQt(Python+Qt)学习随笔:QStandardItemModel指定行和列创建模型后的数据项初始化的两种方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QStandardItemModel通过构造方法 QStandardItemModel(int ro ...

  3. 第一章、PyQt的简介、安装与配置

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 第一章.PyQt的简介.安装与配置 一.引言 当朋友向我推荐PyQt时,老猿才知道有这样一个在Pyt ...

  4. PyQt(Python+Qt)学习随笔:Qt Designer中部件的enabled属性

    enabled属性非常简单,最开始老猿没准备介绍该属性的,因为大家都应该知道,但仔细看了看官网文章,觉得还是有些细节可能很少有人注意到,因此还是在此介绍一下. enabled属性用于表示部件是否可用, ...

  5. 抖音CK备份上号原理

    抖音CK备份和上号是点赞跳频繁上号的最好方式,不会的可以访问网站:rz3w.com,下面介绍备份还原的原理:public void run() { MainActivity.a(this.c); ne ...

  6. Dr.COM获取用户属性超时!请检查防火墙配置允许UDP 61440端口。怎么解决

    最近校园网老是出问题,看到好多同学都遇到了下面的问题,我就来说一下我的解决方法.(目前我认识的有三个同学遇到了这样的情况,用这个方法都解决了,但不一定对每个人都有效) 首先登陆net.scut.edu ...

  7. THE BUG 队第一次团队作业

    1.队名: THE BUG 队 2.队员学号: 杨梓琦 3118005115(队长) 温海源,3118005109 陈杰才,3118005089 李华,3118005097 钟明康,311800512 ...

  8. Java 线程安全问题的本质

    原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: 线程安全问题的本质 理解CPU JVM虚拟机类比于操作系统 重排序 汇总 一些解释 ...

  9. AcWing 180. 排书

    AStar 最坏情况\(O(log_2560 ^ 4)\) 用\(AStar\)算法做了这题,程序跑了\(408ms\). 相比于\(IDA*\)的\(100ms\)左右要慢上不少. 且\(A*\)由 ...

  10. MVC部署出现403.14问题记录

    1.问题截图 2.解决办法有几种 1)在system.webServer下增加,这种是不推荐的. <modules runAllManagedModulesForAllRequests=&quo ...