I am writing a piece of middleware for ASP.NET Core 8 that makes use of the IHttpActivityFeature
; in my integration tests, I am using WebHostBuilder
to create an isolated test server for my middleware to run on.
However, WebHostBuilder
is not configuring the IHttpActivityFeature
out of the box, so when I request the feature from the HttpContext
, I get a null reference; so far I have been unable to track down any information on how this feature is enabled, or how to add it to my test server.
My middleware is:
public class TraceIdMiddleware
{
private const string TraceIdHeader = "X-Trace-Id";
private readonly RequestDelegate _next;
public TraceIdMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
context.Response.OnStarting(() => {
var activityFeature = context.Features.Get<IHttpActivityFeature>();
var traceId = activityFeature.Activity.TraceId.ToString();
context.Response.Headers.TryAdd(TraceIdHeader, traceId);
return Task.CompletedTask;
});
return _next(context);
}
}
And it is tested by this code:
public class WhenMakingAnHttpRequest
{
[Fact]
public async Task ItShouldAddTheTraceIdHeader()
{
var builder = new WebHostBuilder()
.Configure(applicationBuilder => {
applicationBuilder
.UseTraceId()
.Map("/trace-id", app => {
app.Run(async context => {
await context.Response.WriteAsync("Testing TraceId Middleware");
});
});
});
using var server = new TestServer(builder);
using var client = server.CreateClient();
var response = await client.GetAsync("/trace-id");
response.Headers
.Should().ContainKey("X-Trace-Id").WhoseValue
.Should().NotBeNullOrEmpty();
}
}
The test fails when the middleware throws a NullReferenceException
due to the IHttpActivityFeature
not being registered with the HttpContext
when I call Features.Get<T>()
on the context.
How can I configure the test server to return an IHttpActivityFeature
?
Modify your code like below:
WhenMakingAnHttpRequest
public class WhenMakingAnHttpRequest
{
[Fact]
public async Task ItShouldAddTheTraceIdHeader()
{
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
// Configure IHttpActivityFeature
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpActivityFeature>(provider =>
{
var httpContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var activity = new Activity("HTTP Activity");
activity.Start();
return new MockHttpActivityFeature(activity);
});
})
.Configure(applicationBuilder =>
{
applicationBuilder
.UseMiddleware<TraceIdMiddleware>()
.Map("/trace-id", app =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("Testing TraceId Middleware");
});
});
});
using var server = new TestServer(builder);
using var client = server.CreateClient();
// Act
var response = await client.GetAsync("/trace-id");
// Assert
Assert.True(response.Headers.Contains("X-Trace-Id"));
}
}
Custom implementation of ·IHttpActivityFeature·
public class MockHttpActivityFeature : IHttpActivityFeature
{
public Activity Activity { get; set; }
public MockHttpActivityFeature(Activity activity)
{
Activity = activity;
}
}
Middleware
public class TraceIdMiddleware
{
private const string TraceIdHeader = "X-Trace-Id";
private readonly RequestDelegate _next;
public TraceIdMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IHttpActivityFeature activityFeature)
{
if (activityFeature?.Activity != null)
{
var traceId = activityFeature.Activity.TraceId.ToString();
context.Response.Headers.TryAdd(TraceIdHeader, traceId);
}
await _next(context);
}
}