能否在C# gRPC客户端拦截器中注入作用域依赖?
在gRPC客户端拦截器中注入作用域依赖以自动添加通用元数据
问题背景
在Web API控制器中使用gRPC客户端调用数据库时,希望为所有gRPC请求自动添加通用元数据,避免手动在每个调用中添加。gRPC拦截器是合适的实现方案,但需要从作用域生命周期的依赖中获取元数据所需数据——而拦截器的重写方法继承自基类Interceptor,无法像ASP.NET Core中间件那样直接在方法参数中注入作用域依赖。
实现方案
通过在拦截器中注入IServiceScopeFactory,在拦截方法内部创建作用域并解析所需的作用域服务,从而获取元数据并添加到gRPC请求中。
1. 定义作用域元数据提供者
先创建一个作用域服务,负责获取并生成通用元数据(可根据业务需求从HttpContext或其他上下文获取数据):
public interface IMyScopedMetadataProvider { Metadata GetCommonMetadata(); } public class MyScopedMetadataProvider : IMyScopedMetadataProvider { private readonly IHttpContextAccessor _httpContextAccessor; public MyScopedMetadataProvider(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Metadata GetCommonMetadata() { var metadata = new Metadata(); // 示例:从当前HttpContext获取用户ID if (_httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated == true) { metadata.Add("User-Id", _httpContextAccessor.HttpContext.User.FindFirstValue("sub")); } // 添加通用请求ID metadata.Add("Request-Id", Guid.NewGuid().ToString()); return metadata; } }
2. 实现带作用域依赖的gRPC客户端拦截器
创建拦截器,通过构造函数注入IServiceScopeFactory,在每个拦截方法内创建作用域、解析元数据提供者,再将元数据添加到gRPC请求头:
public class MetadataClientInterceptor : Interceptor { private readonly IServiceScopeFactory _scopeFactory; public MetadataClientInterceptor(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } // 处理Unary类型调用 public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) { using var scope = _scopeFactory.CreateScope(); var metadataProvider = scope.ServiceProvider.GetRequiredService<IMyScopedMetadataProvider>(); var commonMetadata = metadataProvider.GetCommonMetadata(); // 将元数据添加到请求头 foreach (var entry in commonMetadata) { context.Options.Headers.Add(entry); } return continuation(request, context); } // 处理ServerStreaming类型调用(按需重写其他调用类型,如ClientStreaming、DuplexStreaming) public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>( TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) { using var scope = _scopeFactory.CreateScope(); var metadataProvider = scope.ServiceProvider.GetRequiredService<IMyScopedMetadataProvider>(); var commonMetadata = metadataProvider.GetCommonMetadata(); foreach (var entry in commonMetadata) { context.Options.Headers.Add(entry); } return continuation(request, context); } }
3. 注册服务与拦截器
在Program.cs中注册相关服务,并将拦截器关联到gRPC客户端:
// 注册作用域元数据提供者 builder.Services.AddScoped<IMyScopedMetadataProvider, MyScopedMetadataProvider>(); // 注册拦截器为单例(匹配gRPC客户端工厂的生命周期) builder.Services.AddSingleton<MetadataClientInterceptor>(); // 注册HttpContextAccessor(如果需要从请求上下文获取数据) builder.Services.AddHttpContextAccessor(); // 注册gRPC客户端并添加拦截器 builder.Services.AddGrpcClient<MyDatabaseGrpc.MyDatabaseGrpcClient>(options => { options.Address = new Uri("https://localhost:5001"); }) .AddInterceptor<MetadataClientInterceptor>();
关键说明
- 拦截器注册为单例:因为gRPC客户端工厂默认是单例,拦截器需要与其生命周期匹配,避免不必要的实例创建。
- 作用域创建:通过
IServiceScopeFactory.CreateScope()创建的作用域会在方法执行完毕后自动释放,确保作用域服务的正确生命周期管理。 - 按需重写方法:根据实际使用的gRPC调用类型(Unary、ServerStreaming等),重写对应的拦截方法,确保所有请求都能自动添加元数据。
内容的提问来源于stack exchange,提问作者BearsEars
相关产品推荐
相关产品推荐

