File: System\ServiceModel\Dispatcher\TransactionValidationBehavior.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Dispatcher
{
    using System.Collections.ObjectModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.ServiceModel.Transactions;
 
    class TransactionValidationBehavior : IEndpointBehavior, IServiceBehavior
    {
        static TransactionValidationBehavior instance;
 
        internal static TransactionValidationBehavior Instance
        {
            get
            {
                if (instance == null)
                    instance = new TransactionValidationBehavior();
                return instance;
            }
        }
 
        TransactionValidationBehavior() { }
 
        void ValidateTransactionFlowRequired(string resource, string name, ServiceEndpoint endpoint)
        {
            bool anOperationRequiresTxFlow = false;
            for (int i = 0; i < endpoint.Contract.Operations.Count; i++)
            {
                OperationDescription operationDescription = endpoint.Contract.Operations[i];
                TransactionFlowAttribute transactionFlow = operationDescription.Behaviors.Find<TransactionFlowAttribute>();
                if (transactionFlow != null && transactionFlow.Transactions == TransactionFlowOption.Mandatory)
                {
                    anOperationRequiresTxFlow = true;
                    break;
                }
            }
 
            if (anOperationRequiresTxFlow)
            {
                CustomBinding binding = new CustomBinding(endpoint.Binding);
                TransactionFlowBindingElement transactionFlowBindingElement =
                                              binding.Elements.Find<TransactionFlowBindingElement>();
 
                if (transactionFlowBindingElement == null || !transactionFlowBindingElement.Transactions)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        String.Format(Globalization.CultureInfo.CurrentCulture, SR.GetString(resource), name, binding.Name)));
                }
            }
        }
 
        void IEndpointBehavior.Validate(ServiceEndpoint serviceEndpoint)
        {
            if (serviceEndpoint == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serviceEndpoint");
            ValidateTransactionFlowRequired(SR.ChannelHasAtLeastOneOperationWithTransactionFlowEnabled,
                           serviceEndpoint.Contract.Name,
                           serviceEndpoint);
            EnsureNoOneWayTransactions(serviceEndpoint);
            ValidateNoMSMQandTransactionFlow(serviceEndpoint);
            ValidateCallbackBehaviorAttributeWithNoScopeRequired(serviceEndpoint);
            OperationDescription autoCompleteFalseOperation = GetAutoCompleteFalseOperation(serviceEndpoint);
            if (autoCompleteFalseOperation != null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                    SR.GetString(SR.SFxTransactionAutoCompleteFalseOnCallbackContract, autoCompleteFalseOperation.Name, serviceEndpoint.Contract.Name)));
            }
 
        }
 
        void ValidateCallbackBehaviorAttributeWithNoScopeRequired(ServiceEndpoint endpoint)
        {
            // If the endpoint has no operations with TransactionScopeRequired=true, disallow any
            // transaction-related properties on the CallbackBehaviorAttribute
            if (!HasTransactedOperations(endpoint))
            {
                CallbackBehaviorAttribute attribute = endpoint.Behaviors.Find<CallbackBehaviorAttribute>();
                if (attribute != null)
                {
                    if (attribute.TransactionTimeoutSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionTransactionTimeoutNeedsScope, endpoint.Contract.Name)));
                    }
 
                    if (attribute.IsolationLevelSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionIsolationLevelNeedsScope, endpoint.Contract.Name)));
                    }
                }
            }
        }
 
        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters)
        {
        }
 
        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher)
        {
        }
 
        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)
        {
        }
 
        void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }
 
        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription service, ServiceHostBase serviceHostBase)
        {
        }
 
        void IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase serviceHostBase)
        {
            if (service == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("service");
 
            ValidateNotConcurrentWhenReleaseServiceInstanceOnTxComplete(service);
            bool singleThreaded = IsSingleThreaded(service);
 
            for (int i = 0; i < service.Endpoints.Count; i++)
            {
                ServiceEndpoint endpoint = service.Endpoints[i];
 
                ValidateTransactionFlowRequired(SR.ServiceHasAtLeastOneOperationWithTransactionFlowEnabled,
                               service.Name,
                               endpoint);
                EnsureNoOneWayTransactions(endpoint);
                ValidateNoMSMQandTransactionFlow(endpoint);
 
                ContractDescription contract = endpoint.Contract;
                for (int j = 0; j < contract.Operations.Count; j++)
                {
                    OperationDescription operation = contract.Operations[j];
                    ValidateScopeRequiredAndAutoComplete(operation, singleThreaded, contract.Name);
                }
 
                ValidateAutoCompleteFalseRequirements(service, endpoint);
            }
 
            ValidateServiceBehaviorAttributeWithNoScopeRequired(service);
            ValidateTransactionAutoCompleteOnSessionCloseHasSession(service);
        }
 
 
        void ValidateAutoCompleteFalseRequirements(ServiceDescription service, ServiceEndpoint endpoint)
        {
            OperationDescription autoCompleteFalseOperation = GetAutoCompleteFalseOperation(endpoint);
 
            if (autoCompleteFalseOperation != null)
            {
                // Does the service have InstanceContextMode.PerSession or Shareable?
                ServiceBehaviorAttribute serviceBehavior = service.Behaviors.Find<ServiceBehaviorAttribute>();
                if (serviceBehavior != null)
                {
                    InstanceContextMode instanceMode = serviceBehavior.InstanceContextMode;
                    if (instanceMode != InstanceContextMode.PerSession)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionAutoCompleteFalseAndInstanceContextMode,
                            endpoint.Contract.Name, autoCompleteFalseOperation.Name)));
                    }
                }
 
                // Does the binding support sessions?
                if (!autoCompleteFalseOperation.IsInsideTransactedReceiveScope)
                {
                    if (!RequiresSessions(endpoint))
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionAutoCompleteFalseAndSupportsSession,
                            endpoint.Contract.Name, autoCompleteFalseOperation.Name)));
                    }
                }
            }
        }
 
        OperationDescription GetAutoCompleteFalseOperation(ServiceEndpoint endpoint)
        {
            foreach (OperationDescription operation in endpoint.Contract.Operations)
            {
                if (!IsAutoComplete(operation))
                {
                    return operation;
                }
            }
            return null;
        }
 
        void ValidateTransactionAutoCompleteOnSessionCloseHasSession(ServiceDescription service)
        {
            ServiceBehaviorAttribute serviceBehavior = service.Behaviors.Find<ServiceBehaviorAttribute>();
 
            if (serviceBehavior != null)
            {
                InstanceContextMode instanceMode = serviceBehavior.InstanceContextMode;
                if (serviceBehavior.TransactionAutoCompleteOnSessionClose &&
                    instanceMode != InstanceContextMode.PerSession)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.SFxTransactionAutoCompleteOnSessionCloseNoSession, service.Name)));
                }
            }
 
        }
 
 
        void ValidateServiceBehaviorAttributeWithNoScopeRequired(ServiceDescription service)
        {
            // If the service has no operations with TransactionScopeRequired=true, disallow any
            // transaction-related properties on the ServiceBehaviorAttribute
            if (!HasTransactedOperations(service))
            {
                ServiceBehaviorAttribute attribute = service.Behaviors.Find<ServiceBehaviorAttribute>();
                if (attribute != null)
                {
                    if (attribute.TransactionTimeoutSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionTransactionTimeoutNeedsScope, service.Name)));
                    }
 
                    if (attribute.IsolationLevelSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionIsolationLevelNeedsScope, service.Name)));
                    }
 
                    if (attribute.ReleaseServiceInstanceOnTransactionCompleteSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionReleaseServiceInstanceOnTransactionCompleteNeedsScope, service.Name)));
                    }
 
                    if (attribute.TransactionAutoCompleteOnSessionCloseSet)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR.GetString(SR.SFxTransactionTransactionAutoCompleteOnSessionCloseNeedsScope, service.Name)));
                    }
                }
            }
        }
 
        void EnsureNoOneWayTransactions(ServiceEndpoint endpoint)
        {
            CustomBinding binding = new CustomBinding(endpoint.Binding);
            TransactionFlowBindingElement txFlowBindingElement = binding.Elements.Find<TransactionFlowBindingElement>();
            if (txFlowBindingElement != null)
            {
                for (int i = 0; i < endpoint.Contract.Operations.Count; i++)
                {
                    OperationDescription operation = endpoint.Contract.Operations[i];
                    if (operation.IsOneWay)
                    {
                        TransactionFlowAttribute tfbp = operation.Behaviors.Find<TransactionFlowAttribute>();
                        TransactionFlowOption transactions;
                        if (tfbp != null)
                        {
                            transactions = tfbp.Transactions;
                        }
                        else
                        {
                            transactions = TransactionFlowOption.NotAllowed;
                        }
                        if (TransactionFlowOptionHelper.AllowedOrRequired(transactions))
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR.GetString(SR.SFxOneWayAndTransactionsIncompatible, endpoint.Contract.Name, operation.Name)));
                        }
                    }
                }
            }
        }
 
        bool HasTransactedOperations(ServiceDescription service)
        {
            for (int i = 0; i < service.Endpoints.Count; i++)
            {
                if (HasTransactedOperations(service.Endpoints[i]))
                {
                    return true;
                }
            }
            return false;
        }
 
        bool HasTransactedOperations(ServiceEndpoint endpoint)
        {
            for (int j = 0; j < endpoint.Contract.Operations.Count; j++)
            {
                OperationDescription operation = endpoint.Contract.Operations[j];
                OperationBehaviorAttribute attribute = operation.Behaviors.Find<OperationBehaviorAttribute>();
 
                if (attribute != null && attribute.TransactionScopeRequired)
                {
                    return true;
                }
            }
            return false;
        }
 
        bool IsSingleThreaded(ServiceDescription service)
        {
            ServiceBehaviorAttribute attribute = service.Behaviors.Find<ServiceBehaviorAttribute>();
 
            if (attribute != null)
            {
                return (attribute.ConcurrencyMode == ConcurrencyMode.Single);
            }
 
            // The default is ConcurrencyMode.Single
            return true;
        }
 
        bool IsAutoComplete(OperationDescription operation)
        {
            OperationBehaviorAttribute attribute = operation.Behaviors.Find<OperationBehaviorAttribute>();
 
            if (attribute != null)
            {
                return attribute.TransactionAutoComplete;
            }
 
            // The default is TransactionAutoComplete=true
            return true;
        }
 
        bool RequiresSessions(ServiceEndpoint endpoint)
        {
            return endpoint.Contract.SessionMode == SessionMode.Required;
        }
 
        void ValidateScopeRequiredAndAutoComplete(OperationDescription operation,
                                                  bool singleThreaded,
                                                  string contractName)
        {
            OperationBehaviorAttribute attribute = operation.Behaviors.Find<OperationBehaviorAttribute>();
 
            if (attribute != null)
            {
                if (!singleThreaded && !attribute.TransactionAutoComplete)
                {
                    string id = SR.SFxTransactionNonConcurrentOrAutoComplete2;
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(id, contractName, operation.Name)));
                }
            }
        }
 
        void ValidateNoMSMQandTransactionFlow(ServiceEndpoint endpoint)
        {
            BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
 
            if (bindingElements.Find<TransactionFlowBindingElement>() != null &&
                bindingElements.Find<MsmqTransportBindingElement>() != null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.SFxTransactionFlowAndMSMQ, endpoint.Address.Uri.AbsoluteUri)));
            }
        }
 
        void ValidateNotConcurrentWhenReleaseServiceInstanceOnTxComplete(ServiceDescription service)
        {
            ServiceBehaviorAttribute attribute = service.Behaviors.Find<ServiceBehaviorAttribute>();
 
            if (attribute != null && HasTransactedOperations(service))
            {
                if (attribute.ReleaseServiceInstanceOnTransactionComplete && !IsSingleThreaded(service))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                        new InvalidOperationException(SR.GetString(
                            SR.SFxTransactionNonConcurrentOrReleaseServiceInstanceOnTxComplete, service.Name)));
                }
 
            }
        }
    }
}