aspnetcore/grpc/interceptors.md
Interceptors are a gRPC concept that allows apps to interact with incoming or outgoing gRPC calls. They offer a way to enrich the request processing pipeline.
Interceptors are configured for a channel or service and executed automatically for each gRPC call. Since interceptors are transparent to the user's application logic, they're an excellent solution for common cases, such as logging, monitoring, authentication, and validation.
Interceptor typeInterceptors can be implemented for both gRPC servers and clients by creating a class that inherits from the Interceptor type:
public class ExampleInterceptor : Interceptor
{
}
By default, the Interceptor base class doesn't do anything. Add behavior to an interceptor by overriding the appropriate base class methods in an interceptor implementation.
gRPC client interceptors intercept outgoing RPC invocations. They provide access to the sent request, the incoming response, and the context for a client-side call.
Interceptor methods to override for client:
BlockingUnaryCall: Intercepts a blocking invocation of a unary RPC.AsyncUnaryCall: Intercepts an asynchronous invocation of an unary RPC.AsyncClientStreamingCall: Intercepts an asynchronous invocation of a client-streaming RPC.AsyncServerStreamingCall: Intercepts an asynchronous invocation of a server-streaming RPC.AsyncDuplexStreamingCall: Intercepts an asynchronous invocation of a bidirectional-streaming RPC.[!WARNING] Although both
BlockingUnaryCallandAsyncUnaryCallrefer to unary RPCs, they aren't interchangeable. A blocking invocation isn't intercepted byAsyncUnaryCall, and an asynchronous invocation isn't intercepted by aBlockingUnaryCall.
The following code presents a basic example of intercepting an asynchronous invocation of a unary call:
public class ClientLoggingInterceptor : Interceptor
{
private readonly ILogger _logger;
public ClientLoggingInterceptor(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ClientLoggingInterceptor>();
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
_logger.LogInformation("Starting call. Type/Method: {Type} / {Method}",
context.Method.Type, context.Method.Name);
return continuation(request, context);
}
}
Overriding AsyncUnaryCall:
continuation parameter passed into the method. This invokes the next interceptor in the chain or the underlying call invoker if this is the last interceptor.Methods on Interceptor for each kind of service method have different signatures. However, the concept behind continuation and context parameters remains the same:
continuation is a delegate which invokes the next interceptor in the chain or the underlying call invoker (if there is no interceptor left in the chain). It isn't an error to call it zero or multiple times. Interceptors aren't required to return a call representation (AsyncUnaryCall in case of unary RPC) returned from the continuation delegate. Omitting the delegate call and returning your own instance of call representation breaks the interceptors' chain and returns the associated response immediately.context carries scoped values associated with the client-side call. Use context to pass metadata, such as security principals, credentials, or tracing data. Moreover, context carries information about deadlines and cancellation. For more information, see xref:grpc/deadlines-cancellation#deadlines.An interceptor can await the response in unary and client streaming calls by updating the AsyncUnaryCall<TResponse>.ResponseAsync or AsyncClientStreamingCall<TRequest, TResponse>.ResponseAsync value.
public class ErrorHandlerInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var call = continuation(request, context);
return new AsyncUnaryCall<TResponse>(
HandleResponse(call.ResponseAsync),
call.ResponseHeadersAsync,
call.GetStatus,
call.GetTrailers,
call.Dispose);
}
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> inner)
{
try
{
return await inner;
}
catch (Exception ex)
{
throw new InvalidOperationException("Custom error", ex);
}
}
}
The preceding code:
AsyncUnaryCall.AsyncUnaryCall:
continuation parameter to invoke the next item in the interceptor chain.AsyncUnaryCall<TResponse> instance based on the result of the continuation.ResponseAsync task using the HandleResponse method.HandleResponse. Awaiting the response allows logic to be added after the client received the response. By awaiting the response in a try-catch block, errors from calls can be logged.For more information on how to create a client interceptor, see the ClientLoggerInterceptor.cs example in the grpc/grpc-dotnet GitHub repository.
gRPC client interceptors are configured on a channel.
The following code:
GrpcChannel.ForAddress.Intercept extension method to configure the channel to use the interceptor. Note that this method returns a CallInvoker. Strongly-typed gRPC clients can be created from an invoker just like a channel.using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var invoker = channel.Intercept(new ClientLoggerInterceptor());
var client = new Greeter.GreeterClient(invoker);
The Intercept extension method can be chained to configure multiple interceptors for a channel. Alternatively, there is an Intercept overload that accepts multiple interceptors. Any number of interceptors can be executed for a single gRPC call, as the following example demonstrates:
var invoker = channel
.Intercept(new ClientTokenInterceptor())
.Intercept(new ClientMonitoringInterceptor())
.Intercept(new ClientLoggerInterceptor());
Interceptors are invoked in reverse order of the chained Intercept extension methods. In the preceding code, interceptors are invoked in the following order:
ClientLoggerInterceptorClientMonitoringInterceptorClientTokenInterceptorFor information on how to configure interceptors with gRPC client factory, see xref:grpc/clientfactory#configure-interceptors.
gRPC server interceptors intercept incoming RPC requests. They provide access to the incoming request, the outgoing response, and the context for a server-side call.
Interceptor methods to override for server:
UnaryServerHandler: Intercepts a unary RPC.ClientStreamingServerHandler: Intercepts a client-streaming RPC.ServerStreamingServerHandler: Intercepts a server-streaming RPC.DuplexStreamingServerHandler: Intercepts a bidirectional-streaming RPC.The following code presents an example of an intercepting an incoming unary RPC:
public class ServerLoggerInterceptor : Interceptor
{
private readonly ILogger _logger;
public ServerLoggerInterceptor(ILogger<ServerLoggerInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
_logger.LogInformation("Starting receiving call. Type/Method: {Type} / {Method}",
MethodType.Unary, context.Method);
try
{
return await continuation(request, context);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error thrown by {context.Method}.");
throw;
}
}
}
Overriding UnaryServerHandler:
continuation parameter passed into the method. This invokes the next interceptor in the chain or the service handler if this is the last interceptor.The signature of both client and server interceptors methods are similar:
continuation stands for a delegate for an incoming RPC calling the next interceptor in the chain or the service handler (if there is no interceptor left in the chain). Similar to client interceptors, you can call it any time and there's no need to return a response directly from the continuation delegate. Outbound logic can be added after a service handler has executed by awaiting the continuation.context carries metadata associated with the server-side call, such as request metadata, deadlines and cancellation, or RPC result.For more information on how to create a server interceptor, see the ServerLoggerInterceptor.cs example in the grpc/grpc-dotnet GitHub repository.
gRPC server interceptors are configured at startup. The following code:
AddGrpc.ServerLoggerInterceptor for all services by adding it to the service option's Interceptors collection.public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
An interceptor can also be configured for a specific service by using AddServiceOptions and specifying the service type.
public void ConfigureServices(IServiceCollection services)
{
services
.AddGrpc()
.AddServiceOptions<GreeterService>(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
}
Interceptors are run in the order that they're added to the InterceptorCollection. If both global and single service interceptors are configured, then globally-configured interceptors are run before those configured for a single service.
By default, gRPC server interceptors have a per-request lifetime. Overriding this behavior is possible through registering the interceptor type with dependency injection. The following example registers the ServerLoggerInterceptor with a singleton lifetime:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
}
ASP.NET Core middleware offers similar functionalities compared to interceptors in C-core-based gRPC apps. ASP.NET Core middleware and interceptors are conceptually similar. Both:
HttpContext:
HttpContext is a parameter.HttpContext can be accessed using the ServerCallContext parameter with the ServerCallContext.GetHttpContext extension method. This feature is specific to interceptors running in ASP.NET Core.gRPC Interceptor differences from ASP.NET Core Middleware:
ServerCallContext.