0%

[DotnetCore]Refit:HttpMessageHandler應用

前情提要

筆者在 [DotnetCore]Refit初體驗那篇中有說到,選擇的Refit的原因有其中一項是可以繼續沿用自定義HttpMessageHandler,此篇就以Refit註冊的同時也掛上自定義HttpMessageHandler的教學。

筆者這邊就以ApiLog這個話題延續,就以實作ApiLog為主去完成自定義HttpMessageHandler,大概邏輯就是

  • 依照Request,新增ApiLog,取得PK欄位值
  • 執行HttpRequest
  • 依照Response及第一步驟PK欄位值,更新ApiLog

撰寫自定義HttpMessageHandler

取得Request

目前這部份會分幾個步驟,首先是解析Request,組ApiLog所需欄位

1
2
3
4
var bodyContent = request.Content == null ? "" : await request.Content.ReadAsStringAsync();
// 以下僅供參考:組出ApiLog,並新增至DB
var apiLog = new ApiLog(requestId, $"{request.RequestUri}", bodyContent, now);
apiLog = await _apiLogRepo.AddApiLog(apiLog);

執行HttpRequest

這邊就單純了,畢竟沒有要特別做任何事,直接執行base的方法即可

1
var response = await base.SendAsync(request, cancellationToken);

取得Response並更新ApiLog

這邊主要是讀出Response結果,更新ApiLog

1
2
3
4
var responseResult = response.Content == null ? "" : await response.Content.ReadAsStringAsync();
// 以下僅供參考:更新ApiLog及更新DB(主要紀錄Response中的StatusCode、response內容及response收到時間
apiLog.UpdateResponseInfo(((int)response.StatusCode).ToString(), responseResult, _timeWrapper.Now);
await _apiLogRepo.UpdateApiLog(apiLog);

最後完整的自定義HttpMessageHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MyHttpMessageHandler : DelegatingHandler
{
private ApiRequestPipelineModel _pipeline = default;
private readonly ITimeWrapper _timeWrapper;
private readonly IApiLogRepository _apiLogRepo;
private readonly IHttpContextAccessor _contextAccessor;
public MyHttpMessageHandler(ApiRequestPipelineModel pipeline, ITimeWrapper timeWrapper, IApiLogRepository apiLogRepo, IHttpContextAccessor contextAccessor)
{
_pipeline = pipeline;
_timeWrapper = timeWrapper;
_apiLogRepo = apiLogRepo;
_contextAccessor = contextAccessor;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var pipelineModel = _contextAccessor?.HttpContext?.RequestServices.GetRequiredService<ApiRequestPipelineModel>();
if(pipelineModel != null) { _pipeline = pipelineModel; }
var bodyContent = request.Content == null ? "" : await request.Content.ReadAsStringAsync();
var apiLog = new ApiLog(_pipeline.RequestId, $"{request.RequestUri}", bodyContent, _timeWrapper.Now);
apiLog = await _apiLogRepo.AddApiLog(apiLog);

var response = await base.SendAsync(request, cancellationToken);

var responseResult = response.Content == null ? "" : await response.Content.ReadAsStringAsync();
apiLog.UpdateResponseInfo(((int)response.StatusCode).ToString(), responseResult, _timeWrapper.Now);
await _apiLogRepo.UpdateApiLog(apiLog);

return response;
}
}

裡面用到的部份,簡單解釋一下,僅供參考,還是要依照各位環境中實際應用到的方式撰寫,筆者這邊環境就是ApiLog,因此想盡辦法把RequestResponse倒出來記錄

  • ApiRequestPipelineModel : 為串起一個Request中的所有ServiceRecord,透過AddScoped特性將取得統一的RequestId
  • ITimeWrapper :統一取得時間的Service
  • IApiLogRepository : ApiLog DB AcessRepository
  • IHttpContextAccessor : 這個下一篇會詳細解釋原因,為第一項ApiRequestPipelineModel中的RequestId而特地引用

註冊自定義HttpMessageHandler

套用方式也非常簡單

  • 首先要安裝Microsoft.Extensions.Http這個nuget套件
  • 最後只要接在AddRefitClient()後面即可,ExtensionMethodAddHttpMessageHandler

完整註冊程式碼如下

1
2
3
4
5
6
services.AddRefitClient<ITestApiRepository>()
.ConfigureHttpClient(x =>
{
x.BaseAddress = new Uri(config["BaseUrl:Test"]);
})
.AddHttpMessageHandler<MyHttpMessageHandler>();//主要是這行

結論

之所以Refit好用的地方在於,不與HttpClient擁有的延伸方法衝突,可繼續使用,不會破壞原有的架構,但發HttpRequest變得更簡易了,不管是撰寫上,或者抓Error上,列幾個筆者喜歡的延伸方法

  • [DotnetCore]Refit初體驗 提到的透過 `HttpClientFactory` 取得 `HttpClient` 實體
  • 就像本篇可以繼續使用AddHttpMessageHandler方法註冊其Handler
  • 接下來會介紹的HttpRequestPolly的結合

此篇就到這邊了,下篇見。

參考