[NET 5][NET Framework 4.8] 通過 Flurl 產生口語化的 HttpClient Request 參數

組合出好維護的 Http Request  這件事一直困擾著我,最近發現了一枚小工具 Flurl,使用 Flent 語法結構組合出維護性高的 Http Request

開發環境

  • Rider 2020.3.2
  • .NET 5 / .NET Framework 4.8
  • Flurl 3.0.1

 

Flurl 是甚麼?

Flurl 專案使用 Fluent Pattern (方法鏈) 設計 API,針對 string / Uri 兩個型別進行擴充,主要有以下 Library

  • Flurl:用 Fluent 優雅的組合 Url
  • Flurl.Http:發送 Http Request,底層使用 HttpClient
  • HttpTest:測試

專案:https://github.com/tmenier/Flurl

文件:https://flurl.dev/

從官網的簡介一眼就能看的出來用途

 

實作

Server

新增一個 Web API / .NET5 專案

 

然後將範本產生出的代碼改成以下

[HttpGet]
public IActionResult Get(int lessTemperature)
{
    var rng = new Random();
    var sources = Enumerable.Range(1, 5).Select(index => new WeatherForecast
                                     {
                                         Date         = DateTime.Now.AddDays(index),
                                         TemperatureC = rng.Next(-20, 55),
                                         Summary      = Summaries[rng.Next(Summaries.Length)]
                                     })
                                     .ToArray();
    var content       = sources.Where(p => p.TemperatureC < lessTemperature).ToList();
    
    var result = new ObjectResult(content)
    {
        StatusCode = 200,
    };
    return result;
}

 

這個只是為了讓 Client 測試,你可以選擇用 Web API 2 / .NET Framework 4.8

 

Client

新一個 WinFrom / .NET 5 的專案並命名為 Client.NET5

在 Terminal 輸入以下

dotnet add Client.NET5 package Flurl --version 3.0.1
dotnet add Client.NET5 package Flurl.Http --version 3.0.1

 

 

在 WinForm 使用 HttpClient 呼叫 Server 的 API,以往的寫法如下

public partial class Form1 : Form
{
    private static readonly string     BasicUrl = "https://localhost:44333/";
    private static readonly HttpClient Client;

    static Form1()
    {
        Client = new HttpClient
        {
            BaseAddress = new Uri(BasicUrl)
        };
    }

    public Form1()
    {
        this.InitializeComponent();
    }
}

 

用 HttpClient.GetAsync 取得 Web API 資源

private async void button1_Click(object sender, EventArgs e)
{
    var url      = "WeatherForecast?lessTemperature=11";
    var response = await Client.GetAsync(url);

    var content = await response.Content.ReadAsStringAsync();
    MessageBox.Show(content);
}

 

換成 Flurl 之後就變成這樣,為了演示我把組合 Url 跟送出 Http Request 的寫法分開

private async void button2_Click(object sender, EventArgs e)
{
    var url = "WeatherForecast";
    var requestUrl = BasicUrl.AppendPathSegment(url)
                             .SetQueryParam("lessTemperature", 11);
    var content           = await requestUrl.GetAsync().ReceiveString();
    MessageBox.Show(content);
}

 

合併之後變得更好閱讀了

private async void Get()
{
    var url = "WeatherForecast";
    var content = await BasicUrl.AppendPathSegment(url)
                                .SetQueryParam("lessTemperature", 11)
                                .GetAsync()
                                .ReceiveString()
        ;
    MessageBox.Show(content);
}

 

使用注意事項

Non-2XX Status

Flurl.Http 預設針對非 200 系列的 HttpStatus 會拋出 Flurl.Http.FlurlHttpException 例外,所以得針對它攔截例外;假如非 200 的錯誤不希望拋出例外的話,可以這樣做

url.AllowHttpStatus("400-404,6xx").GetAsync();
url.AllowAnyHttpStatus().GetAsync();

更多的資源請參考:https://flurl.dev/docs/error-handling/

Singleton HttpClient

底層所依賴的 HttpClent 遵守一個服務使用相同的實例,避免通訊耗盡

 

DNS Change

針對及時反應 DNS 的異動進行處理,在應用程式啟動時設定:

通過 FlurlHttp 的全域配置

FlurlHttp.Configure(settings => settings.ConnectionLeaseTimeout = TimeSpan.FromMinutes(2));

參考:https://github.com/tmenier/Flurl/issues/222

 

.NET 框架底下可以使用的物件有以下

.NET Framework 2.0 以後,使用 ServicePoint.ConnectionLeaseTimeout

ServicePointManager.FindServicePoint(new Uri(BasicUrl))
                   .ConnectionLeaseTimeout = (int) TimeSpan.FromMinutes(10).TotalMilliseconds;

ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds;

 

在 .NET Core 2.1 以後使用 SocketsHttpHandler.PooledConnectionLifetime

搭配 Flurl.Http + SocketsHttpHandler

public class ConnectionLifetimeHttpClientFactory : DefaultHttpClientFactory
{
    public override HttpMessageHandler CreateMessageHandler()
    {
        var socketsHandler = new SocketsHttpHandler
        {
            PooledConnectionLifetime    = TimeSpan.FromMinutes(10),
            PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
            MaxConnectionsPerServer     = 10
        };
        return socketsHandler;
    }
}

 

套用 ConnectionLifetimeHttpClientFactory

FlurlHttp.Configure(settings => settings.HttpClientFactory = new ConnectionLifetimeHttpClientFactory());

 

參考:https://github.com/tmenier/Flurl/issues/418

專案位置

https://github.com/yaochangyu/sample.dotblog/tree/master/Http%20Client/Lab.FluentHttpRequest

 

參考

更多的資源請參考:https://flurl.dev/docs/client-lifetime/#httpclient-lifetime-management

DNS Issues:https://github.com/tmenier/Flurl/issues?q=dns

SocketsHandler

 

 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo