Friday, May 24, 2013

Adding Custom SOAP Header in .NET

An extra layer has been added to our back-end services (implemented by Java). Now all requests to those services require a set of custom SOAP headers. Existing client apps that consuming those services need to be updated for such change. A colleague asked me what's the best way to do the task in .NET and SoapExtension is my answer. SoapExtension has been available since .NET 1.1. There're many resources online about it and following code is my simple implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Web.Services.Protocols;

namespace SoapHeaderTest
{
    public class TestSoapExtension : SoapExtension
    {
        Stream oldStream;
        Stream newStream;

        public override Stream ChainStream(Stream stream)
        {
            oldStream = stream;
            newStream = new MemoryStream();
            return newStream;
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override object GetInitializer(Type WebServiceType)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {
        }

        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    AddSoapHeader(); // Add SOAP header before the request is sent
                    break;

                case SoapMessageStage.BeforeDeserialize:
                    Copy(oldStream, newStream); // No modification on response, simply copy over
                    newStream.Position = 0; // Rewind the stream
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
                default:
                    throw new Exception("invalid stage");
            }
        }

        // Add custom SOAP header
        private void AddSoapHeader()
        {
            string headerTemplate =
                @"<soap:Header>
                    <h:myHeader xmlns:h=""http://company.com/webservice/soapheader/"">
                        <h:version>{0}</h:version>
                        <h:code>{1}</h:code>
                        <h:proxy>{2}</h:proxy>
                    </h:myHeader>
                </soap:Header>";
            //TODO: populate header values here
            string soapHeader = string.Format(headerTemplate, 1, 2, 3);

            // Get original SOAP message
            newStream.Position = 0;
            string soapMessage = (new StreamReader(newStream)).ReadToEnd();

            // Create a string with header data inserted to the original SOAP Xml before the <soap:Body> tag
            string newSoapMessage = soapMessage.Insert(soapMessage.IndexOf("<soap:Body"), soapHeader);

            // Write new SOAP message to the new stream
            newStream.Position = 0;
            TextWriter writer = new StreamWriter(newStream);
            writer.Write(newSoapMessage);
            writer.Flush();

            // Copy the content from the new stream to the old stream
            newStream.Position = 0;
            Copy(newStream, oldStream);
        }

        private void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
    }
}
To use the SoapExtension, simply register it in web.config for web applications, or app.config for Form, class libraries and console apps:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.web>
      <webServices>
        <soapExtensionTypes>
          <add type="SoapHeaderTest.TestSoapExtension, SoapHeaderTest" priority="1" group="Low"/>
        </soapExtensionTypes>
      </webServices>
    </system.web>
</configuration> 
After registration in web.config or app.config, all requests and responses will go through the custom SoapExtension pipe line.