I was looking a way to trace an action flow across my clients and servers with a web request in my set of applications using interceptors to add a header for outbound calls and inbound methods.
For outbound calls it results in an exception as trying to modify readonly object. So I have to add on every request explicitely the header.
Ideally I was looking for a place to add Trace.CorrelationManager.ActivityId as a header without doing that for every call made, but instead using an interceptor for outbound calls.
As an example of allowing interceptors to modify outgoing headers is to allows an integration library with open telemetry.
Are you looking to do something like what the test InterceptMetadata_AddRequestHeader_HeaderInRequest does?
Notice that it creates a client and then creates a wrapping interceptor via Intercept and makes the call via the wrapped interceptor.
You have the choice of using an interceptor like the sample linked to above, or setting up the channel to use a DelegatingHandler when it is created - https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpmessagehandler
I was following the Service/Client examples with DI using interceptors.
you can see a simple service client with the failing code on this repo
I find amazingly simple using interceptors via ServiceCollection (great job you guys here!) and make code extremely readable.
Allowing injectors to write and read headers on the context could make thing simpler and consistent for developers too.
Hope this is considered, because modifying the HttpClient headers looks like a hack :)
Thanks for the time on this!
You can do this to set a header:
public class SetCorrelationIdInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var headers = new Metadata();
headers.Add(new Metadata.Entry("activity-id", Trace.CorrelationManager.ActivityId.ToString()));
var newOptions = context.Options.WithHeaders(headers);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, newOptions);
return base.AsyncUnaryCall(request, newContext, continuation);
}
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request,
ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
{
var header = context.Options.Headers.FirstOrDefault(x=>x.Key == "activity-id");
Trace.CorrelationManager.ActivityId = header != null ? Guid.Parse(header.Value) : Guid.Empty;
return base.AsyncServerStreamingCall(request, context, continuation);
}
}
Had to do minor changes to make it work, but your code works perfectly fine! I have update the repo here with the working code but I will also leave a summay below for anybody makes a Google search.
Thanks again for your time!
2 interceptors
public class CreateCorrelationIdInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
Trace.CorrelationManager.ActivityId = Trace.CorrelationManager.ActivityId != Guid.Empty ? Trace.CorrelationManager.ActivityId : Guid.NewGuid();
Console.WriteLine($"Creating id {Trace.CorrelationManager.ActivityId}");
return base.AsyncUnaryCall(request, context, continuation);
}
}
public class SetCorrelationIdInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
Console.WriteLine("Setting id");
var headers = new Metadata
{
new Metadata.Entry("activity-id", Trace.CorrelationManager.ActivityId.ToString())
};
if (context.Options.Headers != null)
{
foreach (var header in context.Options.Headers)
{
context.Options.Headers.Add(header);
}
}
var newOptions = context.Options.WithHeaders(headers);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, newOptions);
return base.AsyncUnaryCall(request, newContext, continuation);
}
public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var header = context.RequestHeaders.First(x => x.Key == "activity-id");
Trace.CorrelationManager.ActivityId = Guid.Parse(header.Value);
return base.UnaryServerHandler(request, context, continuation);
}
}
Running the client
static async Task Main(string[] args)
{
var services = new ServiceCollection();
services.AddScoped<CreateCorrelationIdInterceptor>();
services.AddScoped<SetCorrelationIdInterceptor>();
services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
.AddInterceptor<CreateCorrelationIdInterceptor>()
.AddInterceptor<SetCorrelationIdInterceptor>();
using var scope = services.BuildServiceProvider().CreateScope();
var client = scope.ServiceProvider.GetService<Greeter.GreeterClient>();
await client.SayHelloAsync(new HelloRequest());
}
And configuring services on Server
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(o => o.Interceptors.Add<SetCorrelationIdInterceptor>());
}
Most helpful comment
You can do this to set a header: