0%

[DotnetCore]Refit初體驗

前情提要

筆者在公司負責的專案,大多數都跟其他廠商串接有關,串接方式百百種,最多就是Http Request了,因新專案已都使用dotnet 6來開發,透過AddHttpClient註冊,注入HttpClient,再將HttpRequest的實作封裝成HttpClientRepository,確實都滿順利的,但光HttpClient串接也是有百百種,有的廠商要FormPost方式送出RequestContent,有的廠商則一般常見的將RequestContent放在Body,格式方面有些則json格式,有些則xml等等,組合也是挺可怕的多,要一直調整其底層共用程式HttpClientRepository

再則筆者在實作中遇到一個滿常見的問題,Header重複設定的錯誤,因Http連線成本確實不低,因此透過AddHttpClient使用HttpClient,希望由dotnet底層管理連線,降低其連線成本,以常見的Header設定來說,就屬Authorization了,筆者這邊為了加速往廠商端的查詢,會透過Parallel的方式併發出Request,全部Response都回來後才做商業邏輯處理,因此第一次連線後Header尚未設定的情況下,有一定的機率兩組執行緒碰撞一次,也有一定的機率兩組同時進到判斷式,要新增Header的值,搞得筆者連Lock機制都用上了,目前尚未找到更好的解,只能先這樣暴力解了。最後筆者自己本身在Linqpad上先行測試時會使用HttpRequest相關套件,例:RestSharp,心血來潮認真看了下其API,看到有Headers章節中的API: AddOrUpdateHeader,覺得完全是解決筆者的痛點,突然有一種HttpRequest應該是一個基礎建設,若能有一個套件幫忙實作完成,就可以專心地將所有精力投入在商業邏輯層面就好,進而縮短開發時間,然而某天在LinkedIn貼文上看到一個套件叫[Refit](https://github.com/reactiveui/refit),不查還好,查完後覺得就它了。

內容

筆者主要選擇使用Refit,有以下原因:

  • 可透過注入的方式取得(底層亦是透過HttpClientFactory取得HttpClient物件)
  • 可繼續沿用客製化HttpMessageHandler
  • 可繼續沿用Polly機制
  • 最棒的一點,要增加不同廠商的API串接,多一組Interface宣告即可,終於不用再調整HttpClient底層邏輯了

因此目前筆者開發串接廠商API的流程大致上如下:

  • 觀看廠商提供的API文件,若有範例,則透過Postman試打
  • API文件或Postman中的Response Sample丟至ChatGPT轉成C# class
  • 定義對應的Refit API interface
  • Startup中註冊其服務

筆者這篇文章內容則透過政府公開平台OpenData的API為例,示範怎麼使用,因簡單示範,就透過Linqpad來完成,但因Refit必須要有產生虛擬代碼,Linqpad上面要做一點手腳才有辦法進行,剛好筆者有看到解法

http://share.linqpad.net/xn3ac8.linq

載入套件

筆者就照Liqnpad引用套件流程套用Refit

宣告API

筆者就省略上方列出的前兩個步驟,假設已拿到C# class內容,先宣告QueryParameters

1
2
3
4
5
public class LandPriceParameters
{
public string City { get; set; }
public string Sec { get; set; }
}

再宣告ResponseModel,Refit預設會透過System.Text.Json做序列化及反序列化,切記套用的Json相關Attribute,必須為System.Text.Json下的

1
2
3
4
5
6
7
8
9
public class LandData
{
[JsonPropertyName("LANDNO")]
public string LandNo { get; set; }
[JsonPropertyName("LANDPRICE")]
public string LandPrice { get; set; }
[JsonPropertyName("CURRENTVALUE")]
public string CurrentValue { get; set; }
}

最後宣告主體,API規格,只要定義就等同寫完串接API

[Get("/WEBAPI/LandPrice/Lastest")] Route中的第一個/非常重要,一定要加,不然會爆錯

1
2
3
4
5
public interface OpenDataApiService
{
[Get("/WEBAPI/LandPrice/Lastest")]
Task<List<LandData>> GetLandPriceAsync([Query]LandPriceParameters parameters);
}

使用方式

筆者示範使用Liqnpad工具呈現,需參考上方所列的linq檔案

1
2
3
4
5
6
7
8
9
10
11
12
13
//為上方所列之linq檔案載入
#load "xn3ac8"

async Task Main()
{
var baseUrl = @"https://openapi.land.moi.gov.tw";
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);
// ProxyRestService即最上面加入的load參考中的物件
var apiService = ProxyRestService.For<OpenDataApiService>(httpClient);
var response = await apiService.GetLandPriceAsync(new LandPriceParameters(){ City = "H", Sec = "0001" });
response.Dump();
}

跑完後的結果如下(僅截取部份資料):

LandNo LandPrice(元/平方公尺) CurrentValue(元/平方公尺)
00760050 81685
00810017 81685
00720014 71400
00720015 81685
00730003 163540
00740001 166591

結論

到這邊是不是覺得呼叫HttpRequest很簡單呢,當然不管是否自己實作HttpClient呼叫,或使用Refit完成HttpRequest,前面轉C# Class是避免不了的,後面測試也是免不了的,其中最大的好處還是在於HttpClient呼叫的基礎建設方法,不用因為你多串一個新的廠商,與以往不同而調整基礎建設,可以順順地依照筆者上方列出的串接廠商API流程完成串接,多了很多處理商業邏輯的時間,覺得CP值非常高,下一篇將介紹Dotnet Core專案套用Refit的流程。

參考