.NET Core 中正確使用 HttpClient 的姿勢
為了更方便在服務端呼叫 HTTP 請求,微軟在 .NET Framework 4.x 的時候引入了 HttpClient。但 HttpClient 有很多嚴重問題,一直飽受詬病,比如 InfoQ 的這篇文章 t.cn/Evzy80y,吐槽了 HttpClient 不能立即關閉連線、效能消耗嚴重等的問題。
.NET Core 2.1 開始引入的 HttpClientFactory 解決了 HttpClient 的所有痛點。有了 HttpClientFactory,我們不需要關心如何建立 HttpClient,又如何釋放它。通過它可以建立具有特定業務的 HttpClient,而且可以很友好的和 DI 容器結合使用,更為靈活。下面以 ASP.NET Core 為例介紹 HttpClient 新的三種正確使用方式。
一、直接使用方式
不管是哪種方式,都要先註冊服務,對於 ASP.NET Core 應用則是在 Startup.cs
檔案的 ConfigureServices
新增如果程式碼:
services.AddHttpClient();
然後在 Controller 中通過構造注入的試獲得 IHttpClientFactory
,然後通過它來建立 HttpClient 物件。示例程式碼:
public class ValuesController : BaseController{ private readonly IHttpClientFactory _httpClientFactory; public ValuesController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpGet] public async Task<ActionResult> Get() { var client = _httpClientFactory.CreateClient(); client.BaseAddress = new Uri("http://api.github.com"); string result = await client.GetStringAsync("/"); return Ok(result); }}
這種使用方式適合一次性的 HTTP 請求呼叫,弊端是如果多次都要請求 github 的介面,那就得寫很多重複程式碼配置 HttpClient。
二、命名式使用方式
命名式的使用方式可以解決第一種方式的問題,對於特定域名的 HTTP 呼叫,可以只做一次配置,然後在多個地方通過名稱拿到相同的 Client 來使用。
首先在 Startup.cs
註冊服務的地方新增帶有特定名稱的 HttpClient 服務,並新增需要的配置,如下示例:
services.AddHttpClient();services.AddHttpClient("github", c =>{ c.BaseAddress = new Uri("https://api.github.com/"); c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");});
注意這裡添加了兩次 AddHttpClient
,一次是沒有命名的,一次是有命名的。在 Controller 中的使用方式是:
public class ValuesController : BaseController{ private readonly IHttpClientFactory _httpClientFactory; public ValuesController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpGet] public async Task<ActionResult> Get() { var client = _httpClientFactory.CreateClient("github"); string result = await client.GetStringAsync("/"); return Ok(result); }}
但如果使用很頻繁,這種獲取 Client 的方式還是有一點麻煩。下面介紹第三種方式,可以在使用的時候方便一點,但配置要麻煩些。
三、型別化使用方式
型別化的使用方式可以預先把配置放到自定義的 HttpClient 中,然後在需要的地方都過依賴注入直接拿到 HttpClient 例項。
我們來看具體的例子,首先自定義一個 HttClient 類,比如訪問 github 介面的類可以這樣定義:
public class GitHubClient{ public HttpClient Client { get; private set; } public GitHubClient(HttpClient httpClient) { httpClient.BaseAddress = new Uri("https://api.github.com/"); httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); Client = httpClient; } public async Task<string> GetData() { return await _client.GetStringAsync("/"); }}
這裡的 GetData
就是額外擴充套件的共用方法。使用前需要為這個類註冊服務:
services.AddHttpClient<GitHubClient>();
然後在需要的地方(比如 Controller)中這樣使用:
public class ValuesController : BaseController{ private readonly GitHubClient _gitHubClient;; public ValuesController(GitHubClient gitHubClient) { _gitHubClient = gitHubClient; } [HttpGet] public async Task<ActionResult> Get() { string result = await _gitHubClient.GetData(); return Ok(result); }}
型別化的方式和命名式的方式相比,雖然在配置的時候會麻煩一點,但它有兩個好處:一是不用通過名稱字串來獲得 Client,而是直接通過依賴注入獲得具體的 Client;二是可以在自定義的 HttpClient 類中擴充套件共用的方法。