本文主要面向.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的作用是在调用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的实现代码:
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类负责调用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的调用。