原文:https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore 發表於:2018年1月 原文:https://www.stevejgordon.co.uk/httpclientfactory-nam ...
原文:https://www.stevejgordon.co.uk/httpclientfactory-named-typed-clients-aspnetcore
發表於:2018年1月
上一篇文章《HttpClientFactory簡介》我解釋了創建該功能的原因。我們知道了它可以解決的問題,然後例舉了一個非常基本的示例展示瞭如何在WebAPI應用程式中使用它。在這篇文章中,我想深入探討另外兩種可以使用它的方法:命名化客戶端(named clients)和類型化客戶端(typed clients)。
命名化客戶端(Name Clients)
在第一篇文章中,我演示瞭如何使用HttpClientFactory來獲取基本的HttpClient實例。當您只需要從單一位置發出快速請求時,這很好。但通常,您可能希望從代碼中的多個位置向同一服務發出多個請求。
通過命名化客戶端的概念,HttpClientFactory使這一點變得更容易。使用命名化客戶端,您可以創建一個註冊,其中包含在創建HttpClient時的一些特定配置。您可以註冊多個命名化客戶端,每個客戶端都可以預先配置不同的設置。
為了讓這個概念更具體一些,讓我們看一個例子。在我的Startup.ConfigureServices方法中,使用AddHttpClient的不同重載方法,該方法接受兩個附加參數。把一個名稱和一個Action委托“告訴”HttpClient。ConfigureServices代碼:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("GitHubClient", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting"); }); services.AddMvc(); }
第一個字元串參數是用於此客戶端註冊的名稱。Action <HttpClient>委托允許我們在為我們構造HttpClient時配置它們。這非常方便,因為我們可以預先定義一個基地址和一些已知的請求頭。當我們請求命名化客戶端時,會為我們創建一個新客戶端,並且每次都會應用此配置。
使用的時候,CreateClient根據名稱來請求一個客戶端:
[Route("api/[controller]")] public class ValuesController : Controller { private readonly IHttpClientFactory _httpClientFactory; public ValuesController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpGet] public async Task<ActionResult> Get() { var client = _httpClientFactory.CreateClient("GitHubClient"); var result = await client.GetStringAsync("/"); return Ok(result); } }
在這個例子中,我們創建的HttpClient實例已經有基本地址集(base address set),所以我們的GetStringAsync方法傳入對應的URI即可。
這種命名化的方式使我們能夠控制應用於HttpClient的配置。我不是“魔力字元串”的忠實粉絲,所以如果我使用命名客戶端,我可能會有一個靜態類,其中包含客戶端名稱的字元串常量。像這樣:
public static class NamedHttpClients { public const string GitHubClient = "GitHubClient"; }
註冊(或請求)客戶端時,我們可以使用靜態類值,而不是“魔力字元串”:
services.AddHttpClient(NamedHttpClients.GitHubClient, client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting"); });
這非常好,但我們可以更進一步,來看看如何使用自定義的類型化客戶端。
類型化客戶端(Typed Clients)
類型化客戶端允許我們定義一個通過構造函數註入HttpClient的自定義類。這樣我們可以使用IHttpClientBuilder的擴展方法鏈接DI系統,或者使用泛型AddHttpClient方法來接收自定義類型。一旦我們有了自定義類,我們就可以直接公開HttpClient,也可以將Http calls封裝在特定方法中,從而更好地定義外部服務的使用。這種方法也意味著我們不再需要“魔術字元串”,並且看起來更加合理。
讓我們看一個自定義類型化客戶端的基本例子:
public class MyGitHubClient { public MyGitHubClient(HttpClient client) { Client = client; } public HttpClient Client { get; } }
這個類需要在構造函數中接受作為參數的HttpClient。現在,我們已經為HttpClient的實例設置了一個公共屬性。
然後,我們需要在ConfigureServices中註冊:
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient<MyGitHubClient>(client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting"); }); services.AddMvc(); }
我們將MyGitHubClient作為泛型參數傳遞給AddHttpClient。它在DI系統中被註冊為transient scope。由於我們的自定義類接受HttpClient,因此相關聯的“工廠”會創建一個適當配置的HttpClient實例,並註入它。現在可以更新控制器以接受我們的類型化客戶端而不是IHttpClientFactory:
[Route("api/[controller]")] public class ValuesController : Controller { private readonly MyGitHubClient _gitHubClient; public ValuesController(MyGitHubClient gitGitHubClient) { _gitHubClient = gitGitHubClient; } [HttpGet] public async Task<ActionResult> Get() { var result = await _gitHubClient.Client.GetStringAsync("/"); return Ok(result); } }
由於我們自定義的類型化客戶端通過屬性公開了HttpClient,因此我們可以直接使用它進行HTTP調用。
封裝HttpClient(Encapsulating the HttpClient)
最後一個例子是我們想要完全封裝HttpClient的情況。當我們想要定義處理對端點的特定調用的方法時,最有可能使用此方法。此時,我們還可以在每個方法中封裝響應和反序列化的驗證,以便在單一位置處理它。
public interface IMyGitHubClient { Task<int> GetRootDataLength(); } public class MyGitHubClient : IMyGitHubClient { private readonly HttpClient _client; public MyGitHubClient(HttpClient client) { _client = client; } public async Task<int> GetRootDataLength() { var data = await _client.GetStringAsync("/"); return data.Length; } }
這種情況下,我們通過private readonly欄位存儲了在構造中註入的HttpClient。與直接通過此類(class)獲得HttpClient不同,我們提供了一個GetRootDataLength方法來執行Http調用並返回請求長度。一個簡單的例子,但你應該已經明白了!
我們現在可以更新控制器以接受和使用介面,如下所示:
[Route("api/[controller]")] public class ValuesController : Controller { private readonly IMyGitHubClient _gitHubClient; public ValuesController(IMyGitHubClient gitHubClient) { _gitHubClient = gitHubClient; } [HttpGet] public async Task<ActionResult> Get() { var result = await _gitHubClient.GetRootDataLength(); return Ok(result); } }
我們現在可以調用介面上定義的GetRootDataLength方法,而無需直接與HttpClient交互。這對測試非常有用,現在可以在我們想要測試這個控制器時輕鬆模擬IMyGitHubClient。過去測試HttpClient有點痛苦,按照我通常習慣的方式會有更多代碼。
在DI容器中註冊,ConfigureServices變為:
services.AddHttpClient<IMyGitHubClient, MyGitHubClient>(client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactoryTesting"); });
AddHttpClient有一個接受兩個泛型參數的簽名,對應DI中的簽名。
總結
在這篇文章中,我們探討了HttpClientFactory一些更高級的方法,它允許我們使用特定的命名配置創建不同的HttpClient實例。然後我們討論了使用類型化客戶端,通過擴展實現了我們自己的類,它接受HttpClient實例。我們可以直接公開HttpClient,也可以將調用封裝到此類中來訪問遠程端點。
下一篇文章,我們將討論使用DelegatingHandlers來實現“傳出請求中間件”( outgoing request middleware)的另一種模式。