前情提要
筆者在 [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的結合
此篇就到這邊了,下篇見。
參考