File: System\ServiceModel\Transactions\WsatTransactionHeader.cs
Web Access
Project: ..\..\..\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.ServiceModel.Channels;
using System.Transactions;
using System.Xml;
 
namespace System.ServiceModel.Transactions
{
    internal class WsatTransactionHeader : MessageHeader
    {
        private string _wsatHeaderElement;
        private string _coordinationNamespace;
        private byte[] _propagationToken;
 
        // Fields for writing a full WS-Coordination context
        private string _atomicTransactionNamespace;
        private AddressingVersion _addressingVersion;
        private Guid _transactionId;
        private uint _expires;
 
        /// <summary>
        /// Constructor for an empty (sentinel) header or for reading.
        /// </summary>
        public WsatTransactionHeader(byte[] propagationToken, string headerElement, string coordinationNamespace)
        {
            _propagationToken = propagationToken;
            _wsatHeaderElement = headerElement;
            _coordinationNamespace = coordinationNamespace;
        }
 
        /// <summary>
        /// Constructor for writing a full WS-Coordination context with OleTx upgrade token.
        /// </summary>
        public WsatTransactionHeader(
            Guid transactionId,
            uint expires,
            byte[] propagationToken,
            string headerElement,
            string coordinationNamespace,
            string atomicTransactionNamespace,
            AddressingVersion addressingVersion)
        {
            _transactionId = transactionId;
            _expires = expires;
            _propagationToken = propagationToken;
            _wsatHeaderElement = headerElement;
            _coordinationNamespace = coordinationNamespace;
            _atomicTransactionNamespace = atomicTransactionNamespace;
            _addressingVersion = addressingVersion;
        }
 
        public override bool MustUnderstand => true;
 
        public override string Name => _wsatHeaderElement;
 
        public override string Namespace => _coordinationNamespace;
 
        public byte[] PropagationToken => _propagationToken;
 
        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            if (_atomicTransactionNamespace != null)
            {
                WriteCoordinationContext(writer);
            }
        }
 
        private void WriteCoordinationContext(XmlDictionaryWriter writer)
        {
            // <wscoor:Identifier>
            writer.WriteStartElement(CoordinationExternalStrings.Prefix,
                CoordinationExternalStrings.Identifier, _coordinationNamespace);
            writer.WriteString("urn:uuid:" + _transactionId.ToString("D"));
            writer.WriteEndElement();
 
            // <wscoor:Expires>
            writer.WriteStartElement(CoordinationExternalStrings.Prefix,
                CoordinationExternalStrings.Expires, _coordinationNamespace);
            writer.WriteValue(_expires);
            writer.WriteEndElement();
 
            // <wscoor:CoordinationType>
            writer.WriteStartElement(CoordinationExternalStrings.Prefix,
                CoordinationExternalStrings.CoordinationType, _coordinationNamespace);
            writer.WriteString(_atomicTransactionNamespace);
            writer.WriteEndElement();
 
            // <wscoor:RegistrationService> - minimal EPR required by WS-Coordination schema.
            // The server uses the OleTx propagation token (below) to unmarshal the transaction
            // directly, so this EPR is not contacted.
            var dummyEpr = new EndpointAddress("https://localhost/wsat/registration");
            dummyEpr.WriteTo(_addressingVersion, writer,
                CoordinationExternalStrings.RegistrationService, _coordinationNamespace);
 
            // <oletx:PropagationToken> - OleTx upgrade token.
            // The server detects this and uses OleTx to unmarshal the transaction directly,
            // bypassing WS-AT coordination protocol.
            WritePropagationTokenElement(writer, _propagationToken);
        }
 
        internal static void WritePropagationTokenElement(XmlDictionaryWriter writer, byte[] propagationToken)
        {
            writer.WriteStartElement(OleTxTransactionExternalStrings.Prefix,
                OleTxTransactionExternalStrings.PropagationToken,
                OleTxTransactionExternalStrings.Namespace);
            writer.WriteBase64(propagationToken, 0, propagationToken.Length);
            writer.WriteEndElement();
        }
 
        public static WsatTransactionHeader ReadFrom(Message message, string headerElement, string ns)
        {
            int index;
            try
            {
                index = message.Headers.FindHeader(headerElement, ns);
            }
            catch (MessageHeaderException e)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                    new TransactionException(SRP.WsatHeaderCorrupt, e));
            }
 
            if (index < 0)
                return null;
 
            WsatTransactionHeader wsatHeader;
            XmlDictionaryReader reader = message.Headers.GetReaderAtHeader(index);
            using (reader)
            {
                try
                {
                    wsatHeader = ReadFrom(reader, headerElement, ns);
                }
                catch (XmlException xe)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                        new TransactionException(SRP.WsatHeaderCorrupt, xe));
                }
            }
 
            MessageHeaderInfo header = message.Headers[index];
            if (!message.Headers.UnderstoodHeaders.Contains(header))
            {
                message.Headers.UnderstoodHeaders.Add(header);
            }
 
            return wsatHeader;
        }
 
        private static WsatTransactionHeader ReadFrom(XmlDictionaryReader reader, string headerElement, string ns)
        {
            reader.ReadStartElement(headerElement, ns);
 
            // A WS-Coordination context contains: Identifier, Expires, CoordinationType,
            // RegistrationService, then optional extensions (including OleTx PropagationToken).
            // We scan through all child elements looking for the OleTx propagation token.
            byte[] propagationToken = null;
            while (reader.IsStartElement())
            {
                if (OleTxTransactionHeader.IsStartPropagationTokenElement(reader))
                {
                    propagationToken = OleTxTransactionHeader.ReadPropagationTokenElement(reader);
                }
                else
                {
                    reader.Skip();
                }
            }
            reader.ReadEndElement();
 
            return new WsatTransactionHeader(propagationToken, headerElement, ns);
        }
    }
}