.NET应用添加SOAP头Transaction ID及Polly重试协同问题
解决SOAP重试时保持统一Transaction ID的方案
针对你遇到的Polly与IClientMessageInspector互不感知的问题,以下是几种实用的解决思路,均无需依赖外部工具,纯代码实现:
方案一:利用Polly Context传递Transaction ID
核心思路是把首次生成的Transaction ID存入Polly的上下文对象,重试时从上下文读取复用,而非每次生成新ID。
步骤与代码示例
- 扩展自动生成的SOAP客户端,添加Polly Context的存储属性:
public partial class YourSoapClient : ClientBase<IYourSoapService>, IYourSoapService { // 用于存储Polly上下文 public Context PollyContext { get; set; } // 保留VS自动生成的其他代码 }
- 实现带上下文感知的IClientMessageInspector:
public class TransactionIdInspector : IClientMessageInspector { public object BeforeSendRequest(ref Message request, IClientChannel channel) { var soapClient = channel as YourSoapClient; if (soapClient?.PollyContext != null) { // 上下文无ID则生成,有则直接复用 if (!soapClient.PollyContext.TryGetValue("TransactionId", out var idObj)) { var transactionId = Guid.NewGuid().ToString(); soapClient.PollyContext["TransactionId"] = transactionId; AddTransactionHeader(ref request, transactionId); } else { AddTransactionHeader(ref request, idObj.ToString()); } } else { // 无Polly上下文时,生成新ID(非重试场景) AddTransactionHeader(ref request, Guid.NewGuid().ToString()); } return null; } private void AddTransactionHeader(ref Message request, string transactionId) { // 构造SOAP头并添加到请求中,替换为你的服务命名空间 var header = new MessageHeader<string>(transactionId); var untypedHeader = header.GetUntypedHeader("TransactionId", "http://your-service-namespace"); request.Headers.Add(untypedHeader); } public void AfterReceiveReply(ref Message reply, object correlationState) { } }
- Polly重试策略与客户端结合使用:
// 定义重试策略(根据实际失败类型调整异常捕获) var retryPolicy = Policy .Handle<SoapException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 发起请求时绑定上下文 using var client = new YourSoapClient(); var pollyContext = new Context(); client.PollyContext = pollyContext; // 确保Inspector已添加到客户端行为 client.Endpoint.Behaviors.Add(new TransactionIdInspector()); await retryPolicy.ExecuteAsync(ctx => client.TargetSoapMethodAsync(), pollyContext);
方案二:绕过Inspector,直接在Polly逻辑中复用Transaction ID
如果不想依赖IClientMessageInspector,可以直接在Polly的执行委托中手动设置SOAP头,确保重试时使用同一个ID。
代码示例
// 首次生成唯一Transaction ID var fixedTransactionId = Guid.NewGuid().ToString(); var retryPolicy = Policy .Handle<SoapException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); using var client = new YourSoapClient(); await retryPolicy.ExecuteAsync(async () => { // 每次请求前重置SOAP头(避免重复添加) client.InnerChannel.OperationContext.OutgoingMessageHeaders.RemoveAll("TransactionId", "http://your-service-namespace"); // 手动添加固定的Transaction ID头 var header = new MessageHeader<string>(fixedTransactionId); var untypedHeader = header.GetUntypedHeader("TransactionId", "http://your-service-namespace"); client.InnerChannel.OperationContext.OutgoingMessageHeaders.Add(untypedHeader); // 调用SOAP接口 return await client.TargetSoapMethodAsync(); });
方案三:在客户端实例级别存储Transaction ID
扩展SOAP客户端,让每个客户端实例对应一个固定的Transaction ID,重试时复用同一个客户端实例即可。
代码示例
public partial class YourSoapClient { // 每个客户端实例对应唯一的Transaction ID public string TransactionId { get; } = Guid.NewGuid().ToString(); public YourSoapClient() : base() { // 自动添加自定义Behavior this.Endpoint.Behaviors.Add(new TransactionIdBehavior(this)); } private class TransactionIdBehavior : IEndpointBehavior { private readonly YourSoapClient _client; public TransactionIdBehavior(YourSoapClient client) { _client = client; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 添加绑定当前实例ID的Inspector clientRuntime.MessageInspectors.Add(new TransactionIdInspector(_client.TransactionId)); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } } private class TransactionIdInspector : IClientMessageInspector { private readonly string _fixedTransactionId; public TransactionIdInspector(string transactionId) { _fixedTransactionId = transactionId; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { var header = new MessageHeader<string>(_fixedTransactionId); var untypedHeader = header.GetUntypedHeader("TransactionId", "http://your-service-namespace"); request.Headers.Add(untypedHeader); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } } }
使用时只需复用同一个客户端实例,Polly重试时自然会使用同一个Transaction ID:
using var client = new YourSoapClient(); var retryPolicy = Policy .Handle<SoapException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); await retryPolicy.ExecuteAsync(() => client.TargetSoapMethodAsync());
关于Visual Studio配置的说明
无法通过Visual Studio的WSDL生成工具(添加服务引用/SvcUtil)直接配置实现这种逻辑。VS生成的仅为基础的SOAP客户端代码,自定义的重试与Transaction ID绑定逻辑必须通过手动编码扩展实现。
内容的提问来源于stack exchange,提问作者user2727133
相关产品推荐
相关产品推荐

