gRPC拦截器在C#中不是ASP.NET Core中间件,必须显式注册到GrpcServiceOptions(服务端)或Channel(客户端),且服务端重写UnaryServerHandler、客户端重写AsyncUnaryCall,混用或错配将导致运行时静默失效。

gRPC拦截器在C#中不是“插件式中间件”,不能像ASP.NET Core中间件那样自由组合或按顺序注入到全局管道;它必须显式注册到GrpcServiceOptions或Channel,且服务端/客户端的拦截器类型、方法签名和调用时机完全不同——混用或错配会导致编译通过但运行时静默失效。
服务端一元拦截器必须继承Interceptor并重写UnaryServerHandler
这是最常出错的地方:很多人照搬HTTP的DelegatingHandler写法,或试图在Program.cs里用AddHttpClient方式注册,结果拦截器根本不会触发。
-
UnaryServerHandler是唯一被gRPC运行时识别的服务端一元调用入口,参数continuation代表“继续执行真实业务方法”,必须显式调用,否则请求卡死 - 不要调用
base.UnaryServerHandler——父类实现为空,调了等于没调 - 异常必须抛出
RpcException(而非Exception),否则客户端收不到标准gRPC错误码 - 从
context.RequestHeaders读取Header时,key不区分大小写,但建议统一用小写字符串匹配(如"authorization")
示例关键片段:
public class AuthInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var auth = context.RequestHeaders.GetValue("authorization");
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
throw new RpcException(new Status(StatusCode.Unauthenticated, "Missing or invalid token"));
return await continuation(request, context);
}
}
客户端拦截器要重写AsyncUnaryCall,且必须包装CallOptions
客户端拦截器不是“发请求前钩子”,而是完整接管一次调用生命周期。常见错误是只改请求头却不传递原始CallOptions,导致超时、取消令牌等机制全部失效。
-
AsyncUnaryCall返回的是AsyncUnaryCall<TResponse>,不是Task<TResponse>,不能直接await后return - 必须把原始
context.Options传给continuation,否则CallOptions.WithDeadline、WithCancellationToken等配置丢失 - 如果需要修改Header,应通过
context.Options.WithHeaders(...)构造新CallOptions,而不是直接操作context对象 - 多个客户端拦截器会按注册顺序链式执行,但每个都必须返回
AsyncUnaryCall实例,否则链断裂
示例关键片段:
public class TokenClientInterceptor : Interceptor
{
private readonly string _token;
public TokenClientInterceptor(string token) => _token = token;
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var newOptions = context.Options.WithHeaders(
new Metadata { { "authorization", $"Bearer {_token}" } });
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method, context.Host, newOptions);
return continuation(request, newContext);
}
}
注册位置决定作用域:服务端用AddGrpc,客户端用Channel构造
没有“全局拦截器”概念。注册错位置=完全不生效。
- 服务端拦截器必须在
builder.Services.AddGrpc(options => options.Interceptors.Add<AuthInterceptor>())中注册,作用于所有gRPC服务 - 客户端拦截器不能通过DI容器注册,必须在创建
Channel时传入:new Channel(host, credentials, new[] { new TokenClientInterceptor(token) }) - 若使用
GrpcChannel.ForAddress,需配合GrpcChannelOptions.Interceptors属性设置 - 同一个
Channel可传入多个拦截器数组,它们按顺序执行;但不同Channel实例之间拦截器不共享
真正容易被忽略的点在于:gRPC拦截器不参与ASP.NET Core中间件管道,HttpContext在服务端拦截器里不可用;客户端拦截器也无法访问IHttpClientFactory生命周期。它是一套独立于Web宿主的底层通信增强机制——想混用Web层能力,得自己桥接,比如把IServiceProvider传进拦截器构造函数,再手动解析服务。


















