深入理解.NET Core中的HttpClient及其测试

本文主要面向.NET Core开发者,特别是那些至少使用过一次HttpClient并希望对其有更深入了解的开发者。将探讨如何设置HttpClient的创建和依赖注入,以及如何通过集成测试来验证其工作方式。

HttpClient是.NET Core中用于发送HTTP请求的类。在ASP.NET Core应用中,HttpClient的实例通常通过依赖注入的方式进行管理。本文将介绍如何通过依赖注入设置HttpClient,以及如何使用DelegatingHandler来管理HttpClient的行为。

使用代码

首先,需要在ASP.NET Core应用的ConfigureServices方法中设置HttpClient的创建和依赖注入。以下是一个示例代码,展示了如何将HttpClient注入到SearchEngineService实例中,并设置两个处理程序:LogHandler和RetryHandler。

public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddTransient<LogHandler>(); services.AddTransient<RetryHandler>(); var googleLocation = Configuration["Google"]; services.AddHttpClient<ISearchEngineService, SearchEngineService>(c => { c.BaseAddress = new Uri(googleLocation); }) .AddHttpMessageHandler<LogHandler>() .AddHttpMessageHandler<RetryHandler>(); }

如代码所示,LogHandler被设置在RetryHandler之前。这意味着当HttpClient被调用时,LogHandler将是第一个处理程序,负责记录请求的响应。

LogHandler的实现

LogHandler的作用是在调用HttpClient后记录响应。以下是LogHandler的实现代码:

public class LogHandler : DelegatingHandler { private readonly ILogger<LogHandler> _logger; public LogHandler(ILogger<LogHandler> logger) { _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); _logger.LogInformation("{response}", response); return response; } }

LogHandler通过重写SendAsync方法实现日志记录功能。当HttpClient发送请求并接收响应后,LogHandler会记录响应信息。

RetryHandler的实现

RetryHandler的作用是在遇到服务器错误时重试请求。以下是RetryHandler的实现代码:

public class RetryHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage result = null; for (int i = 0; i < 3; i++) { result = await base.SendAsync(request, cancellationToken); if (result.StatusCode >= HttpStatusCode.InternalServerError) { continue; } return result; } return result; } }

RetryHandler通过在SendAsync方法中循环发送请求实现重试机制。如果请求失败(即状态码为服务器错误),则重试,直到成功或达到最大重试次数。

SearchEngineService的实现

SearchEngineService类负责调用HttpClient并返回响应内容的长度。以下是SearchEngineService的实现代码:

public class SearchEngineService : ISearchEngineService { private readonly HttpClient _httpClient; public SearchEngineService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<int> GetNumberOfCharactersFromSearchQuery(string toSearchFor) { var result = await _httpClient.GetAsync($"/search?q={toSearchFor}"); var content = await result.Content.ReadAsStringAsync(); return content.Length; } }

SearchEngineService通过HttpClient发送请求,并返回响应内容的长度。这个类是控制器类的一个依赖项。

控制器类的实现

控制器类有一个Get方法,该方法调用SearchEngineService的方法并返回结果。以下是控制器类的实现代码:

[Route("api/[controller]")] [ApiController] public class SearchEngineController : ControllerBase { private readonly ISearchEngineService _searchEngineService; public SearchEngineController(ISearchEngineService searchEngineService) { _searchEngineService = searchEngineService; } [HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")] public async Task

控制器类通过依赖注入获取SearchEngineService实例,并在Get方法中调用其方法,返回响应内容的长度。

集成测试

为了测试控制器类,使用IntegrationFixture(NuGet包)。以下是集成测试的代码:

[Fact] public async Task TestDelegate() { // arrange await using (var fixture = new Fixture<Startup>()) { using (var searchEngineServer = fixture.FreezeServer("Google")) { SetupUnStableServer(searchEngineServer, "Response"); var controller = fixture.Create<SearchEngineController>(); // act var response = await controller.GetNumberOfCharacters("Hoi"); // assert, external var externalResponseMessages = searchEngineServer.LogEntries.Select(l => l.ResponseMessage).ToList(); Assert.Equal(2, externalResponseMessages.Count); Assert.Equal((int)HttpStatusCode.InternalServerError, externalResponseMessages.First().StatusCode); Assert.Equal((int)HttpStatusCode.OK, externalResponseMessages.Last().StatusCode); // assert, internal var loggedResponse = fixture.LogSource.GetLoggedObjects<HttpResponseMessage>().ToList(); Assert.Single(loggedResponse); var externalResponseContent = await loggedResponse.Single().Value.Content.ReadAsStringAsync(); Assert.Equal("Response", externalResponseContent); Assert.Equal(HttpStatusCode.OK, loggedResponse.Single().Value.StatusCode); Assert.Equal(8, ((OkObjectResult)response.Result).Value); } } }

在集成测试中,使用一个模拟服务器来替代外部依赖。模拟服务器在第一次请求后返回服务器错误,第二次请求后成功。调用控制器方法,这会触发对SearchEngineService的调用,进而触发对HttpClient的调用。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485