File: FrameworkFork\System.ServiceModel\System\ServiceModel\Channels\RequestContextBase.cs
Web Access
Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib)
// 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;
using System.Diagnostics;
using System.Runtime;
using System.ServiceModel;
using System.ServiceModel.Diagnostics;
 
namespace System.ServiceModel.Channels
{
    internal abstract class RequestContextBase : RequestContext
    {
        private TimeSpan _defaultSendTimeout;
        private TimeSpan _defaultCloseTimeout;
        private CommunicationState _state = CommunicationState.Opened;
        private Message _requestMessage;
        private Exception _requestMessageException;
        private bool _replySent;
        private bool _replyInitiated;
        private bool _aborted;
        private object _thisLock = new object();
 
        protected RequestContextBase(Message requestMessage, TimeSpan defaultCloseTimeout, TimeSpan defaultSendTimeout)
        {
            _defaultSendTimeout = defaultSendTimeout;
            _defaultCloseTimeout = defaultCloseTimeout;
            _requestMessage = requestMessage;
        }
 
        public void ReInitialize(Message requestMessage)
        {
            _state = CommunicationState.Opened;
            _requestMessageException = null;
            _replySent = false;
            _replyInitiated = false;
            _aborted = false;
            _requestMessage = requestMessage;
        }
 
        public override Message RequestMessage
        {
            get
            {
                if (_requestMessageException != null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(_requestMessageException);
                }
 
                return _requestMessage;
            }
        }
 
        protected void SetRequestMessage(Message requestMessage)
        {
            Fx.Assert(_requestMessageException == null, "Cannot have both a requestMessage and a requestException.");
            _requestMessage = requestMessage;
        }
 
        protected void SetRequestMessage(Exception requestMessageException)
        {
            Fx.Assert(_requestMessage == null, "Cannot have both a requestMessage and a requestException.");
            _requestMessageException = requestMessageException;
        }
 
        protected bool ReplyInitiated
        {
            get { return _replyInitiated; }
        }
 
        protected object ThisLock
        {
            get
            {
                return _thisLock;
            }
        }
 
        public bool Aborted
        {
            get
            {
                return _aborted;
            }
        }
 
        public TimeSpan DefaultCloseTimeout
        {
            get { return _defaultCloseTimeout; }
        }
 
        public TimeSpan DefaultSendTimeout
        {
            get { return _defaultSendTimeout; }
        }
 
        public override void Abort()
        {
            lock (ThisLock)
            {
                if (_state == CommunicationState.Closed)
                    return;
 
                _state = CommunicationState.Closing;
 
                _aborted = true;
            }
 
            try
            {
                this.OnAbort();
            }
            finally
            {
                _state = CommunicationState.Closed;
            }
        }
 
        public override void Close()
        {
            this.Close(_defaultCloseTimeout);
        }
 
        public override void Close(TimeSpan timeout)
        {
            if (timeout < TimeSpan.Zero)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("timeout", timeout, SRServiceModel.ValueMustBeNonNegative));
            }
 
            bool sendAck = false;
            lock (ThisLock)
            {
                if (_state != CommunicationState.Opened)
                    return;
 
                if (TryInitiateReply())
                {
                    sendAck = true;
                }
 
                _state = CommunicationState.Closing;
            }
 
            TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
            bool throwing = true;
 
            try
            {
                if (sendAck)
                {
                    OnReply(null, timeoutHelper.RemainingTime());
                }
 
                OnClose(timeoutHelper.RemainingTime());
                _state = CommunicationState.Closed;
                throwing = false;
            }
            finally
            {
                if (throwing)
                    this.Abort();
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
 
            if (!disposing)
                return;
 
            if (_replySent)
            {
                this.Close();
            }
            else
            {
                this.Abort();
            }
        }
 
        protected abstract void OnAbort();
        protected abstract void OnClose(TimeSpan timeout);
        protected abstract void OnReply(Message message, TimeSpan timeout);
        protected abstract IAsyncResult OnBeginReply(Message message, TimeSpan timeout, AsyncCallback callback, object state);
        protected abstract void OnEndReply(IAsyncResult result);
 
        protected void ThrowIfInvalidReply()
        {
            if (_state == CommunicationState.Closed || _state == CommunicationState.Closing)
            {
                if (_aborted)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationObjectAbortedException(SRServiceModel.RequestContextAborted));
                else
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(this.GetType().FullName));
            }
 
            if (_replyInitiated)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRServiceModel.ReplyAlreadySent));
        }
 
        /// <summary>
        /// Attempts to initiate the reply. If a reply is not initiated already (and the object is opened), 
        /// then it initiates the reply and returns true. Otherwise, it returns false.
        /// </summary>
        protected bool TryInitiateReply()
        {
            lock (_thisLock)
            {
                if ((_state != CommunicationState.Opened) || _replyInitiated)
                {
                    return false;
                }
                else
                {
                    _replyInitiated = true;
                    return true;
                }
            }
        }
 
        public override IAsyncResult BeginReply(Message message, AsyncCallback callback, object state)
        {
            return this.BeginReply(message, _defaultSendTimeout, callback, state);
        }
 
        public override IAsyncResult BeginReply(Message message, TimeSpan timeout, AsyncCallback callback, object state)
        {
            // "null" is a valid reply (signals a 202-style "ack"), so we don't have a null-check here
            lock (_thisLock)
            {
                this.ThrowIfInvalidReply();
                _replyInitiated = true;
            }
 
            return OnBeginReply(message, timeout, callback, state);
        }
 
        public override void EndReply(IAsyncResult result)
        {
            OnEndReply(result);
            _replySent = true;
        }
 
        public override void Reply(Message message)
        {
            this.Reply(message, _defaultSendTimeout);
        }
 
        public override void Reply(Message message, TimeSpan timeout)
        {
            // "null" is a valid reply (signals a 202-style "ack"), so we don't have a null-check here
            lock (_thisLock)
            {
                this.ThrowIfInvalidReply();
                _replyInitiated = true;
            }
 
            this.OnReply(message, timeout);
            _replySent = true;
        }
 
        // This method is designed for WebSocket only, and will only be used once the WebSocket response was sent.
        // For WebSocket, we never call HttpRequestContext.Reply to send the response back. 
        // Instead we call AcceptWebSocket directly. So we need to set the replyInitiated and 
        // replySent boolean to be true once the response was sent successfully. Otherwise when we 
        // are disposing the HttpRequestContext, we will see a bunch of warnings in trace log.
        protected void SetReplySent()
        {
            lock (_thisLock)
            {
                this.ThrowIfInvalidReply();
                _replyInitiated = true;
            }
 
            _replySent = true;
        }
    }
 
    internal class RequestContextMessageProperty : IDisposable
    {
        private RequestContext _context;
        private object _thisLock = new object();
 
        public RequestContextMessageProperty(RequestContext context)
        {
            _context = context;
        }
 
        public static string Name
        {
            get { return "requestContext"; }
        }
 
        void IDisposable.Dispose()
        {
            bool success = false;
            RequestContext thisContext;
 
            lock (_thisLock)
            {
                if (_context == null)
                    return;
                thisContext = _context;
                _context = null;
            }
 
            try
            {
                thisContext.Close();
                success = true;
            }
            catch (CommunicationException)
            {
            }
            catch (TimeoutException e)
            {
                if (WcfEventSource.Instance.CloseTimeoutIsEnabled())
                {
                    WcfEventSource.Instance.CloseTimeout(e.Message);
                }
            }
            finally
            {
                if (!success)
                {
                    thisContext.Abort();
                }
            }
        }
    }
}