新项目必须用 grpc.net.client(纯托管、http/2),禁用 grpc.core;开发时客户端用 channelcredentials.insecure + 服务端 usehttp(),生产必须 https;proto 必须声明 syntax = "proto3"、枚举首值为 0、避免 map;http/2 中间件需透传 te: trailers;grpc 不走 mvc 管道,认证需手动取 header,日志用 activity。

Grpc.Core 还是 Grpc.Net.Client?选错直接卡在 TLS 握手
新项目别碰 Grpc.Core(基于 C++ gRPC)——它在 .NET 6+ 上已标记为“legacy”,Windows/macOS/Linux 行为不一致,尤其在容器里常报 StatusCode=Unavailable, Detail="failed to connect to all addresses"。必须用 Grpc.Net.Client(纯托管、基于 HTTP/2 + System.Net.Http),但它默认要求 TLS:本地开发不配证书就直接失败。
- 开发阶段快速绕过:服务端启动时加
options.ListenAnyIP(5001, o => o.UseHttps())改成UseHttp();客户端创建Channel时传入new Channel("http://localhost:5001", ChannelCredentials.Insecure) - 生产环境必须上 HTTPS:Kestrel 不支持 HTTP/2 明文,
UseHttp()在正式部署会静默降级成 HTTP/1.1,导致调用失败 -
Grpc.Net.Client不支持 Windows Server 2012 R2 及更早系统(缺 ALPN 支持),得换回Grpc.Core——但这时要手动装grpc_csharp_ext.x64.dll,且 Linux 容器需额外加libc6-dev依赖
Protobuf 文件怎么写才让 C# 和 Python/Go 消费不出错
跨语言核心不是语法,是序列化契约。C# 的 Google.Protobuf 对字段编号、保留字、嵌套规则比其他语言更敏感,一个 repeated 写成 list 或漏掉 syntax = "proto3"; 就会导致 Go 解析出空对象、Python 报 AttributeError。
- 所有 message 必须显式声明
syntax = "proto3";,proto2 生成的 C# 类默认带HasXXX属性,其他语言没这概念 - 枚举值第一个必须是
0(如UNKNOWN = 0;),否则 C# 反序列化时默认值为0,但 Go/Python 可能映射失败 - 避免在 proto 里用
map<string string></string>:C# 生成的是Dictionary<string string></string>,但 Python grpcio 默认转成dict,而 Go 是map[string]string—— 看似一样,实际 JSON 序列化顺序不同,diff 工具会误报差异
.NET 客户端调用时为什么总是 “Status(StatusCode=Internal, Detail="Error starting gRPC call")”
这个错误根本不是业务逻辑问题,90% 是 HTTP/2 层被中间件拦了。IIS、Nginx、甚至某些公司内部网关默认不转发 HTTP/2 帧,或者把 te: trailers 头给删了 —— 而 gRPC 必须靠这个头标识流结束。
- 本地测试先绕过代理:用
curl -v --http2 https://localhost:5001/greet.Greeter/SayHello看是否返回HTTP/2 200,不是就说明 Kestrel 配置或 TLS 证书有问题 - IIS 部署必须开 HTTP/2:Windows Server 2016+ + IIS 10.0+ + SNI 证书 + 在站点绑定里勾选 “HTTP/2”(不是“启用 HTTP/2”复选框,是绑定设置里的独立选项)
- Nginx 转发需显式透传头:
proxy_http_version 1.1;改成2.0,并加proxy_set_header TE "trailers";,否则 C# 客户端收不到响应末尾的 status trailer
从 ASP.NET Core Web API 切到 gRPC,HttpContext.Items 为什么丢了
gRPC 请求不走 MVC 管道,HttpContext 是空壳 —— 即使你用 AddGrpc() 和 AddControllers() 混合注册,gRPC endpoint 的 HttpContext 也不会触发 AuthorizationMiddleware 或 ResponseCompressionMiddleware 的完整生命周期。
- 认证必须用
ServerCallContext:在 service 实现里通过context.RequestHeaders.Get("authorization")手动取 token,别依赖[Authorize] - 日志上下文要用
Activity:gRPC 自动注入Activity.Current?.Id,比HttpContext.TraceIdentifier更可靠,且跨语言一致 - 全局异常不能靠
UseExceptionHandler:要实现IGrpcServiceActivator或在每个 service 方法里 try/catch,再用status: StatusCode.PermissionDenied+detail返回结构化错误
真正麻烦的从来不是写几个 async Task<helloreply></helloreply>,而是搞清哪一层在吃你的 header、谁在提前关闭 stream、以及 protobuf 字段编号改一个会不会让 Java 客户端开始返回 null —— 这些细节不会报红,但会让联调花掉三天。











