使用自定义DelegatingHandler编写更整洁的Typed HttpClient
使用自定義DelegatingHandler編寫更整潔的Typed HttpClient
簡介
我寫了很多HttpClient[1],包括類型化的客戶端。自從我發現Refit[2]以來,我只使用了那一個,所以我只編寫了很少的代碼!但是我想到了你!你們中的某些人不一定會使用Refit,[3]因此,我將為您提供一些技巧,以使用HttpClient消息處理程序[4](尤其是DelegatingHandlers)[5]編寫具有最大可重用性的類型化HttpClient[6]。
編寫類型化的HttpClient來轉發JWT并記錄錯誤
這是要整理的HttpClient[7]:
using DemoRefit.Models; using DemoRefit.Repositories; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks;namespace DemoRefit.HttpClients {public class CountryRepositoryClient : ICountryRepositoryClient{private readonly HttpClient _client;private readonly IHttpContextAccessor _httpContextAccessor;private readonly ILogger<CountryRepositoryClient> _logger;public CountryRepositoryClient(HttpClient client, ILogger<CountryRepositoryClient> logger, IHttpContextAccessor httpContextAccessor){_client = client;_logger = logger;_httpContextAccessor = httpContextAccessor;}public async Task<IEnumerable<Country>> GetAsync(){try{string accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");if (string.IsNullOrEmpty(accessToken)){throw new Exception("Access token is missing");}_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);var headers = _httpContextAccessor.HttpContext.Request.Headers;if (headers.ContainsKey("X-Correlation-ID") && !string.IsNullOrEmpty(headers["X-Correlation-ID"])){_client.DefaultRequestHeaders.Add("X-Correlation-ID", headers["X-Correlation-ID"].ToString());}using (HttpResponseMessage response = await _client.GetAsync("/api/democrud")){response.EnsureSuccessStatusCode();return await response.Content.ReadAsAsync<IEnumerable<Country>>();}}catch (Exception e){_logger.LogError(e, "Failed to run http query");return null;}}} }這里有許多事情需要清理,因為它們在您將在同一應用程序中編寫的每個客戶端中可能都是多余的:
?從HttpContext讀取訪問令牌?令牌為空時,管理訪問令牌?將訪問令牌附加到HttpClient[8]進行委派?從HttpContext讀取CorrelationId?將CorrelationId附加到HttpClient[9]進行委托?使用EnsureSuccessStatusCode()驗證Http查詢是否成功
編寫自定義的DelegatingHandler來處理冗余代碼
這是DelegatingHandler[10]:
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks;namespace DemoRefit.Handlers {public class MyDelegatingHandler : DelegatingHandler{private readonly IHttpContextAccessor _httpContextAccessor;private readonly ILogger<MyDelegatingHandler> _logger;public MyDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<MyDelegatingHandler> logger){_httpContextAccessor = httpContextAccessor;_logger = logger;}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){HttpResponseMessage httpResponseMessage;try{string accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");if (string.IsNullOrEmpty(accessToken)){throw new Exception($"Access token is missing for the request {request.RequestUri}");}request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);var headers = _httpContextAccessor.HttpContext.Request.Headers;if (headers.ContainsKey("X-Correlation-ID") && !string.IsNullOrEmpty(headers["X-Correlation-ID"])){request.Headers.Add("X-Correlation-ID", headers["X-Correlation-ID"].ToString());}httpResponseMessage = await base.SendAsync(request, cancellationToken);httpResponseMessage.EnsureSuccessStatusCode();}catch (Exception ex){_logger.LogError(ex, "Failed to run http query {RequestUri}", request.RequestUri);throw;}return httpResponseMessage;}} }如您所見,現在它封裝了用于同一應用程序中每個HttpClient[11]的冗余邏輯 。
現在,清理后的HttpClient[12]如下所示:
using DemoRefit.Models; using DemoRefit.Repositories; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks;namespace DemoRefit.HttpClients {public class CountryRepositoryClientV2 : ICountryRepositoryClient{private readonly HttpClient _client;private readonly ILogger<CountryRepositoryClient> _logger;public CountryRepositoryClientV2(HttpClient client, ILogger<CountryRepositoryClient> logger){_client = client;_logger = logger;}public async Task<IEnumerable<Country>> GetAsync(){using (HttpResponseMessage response = await _client.GetAsync("/api/democrud")){try{return await response.Content.ReadAsAsync<IEnumerable<Country>>();}catch (Exception e){_logger.LogError(e, "Failed to read content");return null;}}}} }好多了不是嗎?????
最后,讓我們將DelegatingHandler[13]附加到Startup.cs中的HttpClient[14]:
using DemoRefit.Handlers; using DemoRefit.HttpClients; using DemoRefit.Repositories; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Refit; using System;namespace DemoRefit {public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.AddHttpContextAccessor();services.AddControllers();services.AddHttpClient<ICountryRepositoryClient, CountryRepositoryClientV2>().ConfigureHttpClient(c => c.BaseAddress = new Uri(Configuration.GetSection("Apis:CountryApi:Url").Value)).AddHttpMessageHandler<MyDelegatingHandler>();}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}} }使用Refit
如果您正在使用Refit[15],則絕對可以重用該DelegatingHandler[16]!
例:
using DemoRefit.Handlers; using DemoRefit.HttpClients; using DemoRefit.Repositories; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Refit; using System;namespace DemoRefit {public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.AddHttpContextAccessor();services.AddControllers();services.AddRefitClient<ICountryRepositoryClient>().ConfigureHttpClient(c => c.BaseAddress = new Uri(Configuration.GetSection("Apis:CountryApi:Url").Value));.AddHttpMessageHandler<MyDelegatingHandler>();}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}} }輪子介紹:
Refit是一個深受Square的 Retrofit 庫啟發的庫,目前在github上共有star 4000枚,通過這個框架,可以把你的REST API變成了一個活的接口:
public interface IGitHubApi {[Get("/users/{user}")]Task<User> GetUser(string user); }RestService類生成一個IGitHubApi的實現,它使用HttpClient進行調用:
var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com");var octocat = await gitHubApi.GetUser("octocat");查看更多:https://reactiveui.github.io/refit/
References
[1]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[2]?Refit:?https://github.com/reactiveui/refit
[3]?Refit,:?https://github.com/reactiveui/refit
[4]?HttpClient消息處理程序:?https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/httpclient-message-handlers
[5]?DelegatingHandlers):?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=netframework-4.8
[6]?類型化HttpClient:?https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
[7]?鍵入的HttpClient:?https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
[8]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[9]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[10]?DelegatingHandler:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=netframework-4.8
[11]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[12]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[13]?DelegatingHandler:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=netframework-4.8
[14]?HttpClient:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.0
[15]?Refit:?https://github.com/reactiveui/refit
[16]?DelegatingHandler:?https://docs.microsoft.com/en-us/dotnet/api/system.net.http.delegatinghandler?view=netframework-4.8
?
總結
以上是生活随笔為你收集整理的使用自定义DelegatingHandler编写更整洁的Typed HttpClient的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【壹刊】Azure AD B2C(一)初
- 下一篇: Polly:提升服务可用性