2
Vote

SoapHeadersClientHook class is implement a singleton and concurrency thread overwrite the Headers field

description

Hi all,
 
SoapHeadersClientHook is implement a singleton and concurrency thread overwrite the Headers field. This issue evoke a SOAP Header is unusable. This issue can fix by the next code sample (is in attachment too) of SoapHeadersClientHook class.
 
Best regards
 
Petr Dvořáček
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.Threading;
 
namespace WCFExtras.Soap
{
class SoapHeadersClientHook : IClientMessageInspector, IChannelInitializer,
    IExtension<IContextChannel>
{
    #region OperationHeaders
 
    class OperationHeaders
    {
        public List<SoapHeaderAttribute> In = new List<SoapHeaderAttribute>();
        public List<SoapHeaderAttribute> Out = new List<SoapHeaderAttribute>();
    }
 
    #endregion
 
    Dictionary<Type, SoapHeaderHelper> shHelpers;
    Dictionary<string, OperationHeaders> headersFromAction;
 
    Dictionary<int, Hashtable> _headersByThread = new Dictionary<int, Hashtable>();
 
    private SoapHeadersClientHook(Dictionary<string, OperationHeaders> headersFromAction,
        Dictionary<Type, SoapHeaderHelper> soapHelpers)
    {
        this.headersFromAction = headersFromAction;
        shHelpers = soapHelpers;
    }
 
    public Hashtable Headers
    {
        get
        {
            var threadHashCode = Thread.CurrentThread.GetHashCode();
            if (!_headersByThread.ContainsKey(threadHashCode))
            {
                _headersByThread.Add(threadHashCode, new Hashtable());
            }
            return _headersByThread[threadHashCode];
        }
    }
 
    void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
    {
        OperationHeaders operationHeaders;
        if (headersFromAction.TryGetValue((string)correlationState, out operationHeaders))
        {
            if (operationHeaders.Out.Count > 0)
            {
                foreach (SoapHeaderAttribute header in operationHeaders.Out)
                {
                    string headerName = header.Name;
                    SoapHeaderHelper sh = shHelpers[header.Type];
                    _headersByThread[Thread.CurrentThread.GetHashCode()][headerName] = sh.GetHeader(headerName, reply.Headers);
                }
            }
        }
    }

    object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        OperationHeaders operationHeaders;
        if (headersFromAction.TryGetValue(request.Headers.Action, out operationHeaders))
        {
            if (operationHeaders.In.Count > 0)
            {
                foreach (SoapHeaderAttribute header in operationHeaders.In)
                {
                    string headerName = header.Name;
                    object headerValue = _headersByThread[Thread.CurrentThread.GetHashCode()][headerName];
                    if (headerValue != null)
                    {
                        SoapHeaderHelper sh = shHelpers[header.Type];
                        sh.AddHeader(headerName, headerValue, request.Headers);
                    }
                }
            }
        }
        return request.Headers.Action;
    }
 
    internal static void Hook(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        //Create a mapping between an operation and it's relevant headers
        var headersFromAction = new Dictionary<string, OperationHeaders>();
        var soapHelpers = new Dictionary<Type, SoapHeaderHelper>();
 
        foreach (OperationDescription op in contractDescription.Operations)
        {
            var soapHeaders = (SoapHeaderAttribute[])op.SyncMethod.GetCustomAttributes(typeof(SoapHeaderAttribute), false);
            if (soapHeaders.Length > 0)
            {
                var headers = new OperationHeaders();
                string action = contractDescription.Namespace + "/" + contractDescription.Name + "/" + op.Name;
                foreach (SoapHeaderAttribute soapHeader in soapHeaders)
                {
                    //prepare a cache of which headers needed in which Action
                    if ((soapHeader.Direction & SoapHeaderDirection.In) == SoapHeaderDirection.In)
                        headers.In.Add(soapHeader);
                    if ((soapHeader.Direction & SoapHeaderDirection.Out) == SoapHeaderDirection.Out)
                        headers.Out.Add(soapHeader);
 
                    //prepare a cache of SoapHeaderHelpers for each header type
                    if (!soapHelpers.ContainsKey(soapHeader.Type))
                        soapHelpers.Add(soapHeader.Type, new SoapHeaderHelper(soapHeader.Type));
                }
                headersFromAction.Add(action, headers);
            }
        }
 
        var clientHook = new SoapHeadersClientHook(headersFromAction, soapHelpers);

        clientRuntime.MessageInspectors.Add(clientHook);
        clientRuntime.ChannelInitializers.Add(clientHook);
    }
 
    void IChannelInitializer.Initialize(IClientChannel channel)
    {
        channel.Extensions.Add(this);
    }
 
    void IExtension<IContextChannel>.Attach(IContextChannel owner)
    {
    }
 
    void IExtension<IContextChannel>.Detach(IContextChannel owner)
    {
    }
}
}

file attachments

comments

Augi wrote Aug 15, 2012 at 4:58 PM

The solution using "Thread.CurrentThread.GetHashCode()" is very hacky because it supposes that the client is running on the same thread on method call start and after the call finishes.

The better way is to implement Extension (implements IExtension<IContextChannel>) that represents the current SOAP headers. Then just create a new instance of this class in the IChannelInitializer.Initialize method call (so each channel has own storage of current headers).

You can read these "current headers" in the IClientMessageInspector.BeforeSendRequest method (from IClientChannel) and then pass them to the IClientMessageInspector.AfterReceiveReply method using correlationState.

wrote Feb 21, 2013 at 10:53 PM