前情提要
筆者在 [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();
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.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
,因此想盡辦法把Request
,Response
倒出來記錄
ApiRequestPipelineModel
: 為串起一個Request
中的所有ServiceRecord
,透過AddScoped
特性將取得統一的RequestId
ITimeWrapper
:統一取得時間的Service
IApiLogRepository
: ApiLog DB Acess
用Repository
IHttpContextAccessor
: 這個下一篇會詳細解釋原因,為第一項ApiRequestPipelineModel
中的RequestId
而特地引用
註冊自定義HttpMessageHandler
套用方式也非常簡單
- 首先要安裝
Microsoft.Extensions.Http
這個nuget
套件
- 最後只要接在
AddRefitClient()
後面即可,ExtensionMethod
為AddHttpMessageHandler
完整註冊程式碼如下
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
- 接下來會介紹的
HttpRequest
與Polly
的結合
此篇就到這邊了,下篇見。
參考