在這篇文章,我將介紹一個名爲 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