File: System\ServiceModel\Dispatcher\DispatchOperationRuntime.cs
Web Access
Project: src\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.Runtime;
using System.ServiceModel.Channels;
using System.ServiceModel.Diagnostics;
 
namespace System.ServiceModel.Dispatcher
{
    internal class DispatchOperationRuntime
    {
        private static AsyncCallback s_invokeCallback = Fx.ThunkCallback(DispatchOperationRuntime.InvokeCallback);
        private readonly bool _isSessionOpenNotificationEnabled;
        private readonly bool _deserializeRequest;
        private readonly bool _serializeReply;
        private readonly bool _disposeParameters;
 
        internal DispatchOperationRuntime(DispatchOperation operation, ImmutableDispatchRuntime parent)
        {
            if (operation == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(operation));
            }
 
            if (operation.Invoker == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.RuntimeRequiresInvoker0));
            }
 
            _disposeParameters = ((operation.AutoDisposeParameters) && (!operation.HasNoDisposableParameters));
            Parent = parent ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(parent));
            ParameterInspectors = EmptyArray<IParameterInspector>.ToArray(operation.ParameterInspectors);
            FaultFormatter = operation.FaultFormatter;
            _deserializeRequest = operation.DeserializeRequest;
            _serializeReply = operation.SerializeReply;
            Formatter = operation.Formatter;
            Invoker = operation.Invoker;
            IsTerminating = operation.IsTerminating;
            _isSessionOpenNotificationEnabled = operation.IsSessionOpenNotificationEnabled;
            Action = operation.Action;
            Name = operation.Name;
            ReplyAction = operation.ReplyAction;
            IsOneWay = operation.IsOneWay;
 
            if (Formatter == null && (_deserializeRequest || _serializeReply))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.Format(SRP.DispatchRuntimeRequiresFormatter0, Name)));
            }
        }
 
        internal string Action { get; }
 
        internal bool DisposeParameters
        {
            get { return _disposeParameters; }
        }
 
        internal IDispatchFaultFormatter FaultFormatter { get; }
 
        internal IDispatchMessageFormatter Formatter { get; }
 
        internal IOperationInvoker Invoker { get; }
 
        internal bool IsOneWay { get; }
 
        internal bool IsTerminating { get; }
 
        internal string Name { get; }
 
        internal IParameterInspector[] ParameterInspectors { get; }
 
        internal ImmutableDispatchRuntime Parent { get; }
 
        internal string ReplyAction { get; }
 
        private void DeserializeInputs(ref MessageRpc rpc)
        {
            bool success = false;
            try
            {
                rpc.InputParameters = Invoker.AllocateInputs();
                // If the field is true, then this operation is to be invoked at the time the service 
                // channel is opened. The incoming message is created at ChannelHandler level with no 
                // content, so we don't need to deserialize the message.
                if (!_isSessionOpenNotificationEnabled)
                {
                    if (_deserializeRequest)
                    {
                        if (WcfEventSource.Instance.DispatchFormatterDeserializeRequestStartIsEnabled())
                        {
                            WcfEventSource.Instance.DispatchFormatterDeserializeRequestStart(rpc.EventTraceActivity);
                        }
 
                        Formatter.DeserializeRequest(rpc.Request, rpc.InputParameters);
 
                        if (WcfEventSource.Instance.DispatchFormatterDeserializeRequestStopIsEnabled())
                        {
                            WcfEventSource.Instance.DispatchFormatterDeserializeRequestStop(rpc.EventTraceActivity);
                        }
                    }
                    else
                    {
                        rpc.InputParameters[0] = rpc.Request;
                    }
                }
 
                success = true;
            }
            finally
            {
                rpc.DidDeserializeRequestBody = (rpc.Request.State != MessageState.Created);
 
                if (!success && MessageLogger.LoggingEnabled)
                {
                    MessageLogger.LogMessage(ref rpc.Request, MessageLoggingSource.Malformed);
                }
            }
        }
 
        private void InspectInputs(ref MessageRpc rpc)
        {
            if (ParameterInspectors.Length > 0)
            {
                InspectInputsCore(ref rpc);
            }
        }
 
        private void InspectInputsCore(ref MessageRpc rpc)
        {
            for (int i = 0; i < ParameterInspectors.Length; i++)
            {
                IParameterInspector inspector = ParameterInspectors[i];
                rpc.Correlation[i] = inspector.BeforeCall(Name, rpc.InputParameters);
                if (WcfEventSource.Instance.ParameterInspectorBeforeCallInvokedIsEnabled())
                {
                    WcfEventSource.Instance.ParameterInspectorBeforeCallInvoked(rpc.EventTraceActivity, ParameterInspectors[i].GetType().FullName);
                }
            }
        }
 
        private void InspectOutputs(ref MessageRpc rpc)
        {
            if (ParameterInspectors.Length > 0)
            {
                InspectOutputsCore(ref rpc);
            }
        }
 
        private void InspectOutputsCore(ref MessageRpc rpc)
        {
            for (int i = ParameterInspectors.Length - 1; i >= 0; i--)
            {
                IParameterInspector inspector = ParameterInspectors[i];
                inspector.AfterCall(Name, rpc.OutputParameters, rpc.ReturnParameter, rpc.Correlation[i]);
                if (WcfEventSource.Instance.ParameterInspectorAfterCallInvokedIsEnabled())
                {
                    WcfEventSource.Instance.ParameterInspectorAfterCallInvoked(rpc.EventTraceActivity, ParameterInspectors[i].GetType().FullName);
                }
            }
        }
 
        internal void InvokeBegin(ref MessageRpc rpc)
        {
            if (rpc.Error == null)
            {
                object target = rpc.Instance;
                DeserializeInputs(ref rpc);
                InspectInputs(ref rpc);
 
                ValidateMustUnderstand(ref rpc);
 
                IAsyncResult result;
                bool isBeginSuccessful = false;
 
                IResumeMessageRpc resumeRpc = rpc.Pause();
                try
                {
                    result = Invoker.InvokeBegin(target, rpc.InputParameters, s_invokeCallback, resumeRpc);
                    isBeginSuccessful = true;
                }
                finally
                {
                    if (!isBeginSuccessful)
                    {
                        rpc.UnPause();
                    }
                }
 
                if (result == null)
                {
                    throw TraceUtility.ThrowHelperError(new ArgumentNullException("IOperationInvoker.BeginDispatch"),
                        rpc.Request);
                }
 
                if (result.CompletedSynchronously)
                {
                    // if the async call completed synchronously, then the responsibility to call
                    // ProcessMessage{6,7,Cleanup} still remains on this thread
                    rpc.UnPause();
                    rpc.AsyncResult = result;
                }
            }
        }
 
        private static void InvokeCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
 
            IResumeMessageRpc resume = result.AsyncState as IResumeMessageRpc;
 
            if (resume == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.SFxInvalidAsyncResultState0);
            }
 
            resume.SignalConditionalResume(result);
        }
 
 
        internal void InvokeEnd(ref MessageRpc rpc)
        {
            if ((rpc.Error == null))
            {
                rpc.ReturnParameter = Invoker.InvokeEnd(rpc.Instance, out rpc.OutputParameters, rpc.AsyncResult);
 
                InspectOutputs(ref rpc);
 
                SerializeOutputs(ref rpc);
            }
        }
 
        private void SerializeOutputs(ref MessageRpc rpc)
        {
            if (!IsOneWay && Parent.EnableFaults)
            {
                Message reply;
                if (_serializeReply)
                {
                    if (WcfEventSource.Instance.DispatchFormatterSerializeReplyStartIsEnabled())
                    {
                        WcfEventSource.Instance.DispatchFormatterSerializeReplyStart(rpc.EventTraceActivity);
                    }
 
                    reply = Formatter.SerializeReply(rpc.RequestVersion, rpc.OutputParameters, rpc.ReturnParameter);
 
                    if (WcfEventSource.Instance.DispatchFormatterSerializeReplyStopIsEnabled())
                    {
                        WcfEventSource.Instance.DispatchFormatterSerializeReplyStop(rpc.EventTraceActivity);
                    }
 
                    if (reply == null)
                    {
                        string message = SRP.Format(SRP.SFxNullReplyFromFormatter2, Formatter.GetType().ToString(), (Name ?? ""));
                        ErrorBehavior.ThrowAndCatch(new InvalidOperationException(message));
                    }
                }
                else
                {
                    if ((rpc.ReturnParameter == null) && (rpc.OperationContext.RequestContext != null))
                    {
                        string message = SRP.Format(SRP.SFxDispatchRuntimeMessageCannotBeNull, Name);
                        ErrorBehavior.ThrowAndCatch(new InvalidOperationException(message));
                    }
 
                    reply = (Message)rpc.ReturnParameter;
 
                    if ((reply != null) && (!ProxyOperationRuntime.IsValidAction(reply, ReplyAction)))
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.Format(SRP.SFxInvalidReplyAction, Name, reply.Headers.Action ?? "{NULL}", ReplyAction)));
                    }
                }
 
                if (DiagnosticUtility.ShouldUseActivity && rpc.Activity != null && reply != null)
                {
                    TraceUtility.SetActivity(reply, rpc.Activity);
                    if (TraceUtility.ShouldPropagateActivity)
                    {
                        TraceUtility.AddActivityHeader(reply);
                    }
                }
                else if (TraceUtility.ShouldPropagateActivity && reply != null && rpc.ResponseActivityId != Guid.Empty)
                {
                    ActivityIdHeader header = new ActivityIdHeader(rpc.ResponseActivityId);
                    header.AddTo(reply);
                }
 
                //rely on the property set during the message receive to correlate the trace
                if (TraceUtility.MessageFlowTracingOnly)
                {
                    //Guard against MEX scenarios where the message is closed by now
                    if (null != rpc.OperationContext.IncomingMessage && MessageState.Closed != rpc.OperationContext.IncomingMessage.State)
                    {
                        FxTrace.Trace.SetAndTraceTransfer(TraceUtility.GetReceivedActivityId(rpc.OperationContext), true);
                    }
                    else
                    {
                        if (rpc.ResponseActivityId != Guid.Empty)
                        {
                            FxTrace.Trace.SetAndTraceTransfer(rpc.ResponseActivityId, true);
                        }
                    }
                }
 
                if (MessageLogger.LoggingEnabled && null != reply)
                {
                    MessageLogger.LogMessage(ref reply, MessageLoggingSource.ServiceLevelSendReply | MessageLoggingSource.LastChance);
                }
                rpc.Reply = reply;
            }
        }
 
 
        private void ValidateMustUnderstand(ref MessageRpc rpc)
        {
            if (Parent.ValidateMustUnderstand)
            {
                rpc.NotUnderstoodHeaders = rpc.Request.Headers.GetHeadersNotUnderstood();
                if (rpc.NotUnderstoodHeaders != null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                        new MustUnderstandSoapException(rpc.NotUnderstoodHeaders, rpc.Request.Version.Envelope));
                }
            }
        }
    }
}