File: System\ServiceModel\Channels\TransactionChannel.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.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Transactions;
using System.Threading.Tasks;
using System.Transactions;
using FxResources.System.ServiceModel.Primitives;
 
namespace System.ServiceModel.Channels
{
    internal interface ITransactionChannel
    {
        void WriteTransactionDataToMessage(Message message, MessageDirection direction);
        void ReadIssuedTokens(Message message, MessageDirection direction);
    }
 
    internal abstract class TransactionChannel<TChannel>
        : LayeredChannel<TChannel>, ITransactionChannel where TChannel : class, IChannel
    {
        private ITransactionChannelManager _factory;
 
        protected TransactionChannel(ChannelManagerBase channelManager, TChannel innerChannel)
            : base(channelManager, innerChannel)
        {
            _factory = (ITransactionChannelManager)channelManager;
 
            if (_factory.TransactionProtocol == TransactionProtocol.OleTransactions)
            {
                Formatter = TransactionFormatter.OleTxFormatter;
            }
            else if (_factory.TransactionProtocol == TransactionProtocol.WSAtomicTransactionOctober2004)
            {
                Formatter = TransactionFormatter.WsatFormatter10;
            }
            else if (_factory.TransactionProtocol == TransactionProtocol.WSAtomicTransaction11)
            {
                Formatter = TransactionFormatter.WsatFormatter11;
            }
            else
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                    new ArgumentException(SRP.SFxBadTransactionProtocols));
            }
        }
 
        internal TransactionFormatter Formatter { get; }
 
        internal TransactionProtocol Protocol => _factory.TransactionProtocol;
 
        public override T GetProperty<T>()
        {
            if (typeof(T) == typeof(FaultConverter))
            {
                return (T)(object)new TransactionChannelFaultConverter<TChannel>(this);
            }
 
            return base.GetProperty<T>();
        }
 
        public T GetInnerProperty<T>() where T : class
        {
            return base.InnerChannel.GetProperty<T>();
        }
 
        private ICollection<RequestSecurityTokenResponse> GetIssuedTokens(Message message)
        {
            return IssuedTokensHeader.ExtractIssuances(message, _factory.StandardsManager, message.Version.Envelope.UltimateDestinationActorValues, null);
        }
 
        private void FaultOnMessage(Message message, string reason, string codeString)
        {
            FaultCode code = FaultCode.CreateSenderFaultCode(codeString, FaultCodeConstants.Namespaces.Transactions);
            FaultException fault = new FaultException(reason, code, FaultCodeConstants.Actions.Transactions);
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(fault);
        }
 
        public void ReadIssuedTokens(Message message, MessageDirection direction)
        {
            TransactionFlowOption option = _factory.FlowIssuedTokens;
 
            ICollection<RequestSecurityTokenResponse> issuances = GetIssuedTokens(message);
 
            if (issuances != null && issuances.Count != 0)
            {
                if (option == TransactionFlowOption.NotAllowed)
                {
                    FaultOnMessage(message, SRP.IssuedTokenFlowNotAllowed, FaultCodeConstants.Codes.IssuedTokenFlowNotAllowed);
                }
 
                foreach (RequestSecurityTokenResponse rstr in issuances)
                {
                    TransactionFlowProperty.Ensure(message).IssuedTokens.Add(rstr);
                }
            }
        }
 
        private void ReadTransactionFromMessage(Message message, TransactionFlowOption txFlowOption)
        {
            TransactionInfo transactionInfo = null;
            try
            {
                transactionInfo = Formatter.ReadTransaction(message);
            }
            catch (TransactionException e)
            {
                DiagnosticUtility.TraceHandledException(e, TraceEventType.Error);
                FaultOnMessage(message, SRP.Format(SRP.SFxTransactionDeserializationFailed, e.Message), FaultCodeConstants.Codes.TransactionHeaderMalformed);
            }
 
            if (transactionInfo != null)
            {
                TransactionMessageProperty.Set(transactionInfo, message);
            }
            else if (txFlowOption == TransactionFlowOption.Mandatory)
            {
                FaultOnMessage(message, SRP.SFxTransactionFlowRequired, FaultCodeConstants.Codes.TransactionHeaderMissing);
            }
        }
 
        public virtual void ReadTransactionDataFromMessage(Message message, MessageDirection direction)
        {
            ReadIssuedTokens(message, direction);
 
            TransactionFlowOption txFlowOption = _factory.GetTransaction(direction, message.Headers.Action);
            if (TransactionFlowOptionHelper.AllowedOrRequired(txFlowOption))
            {
                ReadTransactionFromMessage(message, txFlowOption);
            }
        }
 
        public void WriteTransactionDataToMessage(Message message, MessageDirection direction)
        {
            TransactionFlowOption txFlowOption = _factory.GetTransaction(direction, message.Headers.Action);
            if (TransactionFlowOptionHelper.AllowedOrRequired(txFlowOption))
            {
                WriteTransactionToMessage(message, txFlowOption);
            }
 
            if (TransactionFlowOptionHelper.AllowedOrRequired(_factory.FlowIssuedTokens))
            {
                WriteIssuedTokens(message, direction);
            }
        }
 
        private void WriteTransactionToMessage(Message message, TransactionFlowOption txFlowOption)
        {
            Transaction transaction = TransactionFlowProperty.TryGetTransaction(message);
 
            if (transaction != null)
            {
                try
                {
                    Formatter.WriteTransaction(transaction, message);
                }
                catch (TransactionException e)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(e.Message, e));
                }
            }
            else if (txFlowOption == TransactionFlowOption.Mandatory)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                    new ProtocolException(SRP.SFxTransactionFlowRequired));
            }
        }
 
        private void WriteIssuedTokens(Message message, MessageDirection direction)
        {
            ICollection<RequestSecurityTokenResponse> issuances = TransactionFlowProperty.TryGetIssuedTokens(message);
            if (issuances != null)
            {
                IssuedTokensHeader header = new IssuedTokensHeader(issuances, _factory.StandardsManager);
                message.Headers.Add(header);
            }
        }
    }
 
    internal class TransactionOutputChannelGeneric<TChannel> : TransactionChannel<TChannel>, IOutputChannel, IAsyncOutputChannel where TChannel : class, IOutputChannel
    {
        public TransactionOutputChannelGeneric(ChannelManagerBase channelManager, TChannel innerChannel)
            : base(channelManager, innerChannel)
        {
        }
 
        public EndpointAddress RemoteAddress => InnerChannel.RemoteAddress;
 
        public Uri Via => InnerChannel.Via;
 
        public void Send(Message message)
        {
            Send(message, DefaultSendTimeout);
        }
 
        public void Send(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            InnerChannel.Send(message, timeoutHelper.RemainingTime());
        }
 
        public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state)
        {
            return BeginSend(message, DefaultSendTimeout, callback, state);
        }
 
        public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            return InnerChannel.BeginSend(message, timeoutHelper.RemainingTime(), callback, state);
        }
 
        public void EndSend(IAsyncResult result)
        {
            InnerChannel.EndSend(result);
        }
 
        public Task SendAsync(Message message)
        {
            return SendAsync(message, DefaultSendTimeout);
        }
 
        public Task SendAsync(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            if (InnerChannel is IAsyncOutputChannel asyncChannel)
            {
                return asyncChannel.SendAsync(message, timeoutHelper.RemainingTime());
            }
            else
            {
                return Task.Factory.FromAsync(InnerChannel.BeginSend, InnerChannel.EndSend, message, timeoutHelper.RemainingTime(), null);
            }
        }
    }
 
    internal class TransactionRequestChannelGeneric<TChannel> : TransactionChannel<TChannel>, IRequestChannel, IAsyncRequestChannel where TChannel : class, IRequestChannel
    {
        public TransactionRequestChannelGeneric(ChannelManagerBase channelManager, TChannel innerChannel)
            : base(channelManager, innerChannel)
        {
        }
 
        public EndpointAddress RemoteAddress => InnerChannel.RemoteAddress;
 
        public Uri Via => InnerChannel.Via;
 
        public Message Request(Message message)
        {
            return Request(message, DefaultSendTimeout);
        }
 
        public Message Request(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            Message reply = InnerChannel.Request(message, timeoutHelper.RemainingTime());
            if (reply != null)
                ReadIssuedTokens(reply, MessageDirection.Output);
            return reply;
        }
 
        public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
        {
            return BeginRequest(message, DefaultSendTimeout, callback, state);
        }
 
        public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            return InnerChannel.BeginRequest(message, timeoutHelper.RemainingTime(), callback, state);
        }
 
        public Message EndRequest(IAsyncResult result)
        {
            Message reply = InnerChannel.EndRequest(result);
            if (reply != null)
                ReadIssuedTokens(reply, MessageDirection.Output);
            return reply;
        }
 
        public Task<Message> RequestAsync(Message message)
        {
            return RequestAsync(message, DefaultSendTimeout);
        }
 
        public async Task<Message> RequestAsync(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, MessageDirection.Input);
            Message reply;
            if (InnerChannel is IAsyncRequestChannel asyncChannel)
            {
                reply = await asyncChannel.RequestAsync(message, timeoutHelper.RemainingTime());
            }
            else
            {
                reply = await Task.Factory.FromAsync(
                    InnerChannel.BeginRequest, InnerChannel.EndRequest, message, timeoutHelper.RemainingTime(), null);
            }
            if (reply != null)
                ReadIssuedTokens(reply, MessageDirection.Output);
            return reply;
        }
    }
 
    internal class TransactionDuplexChannelGeneric<TChannel> : TransactionReceiveChannelGeneric<TChannel>, IDuplexChannel, IAsyncDuplexChannel where TChannel : class, IDuplexChannel
    {
        private MessageDirection _sendMessageDirection;
 
        public TransactionDuplexChannelGeneric(ChannelManagerBase channelManager, TChannel innerChannel, MessageDirection direction)
            : base(channelManager, innerChannel, direction)
        {
            if (direction == MessageDirection.Input)
            {
                _sendMessageDirection = MessageDirection.Output;
            }
            else
            {
                _sendMessageDirection = MessageDirection.Input;
            }
        }
 
        public EndpointAddress RemoteAddress => InnerChannel.RemoteAddress;
 
        public Uri Via => InnerChannel.Via;
 
        public override void ReadTransactionDataFromMessage(Message message, MessageDirection direction)
        {
            try
            {
                base.ReadTransactionDataFromMessage(message, direction);
            }
            catch (FaultException fault)
            {
                Message reply = Message.CreateMessage(message.Version, fault.CreateMessageFault(), fault.Action);
 
                RequestReplyCorrelator.AddressReply(reply, message);
                RequestReplyCorrelator.PrepareReply(reply, message.Headers.MessageId);
 
                try
                {
                    Send(reply);
                }
                finally
                {
                    reply.Close();
                }
 
                throw;
            }
        }
 
        public void Send(Message message)
        {
            Send(message, DefaultSendTimeout);
        }
 
        public void Send(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, _sendMessageDirection);
            InnerChannel.Send(message, timeoutHelper.RemainingTime());
        }
 
        public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state)
        {
            return BeginSend(message, DefaultSendTimeout, callback, state);
        }
 
        public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, _sendMessageDirection);
            return InnerChannel.BeginSend(message, timeoutHelper.RemainingTime(), callback, state);
        }
 
        public void EndSend(IAsyncResult result)
        {
            InnerChannel.EndSend(result);
        }
 
        public Task SendAsync(Message message)
        {
            return SendAsync(message, DefaultSendTimeout);
        }
 
        public Task SendAsync(Message message, TimeSpan timeout)
        {
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            WriteTransactionDataToMessage(message, _sendMessageDirection);
            if (InnerChannel is IAsyncOutputChannel asyncChannel)
            {
                return asyncChannel.SendAsync(message, timeoutHelper.RemainingTime());
            }
            else
            {
                return Task.Factory.FromAsync(InnerChannel.BeginSend, InnerChannel.EndSend, message, timeoutHelper.RemainingTime(), null);
            }
        }
    }
 
    internal class TransactionReceiveChannelGeneric<TChannel> : TransactionChannel<TChannel>, IInputChannel, IAsyncInputChannel
    where TChannel : class, IInputChannel
    {
        private MessageDirection _receiveMessageDirection;
 
        public TransactionReceiveChannelGeneric(ChannelManagerBase channelManager, TChannel innerChannel, MessageDirection direction)
            : base(channelManager, innerChannel)
        {
            _receiveMessageDirection = direction;
        }
 
        public EndpointAddress LocalAddress => InnerChannel.LocalAddress;
 
        public Message Receive()
        {
            return Receive(DefaultReceiveTimeout);
        }
 
        public Message Receive(TimeSpan timeout)
        {
            return InputChannel.HelpReceive(this, timeout);
        }
 
        public IAsyncResult BeginReceive(AsyncCallback callback, object state)
        {
            return BeginReceive(DefaultReceiveTimeout, callback, state);
        }
 
        public IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InputChannel.HelpReceiveAsync(this, timeout).ToApm(callback, state);
        }
 
        public Message EndReceive(IAsyncResult result)
        {
            return result.ToApmEnd<Message>();
        }
 
        public IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InnerChannel.BeginTryReceive(timeout, callback, state);
        }
 
        public virtual bool EndTryReceive(IAsyncResult asyncResult, out Message message)
        {
            if (!InnerChannel.EndTryReceive(asyncResult, out message))
            {
                return false;
            }
 
            if (message != null)
            {
                ReadTransactionDataFromMessage(message, _receiveMessageDirection);
            }
 
            return true;
        }
 
        public virtual bool TryReceive(TimeSpan timeout, out Message message)
        {
            if (!InnerChannel.TryReceive(timeout, out message))
            {
                return false;
            }
 
            if (message != null)
            {
                ReadTransactionDataFromMessage(message, _receiveMessageDirection);
            }
 
            return true;
        }
 
        public bool WaitForMessage(TimeSpan timeout)
        {
            return InnerChannel.WaitForMessage(timeout);
        }
 
        public IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return InnerChannel.BeginWaitForMessage(timeout, callback, state);
        }
 
        public bool EndWaitForMessage(IAsyncResult result)
        {
            return InnerChannel.EndWaitForMessage(result);
        }
 
        public Task<bool> WaitForMessageAsync(TimeSpan timeout)
        {
            if (InnerChannel is IAsyncInputChannel asyncInputChannel)
            {
                return asyncInputChannel.WaitForMessageAsync(timeout);
            }
            else
            {
                return Task.Factory.FromAsync(BeginWaitForMessage, EndWaitForMessage, timeout, null);
            }
        }
 
        public Task<Message> ReceiveAsync()
        {
            return ReceiveAsync(DefaultReceiveTimeout);
        }
 
        public Task<Message> ReceiveAsync(TimeSpan timeout)
        {
            if (InnerChannel is IAsyncInputChannel asyncChannel)
            {
                return InputChannel.HelpReceiveAsync(asyncChannel, timeout);
            }
            else
            {
                return Task.Factory.FromAsync(BeginReceive, EndReceive, timeout, null);
            }
        }
 
        public Task<(bool, Message)> TryReceiveAsync(TimeSpan timeout)
        {
            if (InnerChannel is IAsyncInputChannel asyncChannel)
            {
                return asyncChannel.TryReceiveAsync(timeout);
            }
            else
            {
                return TaskHelpers.FromAsync<TimeSpan, bool, Message>(BeginTryReceive, EndTryReceive, timeout, null);
            }
        }
    }
 
    internal class TransactionOutputDuplexChannelGeneric<TChannel> : TransactionDuplexChannelGeneric<TChannel> where TChannel : class, IDuplexChannel
    {
        public TransactionOutputDuplexChannelGeneric(ChannelManagerBase channelManager, TChannel innerChannel)
            : base(channelManager, innerChannel, MessageDirection.Output)
        {
        }
    }
}