TestServer.CreateClient() produces an HttpClient that does not auto-inject distributed tracing when Activity is used
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Describe the bug
`CreateClient()` produces an `HttpClient` object that does not auto-inject correlation data when a [`System.Diagnostics.Activity`](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.activity?view=netcore-3.1) is instantiated and started (using the [`Start()`](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.activity.start?view=netcore-3.1) method).
Background information about how to use `Activity` to send correlation data can be found here: https://devblogs.microsoft.com/aspnet/improvements-in-net-core-3-0-for-troubleshooting-and-monitoring-distributed-apps/ (refer to the "Initiate distributed trace in .NET Core 3.0 app" section).
Though the (docs.microsoft.com)["Test ASP.NET Core middleware"](https://docs.microsoft.com/en-us/aspnet/core/test/middleware?view=aspnetcore-3.1) guidance states that `TestServer` "does not try to replicate all `HttpClient` behavior", having `TestServer` create an `HttpClient` object such that propagating any correlation data that may be found in the HTTP request header would make functional/integration tests of middleware more complete and righteous as distributed tracing & correlation support is significant to the story of developing & testing ASP.NET Core apps.
### To Reproduce
Suppose we had a test method like the sample one in (docs.microsoft.com)"[Send requests with HttpClient](https://docs.microsoft.com/en-us/aspnet/core/test/middleware?view=aspnetcore-3.1#send-requests-with-httpclient)". In addition, we'll wrap the call with an `Activity`:
```
[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware();
});
})
.StartAsync();
var activity = new Activity("CallToBackend").SetIdFormat(ActivityIdFormat.W3C).Start();
{
var response = await host.GetTestServer().CreateClient().GetAsync("/");
}
finally
{
activity.Stop();
}
// ...
}
```
I'd expect that when the registered middleware code is reached, the "traceparent" header would be valued (since the Activity ID's format was set to the W3C standard above). However, when the test is run, the expected "traceparent" header does not exist in the request.
```
public class CorrelationValidationMiddleware
{
// ...
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Headers.TryGetValue("traceparent", out StringValues headerValue) == false)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync($"The 'traceparent' header is missing.").ConfigureAwait(false);
return;
}
// ...
// Call the next delegate/middleware in the pipeline
await this.next(context).ConfigureAwait(false);
}
// ...
}
```
If you were to instantiate a new `System.Net.Http.HttpClient` (i.e. `HttpClient httpClient = new HttpClient()`) in the unit test (not from `TestServer.CreateClient()`), the "traceparent" header would be present along with the generated value.
Just for your information, my team has developed a workaround so that our instantiated `TestServer` objects in our functional/integration tests produces an `HttpClient` that supports distributed tracing (just as if you were instantiating a new ``System.Net.Http.HttpClient` object outside the `TestServer`) using an extension method:
```
public static class TestServerExtensions
{
///
/// Creates an HTTP client.
///
///
///
/// This method creates an containing a DiagnosticsHandler so that the
/// distributed tracing from an can automatically be injected into the request
/// header.
///
///
/// This extension method is a workaround solution as the created from the
/// method does not inject distributed tracing into the request
/// header from an instantiated as expected.
///
///
/// This .
/// The HTTP client.
public static HttpClient CreateClientWithDiagnosticsHandler(this TestServer testServer)
{
Assembly assembly = Assembly.Load("System.Net.Http");
Type diagnosticsHandlerType = assembly.GetType("System.Net.Http.DiagnosticsHandler");
HttpMessageHandler handler =
(HttpMessageHandler)Activator.CreateInstance(diagnosticsHandlerType, testServer.CreateHandler());
return new HttpClient(handler) { BaseAddress = testServer.BaseAddress };
}
}
```
### Exceptions (if any)
### Further technical details
- ASP.NET Core version
- Include the output of `dotnet --info`
- The IDE (VS / VS Code/ VS4Mac) you're running on, and it's version
Contributor guide
Assessment
This issue has not been assessed yet.