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

.NET应用添加SOAP头Transaction ID及Polly重试协同问题

解决SOAP重试时保持统一Transaction ID的方案

针对你遇到的Polly与IClientMessageInspector互不感知的问题,以下是几种实用的解决思路,均无需依赖外部工具,纯代码实现:

方案一:利用Polly Context传递Transaction ID

核心思路是把首次生成的Transaction ID存入Polly的上下文对象,重试时从上下文读取复用,而非每次生成新ID。

步骤与代码示例

  1. 扩展自动生成的SOAP客户端,添加Polly Context的存储属性:
public partial class YourSoapClient : ClientBase<IYourSoapService>, IYourSoapService
{
    // 用于存储Polly上下文
    public Context PollyContext { get; set; }
    // 保留VS自动生成的其他代码
}
  1. 实现带上下文感知的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) { }
}
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:50:45