Skip to content

Commit 7718b4f

Browse files
authored
Add CreateHandler method that allows additonal configuration (#42677)
1 parent 450671c commit 7718b4f

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

src/Hosting/TestHost/src/ClientHandler.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ namespace Microsoft.AspNetCore.TestHost;
2020
public class ClientHandler : HttpMessageHandler
2121
{
2222
private readonly ApplicationWrapper _application;
23+
private readonly Action<HttpContext> _additionalContextConfiguration;
2324
private readonly PathString _pathBase;
2425

2526
/// <summary>
2627
/// Create a new handler.
2728
/// </summary>
2829
/// <param name="pathBase">The base path.</param>
2930
/// <param name="application">The <see cref="IHttpApplication{TContext}"/>.</param>
30-
internal ClientHandler(PathString pathBase, ApplicationWrapper application)
31+
/// <param name="additionalContextConfiguration">The action to additionally configure <see cref="HttpContext"/>.</param>
32+
internal ClientHandler(PathString pathBase, ApplicationWrapper application, Action<HttpContext>? additionalContextConfiguration = null)
3133
{
3234
_application = application ?? throw new ArgumentNullException(nameof(application));
35+
_additionalContextConfiguration = additionalContextConfiguration ?? NoExtraConfiguration;
3336

3437
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
3538
if (pathBase.HasValue && pathBase.Value.EndsWith('/'))
@@ -163,6 +166,8 @@ protected override async Task<HttpResponseMessage> SendAsync(
163166
req.QueryString = QueryString.FromUriComponent(request.RequestUri);
164167
});
165168

169+
contextBuilder.Configure((context, _) => _additionalContextConfiguration(context));
170+
166171
var response = new HttpResponseMessage();
167172

168173
// Copy trailers to the response message when the response stream is complete
@@ -200,4 +205,9 @@ protected override async Task<HttpResponseMessage> SendAsync(
200205
}
201206
return response;
202207
}
208+
209+
private static void NoExtraConfiguration(HttpContext context)
210+
{
211+
// Intentional no op
212+
}
203213
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
#nullable enable
2+
Microsoft.AspNetCore.TestHost.TestServer.CreateHandler(System.Action<Microsoft.AspNetCore.Http.HttpContext!>! additionalContextConfiguration) -> System.Net.Http.HttpMessageHandler!

src/Hosting/TestHost/src/TestServer.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,30 @@ private ApplicationWrapper Application
146146
get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured.");
147147
}
148148

149+
private PathString PathBase => BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
150+
149151
/// <summary>
150152
/// Creates a custom <see cref="HttpMessageHandler" /> for processing HTTP requests/responses with the test server.
151153
/// </summary>
152154
public HttpMessageHandler CreateHandler()
153155
{
154-
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
155-
return new ClientHandler(pathBase, Application) { AllowSynchronousIO = AllowSynchronousIO, PreserveExecutionContext = PreserveExecutionContext };
156+
return new ClientHandler(PathBase, Application)
157+
{
158+
AllowSynchronousIO = AllowSynchronousIO,
159+
PreserveExecutionContext = PreserveExecutionContext
160+
};
161+
}
162+
163+
/// <summary>
164+
/// Creates a custom <see cref="HttpMessageHandler" /> for processing HTTP requests/responses with custom configuration with the test server.
165+
/// </summary>
166+
public HttpMessageHandler CreateHandler(Action<HttpContext> additionalContextConfiguration)
167+
{
168+
return new ClientHandler(PathBase, Application, additionalContextConfiguration)
169+
{
170+
AllowSynchronousIO = AllowSynchronousIO,
171+
PreserveExecutionContext = PreserveExecutionContext
172+
};
156173
}
157174

158175
/// <summary>

src/Hosting/TestHost/test/ClientHandlerTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,36 @@ public async Task ServerTrailersSetOnResponseAfterContentRead()
259259
});
260260
}
261261

262+
[Fact]
263+
public Task AdditionalConfigurationAllowsSettingConnectionInfo()
264+
{
265+
var handler = new ClientHandler(PathString.Empty, new InspectingApplication(features =>
266+
{
267+
Assert.Equal(IPAddress.Parse("1.1.1.1"), features.Get<IHttpConnectionFeature>().RemoteIpAddress);
268+
}), context =>
269+
{
270+
context.Connection.RemoteIpAddress = IPAddress.Parse("1.1.1.1");
271+
});
272+
273+
var httpClient = new HttpClient(handler);
274+
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
275+
}
276+
277+
[Fact]
278+
public Task AdditionalConfigurationAllowsOverridingDefaultBehavior()
279+
{
280+
var handler = new ClientHandler(PathString.Empty, new InspectingApplication(features =>
281+
{
282+
Assert.Equal("?and=something", features.Get<IHttpRequestFeature>().QueryString);
283+
}), context =>
284+
{
285+
context.Request.QueryString = new QueryString("?and=something");
286+
});
287+
288+
var httpClient = new HttpClient(handler);
289+
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
290+
}
291+
262292
[Fact]
263293
public async Task ResponseStartAsync()
264294
{

0 commit comments

Comments
 (0)