在这篇文章,我将介绍一个名为 System.Net.Http.Json 的扩展库,它最近添加到了 .NET 中,咱们看一下这个库可以给咱们解决什么问题,今天会介绍下如何在代码中使用。git
JSON是一种广泛和流行的串行化格式数据来发送现代web api,我常常在个人项目中使用HttpClient 调用外部资源, 当 content type 是 “application/json”, 我拿到Json的响应内容后,我须要手动处理响应,一般会验证响应状态代码是否为200,检查内容是否是为空,而后再试图从响应内容流反序列化github
若是咱们使用 Newtonsoft.Json, 代码多是像下边这样web
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
你们可能一般也会把 Json 序列化成 String,在 HttpClient 的 HttpContent 中调用GetStringAsync
ReadAsStringAsync
,能够直接使用 Newtonsoft.Json 和 System.Text.Json,如今的一个问题是咱们须要多分配一个包含整个Json 数据的 String,这样会存在浪费,由于咱们看上面的代码已经有一个可用的响应流,能够直接反序列化到实体,经过使用流,也能够进一步提升性能,在个人另外一篇文章里, 能够利用HttpCompletionOption来改善HttpClient性能。api
若是您在过去在项目中使用过 HttpClient 来处理返回的Json数据,那么您可能已经使用了Microsoft.AspNet.WebApi.Client
。我在过去使用过它,由于它提供了有用的扩展方法来支持从HttpResponseMessage上的内容流进行高效的JSON反序列化,这个库依赖于Newtonsoft.Json文件并使用其基于流的API来支持数据的高效反序列化,这是一个方便的库,我用了几年了网络
若是咱们在项目中使用这个库,上面的代码能够减小一些app
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
今天,我更倾向于使用 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
提供的扩展方法。post
我从今年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和使用场景。
下边的一些代码和示例我已经上传到了这里 https://github.com/stevejgordon/SystemNetHttpJsonSamples
这第一步是包添加到您的项目,你可使用NuGet包管理器或者下边的命令行安装
dotnet add package System.Net.Http.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
在某些状况下,您可能想要发送请求的自定义 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; }
最后一个示例咱们使用 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