diff --git a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut.csproj b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut.csproj index ef14247743..7126bc21cc 100644 --- a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut.csproj +++ b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut.csproj @@ -1,16 +1,14 @@  - net8.0 - disable + net10.0 + enable enable d0d8591a-b364-4f96-921f-d24a0e638079 - Linux - . - + diff --git a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/Program.cs b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/Program.cs index ff96ee4d51..0d931ddd1c 100644 --- a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/Program.cs +++ b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/Program.cs @@ -1,14 +1,5 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc; -using System.Net.Http; -using System.Threading; - var builder = WebApplication.CreateBuilder(args); -// Add services to the container. - -builder.Services.AddControllers(); - builder.Services.AddHttpClient("TestClient", (sp, httpClient) => { var configuration = sp.GetRequiredService(); @@ -18,15 +9,17 @@ httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); }); -var app = builder.Build(); - -// Configure the HTTP request pipeline. - -app.UseHttpsRedirection(); - -app.UseAuthorization(); +builder.Services.AddHttpClient("ResilientClient", httpClient => +{ + httpClient.BaseAddress = new Uri("http://localhost:5000"); +}) +.AddStandardResilienceHandler(options => +{ + options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(2); + options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(8); +}); -app.MapControllers(); +var app = builder.Build(); app.MapGet("/api/delay-4-seconds", async (CancellationToken cancellationToken) => await Task.Delay(TimeSpan.FromSeconds(4), cancellationToken)); @@ -36,7 +29,7 @@ try { - var response = await httpClient.GetAsync("/api/delay-4-seconds"); + using var response = await httpClient.GetAsync("/api/delay-4-seconds"); return Results.Ok(); } @@ -53,7 +46,7 @@ try { - var response = await httpClient.GetAsync($"/api/delay-4-seconds", cancellationToken); + using var response = await httpClient.GetAsync($"/api/delay-4-seconds", cancellationToken); return Results.Ok(); } @@ -71,7 +64,7 @@ try { - var response = await httpClient.GetAsync("/api/delay-4-seconds", tokenSource.Token); + using var response = await httpClient.GetAsync("/api/delay-4-seconds", tokenSource.Token); return Results.Ok(); } @@ -83,4 +76,40 @@ } }); +app.MapGet("/api/test-timeout-vs-cancellation", async (IHttpClientFactory httpClientFactory, CancellationToken cancellationToken) => +{ + var httpClient = httpClientFactory.CreateClient("TestClient"); + + try + { + using var response = await httpClient.GetAsync("/api/delay-4-seconds", cancellationToken); + + return Results.Ok(); + } + catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException) + { + return Results.Text("The request timed out"); + } + catch (OperationCanceledException) + { + return Results.Text("The caller canceled the request"); + } +}); + +app.MapGet("/api/test-resilience-timeout", async (IHttpClientFactory httpClientFactory) => +{ + var httpClient = httpClientFactory.CreateClient("ResilientClient"); + + try + { + using var response = await httpClient.GetAsync("/api/delay-4-seconds"); + + return Results.Ok(); + } + catch (Exception ex) + { + return Results.Text($"{ex.GetType().Name} after {httpClient.Timeout}"); + } +}); + app.Run(); diff --git a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/EndpointsLiveTests.cs b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/EndpointsLiveTests.cs index b32d371271..d3bc20368b 100644 --- a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/EndpointsLiveTests.cs +++ b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/EndpointsLiveTests.cs @@ -55,4 +55,30 @@ public async Task GivenCustomTokenIsCombinedWithGlobalAndRequestToken_WhenTheReq // Assert await Assert.ThrowsAsync(() => client.GetAsync("/api/test-combined-timeout", cts.Token)); } + + [Fact] + public async Task WhenTheTimeoutElapses_ThenTheOperationCanceledExceptionNestsATimeoutException() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var responseMessage = await client.GetStringAsync("/api/test-timeout-vs-cancellation"); + + // Assert + Assert.Equal("The request timed out", responseMessage); + } + + [Fact] + public async Task WhenTheResilienceHandlerTimesOut_ThenItThrowsTimeoutRejectedExceptionAndDisablesHttpClientTimeout() + { + // Arrange + var client = _factory.CreateClient(); + + // Act + var responseMessage = await client.GetStringAsync("/api/test-resilience-timeout"); + + // Assert + Assert.Equal("TimeoutRejectedException after -00:00:00.0010000", responseMessage); + } } \ No newline at end of file diff --git a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/HttpClientDefaultAndPerRequestTimeOut.Tests.csproj b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/HttpClientDefaultAndPerRequestTimeOut.Tests.csproj index 7b06f7d1f5..87e4fe4bb1 100644 --- a/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/HttpClientDefaultAndPerRequestTimeOut.Tests.csproj +++ b/aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/HttpClientDefaultAndPerRequestTimeOut.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,11 +10,11 @@ - - - - - + + + + +