C# gRPC拦截器方法 C#如何为gRPC服务添加拦截器

来源:这里教程网 时间:2026-02-21 17:41:55 作者:

gRPC拦截器在C#中用
ServerInterceptor
实现,不是中间件

gRPC .NET 的服务端拦截器和 ASP.NET Core 中间件完全不同:它不走

HttpContext
,也不在请求管道里;而是通过继承
ServerInterceptor
类,在方法调用前后插入逻辑。如果你试图往
Startup.Configure
Program.cs
的中间件链里加拦截逻辑,会完全失效。

关键点:

ServerInterceptor
必须在注册 gRPC 服务时显式传入,例如
AddGrpc().AddServiceOptions<yourservice>(o => o.Interceptors.Add<yourinterceptor>())</yourinterceptor></yourservice>
一个服务可添加多个拦截器,执行顺序按
Add
顺序(先注册的先执行
UnaryServerHandler
前置逻辑)
拦截器实例默认是单例(
Singleton
生命周期),不能直接注入
Scoped
服务(如
DbContext
),需通过
IServiceScopeFactory
手动创建作用域

InterceptUnaryAsync
是最常用入口,但别漏掉流式方法

大多数日志、鉴权、指标场景都从

InterceptUnaryAsync
开始,但它只覆盖 unary(一元)调用。如果你的服务用了
stream
(server-streaming、client-streaming、bidi-streaming),必须同时重写对应方法:
InterceptClientStreamingAsync
InterceptServerStreamingAsync
InterceptDuplexStreamingAsync
,否则这些调用完全绕过你的拦截逻辑。

常见疏忽:

只实现
InterceptUnaryAsync
,上线后发现流式接口没打日志、没校验 token
在流式方法里直接 await
continuation(...)
而没包装
IAsyncEnumerable<t></t>
或处理
IServerStreamWriter<t></t>
,导致响应中断或内存泄漏
想统一处理所有类型?可以提取公共逻辑到私有方法,但四个入口仍需分别调用,无法“一次编写四处生效”

修改请求/响应内容必须用
AsyncUnaryCall<tresponse></tresponse>
包装

拦截器里不能直接改

request
response
参数——它们是只读的。要篡改数据(比如加 trace-id 到响应头、脱敏请求字段),得自己构造新的
AsyncUnaryCall<tresponse></tresponse>
并返回。这一步最容易出错:

调用
continuation(...)
后拿到原始
AsyncUnaryCall<tresponse></tresponse>
,再用
new AsyncUnaryCall<t>(responseTask, ...)</t>
封装,其中
responseTask
需要 await + 修改后再 return Task.FromResult(...)
如果只是读取 header(如
context.RequestHeaders
),没问题;但写 header 必须在
continuation
调用前用
context.ResponseTrailers.Add(...)
,或在
continuation
返回后通过
call.ResponseHeadersAsync
获取并修改(注意时机)
别在拦截器里 throw 异常后还调用
continuation
,会导致重复响应或状态码冲突;应提前 return 新建的失败
AsyncUnaryCall

拦截器里访问 DI 容器要小心生命周期和线程上下文

拦截器本身是 Singleton,但 gRPC 调用是并发的,每个调用都有独立的

ServerCallContext
。如果你需要
Scoped
服务(比如
IHttpContextAccessor
、数据库上下文),不能直接构造函数注入,而要用
IServiceScopeFactory

private readonly IServiceScopeFactory _scopeFactory;
public MyInterceptor(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
public override async Task<TResponse> InterceptUnaryAsync<TRequest, TResponse>(
    TRequest request,
    ServerCallContext context,
    UnaryServerMethod<TRequest, TResponse> continuation)
{
    using var scope = _scopeFactory.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    // ...
}

注意:

不要把
scope.ServiceProvider
存为字段——它不是线程安全的
ServerCallContext
不包含
HttpContext
,所以
IHttpContextAccessor
在纯 gRPC 拦截器里始终为 null(除非你启用了
Grpc.AspNetCore.Server.ClientFactory
并显式桥接)
异步方法里 await 的地方可能切换线程,避免在拦截器里操作 UI 相关或线程绑定资源 实际用的时候,最麻烦的往往不是写拦截器,而是调试——gRPC 错误堆栈不显示拦截器帧,
ServerCallContext.Status
被设为
Cancelled
Unknown
时很难定位是哪个拦截器干的。建议每个拦截器开头加
context.RequestHeaders.TryGetValues("x-request-id", out var ids)
并打结构化日志,不然排查成本很高。

相关推荐