You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 10:32:21