C#发送Service Bus消息失败90%是连接字符串缺少Send权限,因Azure默认策略仅含Listen;UnauthorizedAccessException或401错误源于底层HTTP 401被SDK转换,未暴露响应头;正确做法是用ServiceBusClient.CreateSender()获取ServiceBusSender再调用SendMessageAsync(),且消息须封装为ServiceBusMessage。

直接说结论:C# 发送 Service Bus 消息失败,90% 是连接字符串没开 Send 权限,不是代码写错了。
为什么 UnauthorizedAccessException 或 401 却查不到权限问题?
错误堆栈里通常不显式提“权限不足”,它藏在底层 HTTP 响应中。SDK 把 401 转成 UnauthorizedAccessException,但没附带原始响应头或诊断信息。你看到的可能是:
System.UnauthorizedAccessException: Put token failed. status-code: 401- 或更模糊的
ServiceBusException,ErrorSource为None
根本原因:Azure 门户里用默认的 RootManageSharedAccessKey 生成的连接字符串,权限是 Manage(含 Send+Listen+Delete),但如果你手动在“Shared access policies”里新建策略,很容易只勾了 Listen —— 这对发送方完全无效。
务必确认连接字符串来自一个明确勾选了 Send 的策略(Manage 也行,但不推荐用于纯发送场景)。
ServiceBusClient 和 ServiceBusSender 怎么配对用?
常见误写:client.SendAsync(...) —— 这个方法根本不存在,编译就过不去。
正确链路只有这一条:
- 用
ServiceBusClient实例调用CreateSender("queue-name")或CreateSender("topic-name") - 得到的
ServiceBusSender实例再调用SendMessageAsync()(注意是Message,不是SendAsync) - 消息必须封装成
ServiceBusMessage,不能直接传string、byte[]或JsonElement
示例片段:
var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender("orders-topic"); // 主题名,不是订阅名
await sender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(order)));注意:ServiceBusSender 可复用,但别在高并发下跨线程共用同一个实例(虽然 SDK 内部有基础线程安全,但实测在极端吞吐下仍建议按作用域创建)。
发到队列和发到主题,代码上真的一样吗?
API 层面完全一致,区别只在 CreateSender() 的参数语义:
- 队列场景:
CreateSender("myqueue")→ 对应真实队列名 - 主题场景:
CreateSender("orders-topic")→ 对应真实主题名;订阅名由接收方控制,发送方完全不感知
容易踩的坑:
- 误写成
CreateSender("orders-topic/subscriptions/new-orders")→ 会报Entity not found,因为这不是合法实体路径 - 想让消息只进某个订阅?发送方做不到。得靠接收端配置
Rule+ 设置Message.ApplicationProperties或UserProperties做路由匹配 - 主题支持通配符订阅(如
orders-*),但发送方无需、也不能干预匹配逻辑
本地调试时怎么避免硬编码连接字符串?
别用 RootManageSharedAccessKey 的完整连接字符串写死在代码或 appsettings.json 里——容易误提交、泄露权限。
更安全的做法:
- 开发阶段用 Azure AD 凭据 + RBAC(即“无密码方式”),需给服务主体分配
Azure Service Bus Data Sender角色 - 若必须用连接字符串,把它存在本地
user-secrets或环境变量中,用Configuration.GetConnectionString("ServiceBus")读取 - CI/CD 中通过密钥管理服务(如 Azure Key Vault)注入,绝不走 Git
另外,标准层命名空间不支持会话(SessionId),如果代码里设置了 message.SessionId = "abc" 却没在创建队列/订阅时启用会话,发消息会静默失败或被丢弃——这个点非常隐蔽,且不会报错,只会在接收端收不到。


















