.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 容器結合使用,更為靈活。下面以
http://
ASP。NET
Core 為例介紹 HttpClient 新的三種正確使用方式。
一、直接使用方式
不管是哪種方式,都要先註冊服務,對於
http://
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
然後在需要的地方(比如 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 類中擴充套件共用的方法。