grpc server 通过启用 serverreflection 扩展暴露服务方法列表,需手动注册 grpc.aspnetcore.server.reflection 并调用 addgrpcreflection() 和 mapgrpcreflectionservice(),其返回的是编译时静态的 .proto 定义而非运行时动态方法。

gRPC Server 如何暴露可用服务方法列表
gRPC 本身不内置服务发现接口,ServerReflection 是官方提供的标准扩展机制,用于让客户端在运行时查询服务定义(.proto)和可用方法。C# 的 Grpc.AspNetCore.Server.Reflection 包提供了支持,但**默认不启用**,必须显式注册。
关键点:它不返回“运行中方法的动态列表”,而是返回你项目里已编译进来的 .proto 定义——即静态契约。所谓“动态发现”,本质是靠反射 + 预置契约,不是运行时扫描 Service 类型成员。
- 安装 NuGet 包:
Grpc.AspNetCore.Server.Reflection - 在
Program.cs中调用AddGrpcReflection()并注册服务:builder.Services.AddGrpcReflection();
- 在
MapGrpcService<t>()</t>之后调用MapGrpcReflectionService():app.MapGrpcService<MyService>();<br>app.MapGrpcReflectionService();
为什么 ServerReflection 查不到新添加但未重启的服务
因为 ServerReflection 依赖 IServiceBinder 在应用启动时收集所有 MapGrpcService 注册的服务,并将其对应的 ServiceDescriptor 编译为 FileDescriptorProto。它不监听类型系统变化,也不扫描程序集。
常见误判场景:
- 只改了
.proto文件但没重新生成 C# 类 → 反射仍显示旧方法 - 新增了
MyOtherService类但忘了调用MapGrpcService<myotherservice>()</myotherservice>→ 不会出现在反射结果中 - 用
ActivatorUtilities.CreateInstance动态构造服务实例 → 不影响反射元数据
如何实现真正的运行时方法发现(绕过 .proto 约束)
若需不依赖 .proto、纯靠 C# 类型推导服务方法(例如插件式服务加载),就得自己暴露一个 HTTP/REST 接口,手动遍历 IGrpcServiceActivator 或已注册的 ServiceDescriptor。
示例思路(非反射服务,自定义端点):
- 获取所有已注册的 gRPC 服务类型:
app.Services.GetServices<igrpcserviceactivator>()</igrpcserviceactivator>不直接暴露,改用app.Services.GetRequiredService<iserviceprovider>().GetServices<object>()</object></iserviceprovider>+ 过滤typeof(IGrpcService).IsAssignableFrom(t) - 对每个服务类型,用
typeof(T).GetMethods()扫描带[OperationContract]或命名符合Async模式的public方法(注意:gRPC 方法名与 proto 中定义一致,不是 C# 方法名) - 更可靠的方式:读取
AppContext.GetData("GRPC_SERVICE_DESCRIPTORS")(内部机制,不稳定)或维护一个全局ConcurrentDictionary<string serviceinfo></string>,在MapGrpcService后主动注册
⚠️ 注意:这种做法绕过了 gRPC 类型安全和序列化契约,客户端无法自动生成 stub,仅适合管理后台或调试用途。
客户端用 grpc_cli 或 grpcurl 查服务时连不上怎么办
典型错误是 Failed to connect to server: rpc error: code = Internal desc = transport: received the unexpected content-type "text/html" —— 这说明反向代理(如 Nginx、Kestrel 前置 HTTPS)未正确透传 HTTP/2 或未启用 ALPN,导致反射请求被降级成 HTTP/1.1 返回 HTML 错误页。
- 确保 Kestrel 启用 HTTP/2:
"Kestrel": { "EndpointDefaults": { "Protocols": "Http2" } } - 若走 TLS,确认证书支持 ALPN;若本地调试,可临时用
http://+--plaintext参数 -
grpcurl -plaintext localhost:5001 list应返回类似grpc.reflection.v1alpha.ServerReflection和你的服务名;若只返回前者,说明你的服务没被MapGrpcReflectionService()捕获到(检查注册顺序)
真正容易被忽略的是:反射服务只在开发环境有意义,生产环境通常关闭,且它本身不提供方法参数结构细节——要拿到完整 proto 内容,客户端还得额外调用 FileByFilename 或 AllExtensionNumbersForType,这些都得基于原始 .proto 文件存在。










