Skip to content

Commit 599d760

Browse files
HttpClient timeouts: retarget net10.0, add timeout-vs-cancellation and resilience endpoints (#2234)
- Both projects net8.0 to net10.0; web project Nullable enabled. - Test packages: Microsoft.NET.Test.Sdk 18.10.1, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1, Microsoft.AspNetCore.Mvc.Testing 10.0.12. - Add Microsoft.Extensions.Http.Resilience 10.10.0, a ResilientClient named client with AddStandardResilienceHandler, and /api/test-resilience-timeout. - Add /api/test-timeout-vs-cancellation, which uses an exception filter on the nested TimeoutException. - Two new tests in EndpointsLiveTests, so the CI Live filter still skips them. - Drop the unused container tools package and its Docker properties, AddControllers, MapControllers, UseAuthorization, UseHttpsRedirection and four unused using directives. - Dispose the three HttpResponseMessage locals with using var.
1 parent ee4ab1b commit 599d760

4 files changed

Lines changed: 84 additions & 31 deletions

File tree

‎aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut.csproj‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
5-
<Nullable>disable</Nullable>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<UserSecretsId>d0d8591a-b364-4f96-921f-d24a0e638079</UserSecretsId>
8-
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
9-
<DockerfileContext>.</DockerfileContext>
108
</PropertyGroup>
119

1210
<ItemGroup>
13-
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
11+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.10.0" />
1412
</ItemGroup>
1513

1614
<ItemGroup>

‎aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/HttpClientDefaultAndPerRequestTimeOut/Program.cs‎

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
using Microsoft.AspNetCore.Builder;
2-
using Microsoft.AspNetCore.Mvc;
3-
using System.Net.Http;
4-
using System.Threading;
5-
61
var builder = WebApplication.CreateBuilder(args);
72

8-
// Add services to the container.
9-
10-
builder.Services.AddControllers();
11-
123
builder.Services.AddHttpClient("TestClient", (sp, httpClient) =>
134
{
145
var configuration = sp.GetRequiredService<IConfiguration>();
@@ -18,15 +9,17 @@
189
httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
1910
});
2011

21-
var app = builder.Build();
22-
23-
// Configure the HTTP request pipeline.
24-
25-
app.UseHttpsRedirection();
26-
27-
app.UseAuthorization();
12+
builder.Services.AddHttpClient("ResilientClient", httpClient =>
13+
{
14+
httpClient.BaseAddress = new Uri("http://localhost:5000");
15+
})
16+
.AddStandardResilienceHandler(options =>
17+
{
18+
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(2);
19+
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(8);
20+
});
2821

29-
app.MapControllers();
22+
var app = builder.Build();
3023

3124
app.MapGet("/api/delay-4-seconds", async (CancellationToken cancellationToken) => await Task.Delay(TimeSpan.FromSeconds(4), cancellationToken));
3225

@@ -36,7 +29,7 @@
3629

3730
try
3831
{
39-
var response = await httpClient.GetAsync("/api/delay-4-seconds");
32+
using var response = await httpClient.GetAsync("/api/delay-4-seconds");
4033

4134
return Results.Ok();
4235
}
@@ -53,7 +46,7 @@
5346

5447
try
5548
{
56-
var response = await httpClient.GetAsync($"/api/delay-4-seconds", cancellationToken);
49+
using var response = await httpClient.GetAsync($"/api/delay-4-seconds", cancellationToken);
5750

5851
return Results.Ok();
5952
}
@@ -71,7 +64,7 @@
7164

7265
try
7366
{
74-
var response = await httpClient.GetAsync("/api/delay-4-seconds", tokenSource.Token);
67+
using var response = await httpClient.GetAsync("/api/delay-4-seconds", tokenSource.Token);
7568

7669
return Results.Ok();
7770
}
@@ -83,4 +76,40 @@
8376
}
8477
});
8578

79+
app.MapGet("/api/test-timeout-vs-cancellation", async (IHttpClientFactory httpClientFactory, CancellationToken cancellationToken) =>
80+
{
81+
var httpClient = httpClientFactory.CreateClient("TestClient");
82+
83+
try
84+
{
85+
using var response = await httpClient.GetAsync("/api/delay-4-seconds", cancellationToken);
86+
87+
return Results.Ok();
88+
}
89+
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException)
90+
{
91+
return Results.Text("The request timed out");
92+
}
93+
catch (OperationCanceledException)
94+
{
95+
return Results.Text("The caller canceled the request");
96+
}
97+
});
98+
99+
app.MapGet("/api/test-resilience-timeout", async (IHttpClientFactory httpClientFactory) =>
100+
{
101+
var httpClient = httpClientFactory.CreateClient("ResilientClient");
102+
103+
try
104+
{
105+
using var response = await httpClient.GetAsync("/api/delay-4-seconds");
106+
107+
return Results.Ok();
108+
}
109+
catch (Exception ex)
110+
{
111+
return Results.Text($"{ex.GetType().Name} after {httpClient.Timeout}");
112+
}
113+
});
114+
86115
app.Run();

‎aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/EndpointsLiveTests.cs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,30 @@ public async Task GivenCustomTokenIsCombinedWithGlobalAndRequestToken_WhenTheReq
5555
// Assert
5656
await Assert.ThrowsAsync<TaskCanceledException>(() => client.GetAsync("/api/test-combined-timeout", cts.Token));
5757
}
58+
59+
[Fact]
60+
public async Task WhenTheTimeoutElapses_ThenTheOperationCanceledExceptionNestsATimeoutException()
61+
{
62+
// Arrange
63+
var client = _factory.CreateClient();
64+
65+
// Act
66+
var responseMessage = await client.GetStringAsync("/api/test-timeout-vs-cancellation");
67+
68+
// Assert
69+
Assert.Equal("The request timed out", responseMessage);
70+
}
71+
72+
[Fact]
73+
public async Task WhenTheResilienceHandlerTimesOut_ThenItThrowsTimeoutRejectedExceptionAndDisablesHttpClientTimeout()
74+
{
75+
// Arrange
76+
var client = _factory.CreateClient();
77+
78+
// Act
79+
var responseMessage = await client.GetStringAsync("/api/test-resilience-timeout");
80+
81+
// Assert
82+
Assert.Equal("TimeoutRejectedException after -00:00:00.0010000", responseMessage);
83+
}
5884
}

‎aspnetcore-features/HttpClientDefaultAndPerRequestTimeOut/Tests/HttpClientDefaultAndPerRequestTimeOut.Tests/HttpClientDefaultAndPerRequestTimeOut.Tests.csproj‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77

@@ -10,11 +10,11 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="coverlet.collector" Version="6.0.0" />
14-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.4" />
15-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16-
<PackageReference Include="xunit" Version="2.5.3" />
17-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
13+
<PackageReference Include="coverlet.collector" Version="10.0.1" />
14+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
16+
<PackageReference Include="xunit" Version="2.9.3" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" />
1818
</ItemGroup>
1919

2020
<ItemGroup>

0 commit comments

Comments
 (0)