File: System\ServiceModel\Activities\Send.cs
Project: ndp\cdf\src\NetFx40\System.ServiceModel.Activities\System.ServiceModel.Activities.csproj (System.ServiceModel.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.ServiceModel.Activities
{
    using System.Activities;
    using System.Activities.Expressions;
    using System.Activities.Statements;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Net.Security;
    using System.Runtime;
    using System.Security.Principal;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.ServiceModel.XamlIntegration;
    using System.Windows.Markup;
    using System.Xml.Linq;
 
    [ContentProperty("Content")]
    public sealed class Send : Activity
    {
        ToRequest requestFormatter;
        InternalSendMessage internalSend;
        Collection<Type> knownTypes;
 
        Collection<CorrelationInitializer> correlationInitializers;
 
        bool isOneWay;
        IClientMessageFormatter lazyFormatter;
        IList<CorrelationQuery> lazyCorrelationQueries;
 
        bool? channelCacheEnabled;
 
        public Send()
        {
            this.isOneWay = true; // ReceiveReply if defined by user, will set this to false
            this.TokenImpersonationLevel = TokenImpersonationLevel.Identification;
            base.Implementation = () =>
            {
                // if CacheMetadata isn't called, bail early
                if (this.internalSend == null)
                {
                    return null;
                }
 
                if (this.requestFormatter == null) 
                {
                    return this.internalSend;
                }
                else
                {
                    Variable<Message> request = new Variable<Message> { Name = "RequestMessage" };
                    this.requestFormatter.Message = new OutArgument<Message>(request);
                    this.requestFormatter.Send = this;
                    this.internalSend.Message = new InArgument<Message>(request);
                    this.internalSend.MessageOut = new OutArgument<Message>(request);
 
                    return new MessagingNoPersistScope
                    {
                        Body = new Sequence
                        {
                            Variables = { request },
                            Activities = 
                            { 
                                this.requestFormatter, 
                                this.internalSend, 
                            }
                        }
                    };
                }
            };
        }
 
        // the content to send (either message or parameters-based) declared by the user
        [DefaultValue(null)]
        public SendContent Content
        {
            get;
            set;
        }
 
        // Internally, we should always use InternalContent since this property may have default content that we added
        internal SendContent InternalContent
        {
            get
            {
                return this.Content ?? SendContent.DefaultSendContent;
            }
        }
 
        // Additional correlations allow situations where a "session" involves multiple
        // messages between two workflow instances.
        public Collection<CorrelationInitializer> CorrelationInitializers
        {
            get
            {
                if (this.correlationInitializers == null)
                {
                    this.correlationInitializers = new Collection<CorrelationInitializer>();
                }
                return this.correlationInitializers;
            }
        }
 
        // Allows the action for the message to be specified by the user/designer.
        // If not set, the value is constructed from the Name of the activity.
        // If specified on the Send side of a message, the Receive side needs to have the same value in order
        // for the message to be delivered correctly.
        [DefaultValue(null)]
        public string Action
        {
            get;
            set;
        }
 
        [DefaultValue(null)]
        public InArgument<CorrelationHandle> CorrelatesWith
        {
            get;
            set;
        }
 
        // Specifies the endpoint of the service we are sending to. If not specified, it must come
        // from the configuration file.
        [DefaultValue(null)]
        public Endpoint Endpoint
        {
            get;
            set;
        }
 
        // This is used to load the client Endpoint from configuration. This should be mutually exclusive
        // with the Endpoint property.
        // optional defaults to ServiceContractName.LocalName
        [DefaultValue(null)]
        public string EndpointConfigurationName
        {
            get;
            set;
        }
 
        // Allows the address portion of the endpoint to be overridden at runtime by the
        // workflow instance.
        [DefaultValue(null)]
        public InArgument<Uri> EndpointAddress
        {
            get;
            set;
        }
 
        // Do we need this?
        public Collection<Type> KnownTypes
        {
            get
            {
                if (this.knownTypes == null)
                {
                    this.knownTypes = new Collection<Type>();
                }
                return this.knownTypes;
            }
        }
 
        // this will be used to construct the default value for Action if the Action property is
        // not specifically set.
        // There is validation to make sure either this or Action has a value.
        [DefaultValue(null)]
        public string OperationName
        {
            get;
            set;
        }
 
        // The protection level for the message.
        // If ValueType or Value.Expression.Type is MessageContract, the MessageContract definition may have additional settings.
        // Default if this is not specified is Sign.
        [DefaultValue(null)]
        public ProtectionLevel? ProtectionLevel
        {
            get;
            set;
        }
 
        // Maybe an enum to limit possibilities
        // I am still a little fuzzy on this property.
        [DefaultValue(SerializerOption.DataContractSerializer)]
        public SerializerOption SerializerOption
        {
            get;
            set;
        }
 
        // The service contract name. This allows the same workflow instance to implement multiple
        // servce "contracts". If not specified and this is the first Receive* activity in the
        // workflow, contract inference uses this activity's Name as the service contract name.
        [DefaultValue(null)]
        [TypeConverter(typeof(ServiceXNameTypeConverter))]
        public XName ServiceContractName
        {
            get;
            set;
        }
        // The token impersonation level that is allowed for the receiver of the message.
        [DefaultValue(TokenImpersonationLevel.Identification)]
        public TokenImpersonationLevel TokenImpersonationLevel
        {
            get;
            set;
        }
 
        internal bool ChannelCacheEnabled
        {
            get
            {
                Fx.Assert(this.channelCacheEnabled.HasValue, "The value of channelCacheEnabled must be initialized!");
                return this.channelCacheEnabled.Value;
            }
        }
 
        internal bool OperationUsesMessageContract
        {
            get;
            set;
        }
 
        internal OperationDescription OperationDescription
        {
            get;
            set;
        }
 
        protected override void CacheMetadata(ActivityMetadata metadata)
        {
            if (string.IsNullOrEmpty(this.OperationName))
            {
                metadata.AddValidationError(SR.MissingOperationName(this.DisplayName));
            }
            if (this.ServiceContractName == null)
            {
                string errorOperationName = ContractValidationHelper.GetErrorMessageOperationName(this.OperationName);
                metadata.AddValidationError(SR.MissingServiceContractName(this.DisplayName, errorOperationName));
            }
 
            if (this.Endpoint == null)
            {
                if (string.IsNullOrEmpty(this.EndpointConfigurationName))
                {
                    metadata.AddValidationError(SR.EndpointNotSet(this.DisplayName, this.OperationName));
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(this.EndpointConfigurationName))
                {
                    metadata.AddValidationError(SR.EndpointIncorrectlySet(this.DisplayName, this.OperationName));
                }
                if (this.Endpoint.Binding == null)
                {
                    metadata.AddValidationError(SR.MissingBindingInEndpoint(this.Endpoint.Name, this.ServiceContractName));
                }
            }
 
            // validate Correlation Initializers
            MessagingActivityHelper.ValidateCorrelationInitializer(metadata, this.correlationInitializers, false, this.DisplayName, this.OperationName);
            
            // Add runtime arguments
            MessagingActivityHelper.AddRuntimeArgument(this.CorrelatesWith, "CorrelatesWith", Constants.CorrelationHandleType, ArgumentDirection.In, metadata);
            MessagingActivityHelper.AddRuntimeArgument(this.EndpointAddress, "EndpointAddress", Constants.UriType, ArgumentDirection.In, metadata);
 
            // Validate Content
            this.InternalContent.CacheMetadata(metadata, this, this.OperationName);
 
            if (this.correlationInitializers != null)
            {
                for (int i = 0; i < this.correlationInitializers.Count; i++)
                {
                    CorrelationInitializer initializer = this.correlationInitializers[i];
                    initializer.ArgumentName = Constants.Parameter + i;
                    RuntimeArgument initializerArgument = new RuntimeArgument(initializer.ArgumentName, Constants.CorrelationHandleType, ArgumentDirection.In);
                    metadata.Bind(initializer.CorrelationHandle, initializerArgument);
                    metadata.AddArgument(initializerArgument);
                }
            }
 
            if (!metadata.HasViolations)
            {
                if (this.InternalContent is SendMessageContent
                    && MessageBuilder.IsMessageContract(((SendMessageContent)this.InternalContent).InternalDeclaredMessageType))
                {
                    this.OperationUsesMessageContract = true;
                }
 
                this.internalSend = CreateInternalSend();
                this.InternalContent.ConfigureInternalSend(this.internalSend, out this.requestFormatter);
 
                if (this.requestFormatter != null && this.lazyFormatter != null)
                {
                    this.requestFormatter.Formatter = this.lazyFormatter;
                }
            }
            else
            {
                this.internalSend = null;
                this.requestFormatter = null;
            }
        }
 
        InternalSendMessage CreateInternalSend()
        {
            InternalSendMessage result = new InternalSendMessage
            {
                OwnerDisplayName = this.DisplayName,
                OperationName = this.OperationName,
                CorrelatesWith = new InArgument<CorrelationHandle>(new ArgumentValue<CorrelationHandle> { ArgumentName = "CorrelatesWith" }),
                Endpoint = this.Endpoint,
                EndpointConfigurationName = this.EndpointConfigurationName,
                IsOneWay = this.isOneWay,
                IsSendReply = false,
                TokenImpersonationLevel = this.TokenImpersonationLevel,
                ServiceContractName = this.ServiceContractName,
                Action = this.Action,
                Parent = this
            };
 
            if (this.correlationInitializers != null)
            {
                foreach (CorrelationInitializer correlation in this.correlationInitializers)
                {
                    result.CorrelationInitializers.Add(correlation.Clone());
                }
 
                Collection<CorrelationQuery> internalCorrelationQueryCollection = ContractInferenceHelper.CreateClientCorrelationQueries(null, this.correlationInitializers,
                        this.Action, this.ServiceContractName, this.OperationName, false);
                Fx.Assert(internalCorrelationQueryCollection.Count <= 1, "Querycollection for send cannot have more than one correlation query");
                if (internalCorrelationQueryCollection.Count == 1)
                {
                    result.CorrelationQuery = internalCorrelationQueryCollection[0];
                }
            }
 
            if (this.EndpointAddress != null)
            {
                result.EndpointAddress = new InArgument<Uri>(context => ((InArgument<Uri>)this.EndpointAddress).Get(context));
            }
 
            if (this.lazyCorrelationQueries != null)
            {
                foreach (CorrelationQuery correlationQuery in this.lazyCorrelationQueries)
                {
                    result.ReplyCorrelationQueries.Add(correlationQuery);
                }
            }
 
            return result;
        }
 
        // Acccessed by ReceiveReply.CreateBody to set OneWay to false
        internal void SetIsOneWay(bool value)
        {
            this.isOneWay = value;
            if (this.internalSend != null)
            {
                this.internalSend.IsOneWay = this.isOneWay;
            }
        }
 
        internal MessageVersion GetMessageVersion()
        {
            return this.internalSend.GetMessageVersion();
        }
 
        internal void SetFormatter(IClientMessageFormatter formatter)
        {
            if (this.requestFormatter != null)
            {
                this.requestFormatter.Formatter = formatter;
            }
            else
            {
                // we save the formatter and set the requestFormatter.Formatter later
                this.lazyFormatter = formatter;
            }
        }
 
        internal void SetReplyCorrelationQuery(CorrelationQuery replyQuery)
        {
            Fx.Assert(replyQuery != null, "replyQuery cannot be null!");
 
            if (this.internalSend != null && !this.internalSend.ReplyCorrelationQueries.Contains(replyQuery))
            {
                this.internalSend.ReplyCorrelationQueries.Add(replyQuery);
            }
            else
            {
                // we save the CorrelationQuery and add it to InternalSendMessage later
                if (this.lazyCorrelationQueries == null)
                {
                    this.lazyCorrelationQueries = new List<CorrelationQuery>();
                }
                this.lazyCorrelationQueries.Add(replyQuery);
            }
        }
 
        internal void InitializeChannelCacheEnabledSetting(ActivityContext context)
        {
            if (!this.channelCacheEnabled.HasValue)
            {
                SendMessageChannelCache channelCacheExtension = context.GetExtension<SendMessageChannelCache>();
                Fx.Assert(channelCacheExtension != null, "channelCacheExtension must exist!");
 
                InitializeChannelCacheEnabledSetting(channelCacheExtension);
            }
        }
 
        internal void InitializeChannelCacheEnabledSetting(SendMessageChannelCache channelCacheExtension)
        {
            Fx.Assert(channelCacheExtension != null, "channelCacheExtension cannot be null!");
 
            ChannelCacheSettings factorySettings = channelCacheExtension.FactorySettings;
            Fx.Assert(factorySettings != null, "FactorySettings cannot be null!");
 
            bool enabled;
 
            if (factorySettings.IdleTimeout == TimeSpan.Zero || factorySettings.LeaseTimeout == TimeSpan.Zero || factorySettings.MaxItemsInCache == 0)
            {
                enabled = false;
            }
            else
            {
                enabled = true;
            }
 
            if (!this.channelCacheEnabled.HasValue)
            {
                this.channelCacheEnabled = enabled;
            }
            else
            {
                Fx.Assert(this.channelCacheEnabled.Value == enabled, "Once ChannelCacheEnabled is set, it cannot be changed!");
            }
        }
    }
}