//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Activities { using System.Activities; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net.Security; using System.Runtime; using System.ServiceModel.Activities.Description; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.Xml; using System.Xml.Linq; static class ContractInferenceHelper { static DataContractFormatAttribute dataContractFormatAttribute; static XmlSerializerFormatAttribute xmlSerializerFormatAttribute; static Type exceptionType; static Type faultExceptionType; public static DataContractFormatAttribute DataContractFormatAttribute { get { if (dataContractFormatAttribute == null) { dataContractFormatAttribute = new DataContractFormatAttribute(); } return dataContractFormatAttribute; } } public static XmlSerializerFormatAttribute XmlSerializerFormatAttribute { get { if (xmlSerializerFormatAttribute == null) { xmlSerializerFormatAttribute = new XmlSerializerFormatAttribute { SupportFaults = true }; } return xmlSerializerFormatAttribute; } } public static Type ExceptionType { get { if (exceptionType == null) { exceptionType = typeof(Exception); } return exceptionType; } } public static Type FaultExceptionType { get { if (faultExceptionType == null) { faultExceptionType = typeof(FaultException<>); } return faultExceptionType; } } public static void ProvideDefaultNamespace(ref XName serviceContractName) { Fx.Assert(serviceContractName != null, "Argument cannot be null!"); if (string.IsNullOrEmpty(serviceContractName.NamespaceName)) { // If no namespace is given by the user, we provide default namespace. This is consistent with WCF. serviceContractName = XName.Get(serviceContractName.LocalName, NamingHelper.DefaultNamespace); } } public static ContractDescription CreateContractFromOperation(XName serviceContractName, OperationDescription operation) { Fx.Assert(serviceContractName != null, "serviceContractName cannot be null"); ProvideDefaultNamespace(ref serviceContractName); ContractDescription contract = new ContractDescription(serviceContractName.LocalName, serviceContractName.NamespaceName) { // For inferred client side contracts, we do not set ContractType ConfigurationName = serviceContractName.LocalName, SessionMode = SessionMode.Allowed }; contract.Operations.Add(operation); return contract; } public static ContractDescription CreateOutputChannelContractDescription(XName serviceContractName, ProtectionLevel? protectionLevel) { Fx.Assert(serviceContractName != null, "cannot be null"); Type channelType = typeof(IOutputChannel); ProvideDefaultNamespace(ref serviceContractName); ContractDescription contract = new ContractDescription(serviceContractName.LocalName, serviceContractName.NamespaceName) { ContractType = channelType, ConfigurationName = serviceContractName.LocalName, SessionMode = SessionMode.Allowed }; OperationDescription operation = new OperationDescription("Send", contract); MessageDescription message = new MessageDescription(MessageHeaders.WildcardAction, MessageDirection.Input); operation.Messages.Add(message); if (protectionLevel.HasValue) { operation.ProtectionLevel = protectionLevel.Value; } contract.Operations.Add(operation); return contract; } public static ContractDescription CreateRequestChannelContractDescription(XName serviceContractName, ProtectionLevel? protectionLevel) { Fx.Assert(serviceContractName != null, "cannot be null"); Type channelType = typeof(IRequestChannel); ProvideDefaultNamespace(ref serviceContractName); ContractDescription contract = new ContractDescription(serviceContractName.LocalName, serviceContractName.NamespaceName) { ContractType = channelType, ConfigurationName = serviceContractName.LocalName, SessionMode = SessionMode.Allowed }; OperationDescription operation = new OperationDescription("Request", contract); MessageDescription request = new MessageDescription(MessageHeaders.WildcardAction, MessageDirection.Input); MessageDescription reply = new MessageDescription(MessageHeaders.WildcardAction, MessageDirection.Output); operation.Messages.Add(request); operation.Messages.Add(reply); if (protectionLevel.HasValue) { operation.ProtectionLevel = protectionLevel.Value; } contract.Operations.Add(operation); return contract; } public static void EnsureTransactionFlowOnContract( ref ServiceEndpoint serviceEndpoint, XName serviceContractName, string operationName, string action, ProtectionLevel? protectionLevel) { Fx.Assert(serviceEndpoint != null, "ServiceEndpoint cannot be null!"); // Client side fully inferred contract always has null ContractType if (serviceEndpoint.Contract.ContractType == null) { // If we are using the real contract, we only need to add TrancactionFlowAttribute to the operation Fx.Assert(serviceEndpoint.Contract.Operations.Count == 1, "Client side contract should have exactly one operation!"); serviceEndpoint.Contract.Operations[0].Behaviors.Add(new TransactionFlowAttribute(TransactionFlowOption.Allowed)); } else { // Replace the original fake contract with a fake contract tailored for transaction ContractDescription contract = null; OperationDescription operation = null; MessageDescription request = null; MessageDescription reply = null; Type channelType = typeof(IRequestChannel); // We need to create a contract description with the real service contract name // and operation name and actions and with the TransactionFlow operation behavior // because the TransactionChannelFactory has a dictionary of "Directional Action" to // transaction flow value that it uses to decide whether or not to include the // transaction header in the message. Fx.Assert(serviceContractName != null, "Argument serviceContractName cannot be null!"); Fx.Assert(operationName != null, "Argument operationName cannot be null!"); ProvideDefaultNamespace(ref serviceContractName); contract = new ContractDescription(serviceContractName.LocalName, serviceContractName.NamespaceName) { ContractType = channelType, SessionMode = SessionMode.Allowed }; operation = new OperationDescription(operationName, contract); operation.Behaviors.Add(new TransactionFlowAttribute(TransactionFlowOption.Allowed)); string requestAction = null; string replyAction = null; if (String.IsNullOrEmpty(action)) { // Construct the action. requestAction = NamingHelper.GetMessageAction(operation, false); replyAction = NamingHelper.GetMessageAction(operation, true); } else { requestAction = action; replyAction = action + TypeLoader.ResponseSuffix; } request = new MessageDescription(requestAction, MessageDirection.Input); reply = new MessageDescription(replyAction, MessageDirection.Output); operation.Messages.Add(request); operation.Messages.Add(reply); if (protectionLevel.HasValue) { operation.ProtectionLevel = protectionLevel.Value; } contract.Operations.Add(operation); // We need to replace the ServiceEndpoint because ServiceEndpoint.Contract does not have a public setter Uri listenUri = serviceEndpoint.ListenUri; serviceEndpoint = new ServiceEndpoint(contract) { Binding = serviceEndpoint.Binding, Address = serviceEndpoint.Address, Name = serviceEndpoint.Name, }; if (listenUri != null) { serviceEndpoint.ListenUri = listenUri; } } } public static OperationDescription CreateOneWayOperationDescription(Send send) { Fx.Assert(send != null, "Argument cannot be null!"); return CreateOperationDescriptionCore(send, null); } public static OperationDescription CreateTwoWayOperationDescription(Send send, ReceiveReply receiveReply) { Fx.Assert(send != null && receiveReply != null, "Arguments cannot be null!"); return CreateOperationDescriptionCore(send, receiveReply); } static OperationDescription CreateOperationDescriptionCore(Send send, ReceiveReply receiveReply) { XName contractXName = send.ServiceContractName; ProvideDefaultNamespace(ref contractXName); // Infer Name, Namespace, ConfigurationName ContractDescription contract = new ContractDescription(contractXName.LocalName, contractXName.NamespaceName); contract.ConfigurationName = send.EndpointConfigurationName; OperationDescription operation = new OperationDescription(NamingHelper.XmlName(send.OperationName), contract); if (send.ProtectionLevel.HasValue) { operation.ProtectionLevel = send.ProtectionLevel.Value; } AddKnownTypesToOperation(operation, send.KnownTypes); // Infer In-Message send.InternalContent.InferMessageDescription(operation, send, MessageDirection.Input); // Infer Out-Message if (receiveReply != null) { receiveReply.InternalContent.InferMessageDescription(operation, receiveReply, MessageDirection.Output); } PostProcessOperation(operation); AddSerializerProvider(operation, send.SerializerOption); contract.Operations.Add(operation); return operation; } // Create server side OperationDescription. // Note this method assumes that CacheMetadata has been called on the Receive activity (as part of // the activity tree walk that is done in WorkflowService.GetContractDescriptions) because it relies on // InternalReceiveMessage property of the Receive actitivy to be non-null. public static OperationDescription CreateOperationDescription(Receive receive, ContractDescription contract) { Fx.Assert(receive.InternalReceive != null, "This method can only be called if CacheMetadata has been called on the receive activity"); OperationDescription operation = new OperationDescription(NamingHelper.XmlName(receive.OperationName), contract); if (receive.ProtectionLevel.HasValue) { operation.ProtectionLevel = receive.ProtectionLevel.Value; } // Infer In-Message receive.InternalContent.InferMessageDescription(operation, receive, MessageDirection.Input); // Infer Out-Message if (receive.HasReply) { // At this point, we already know all the following SendReplies are equivalent SendReply sendReply = receive.FollowingReplies[0]; sendReply.InternalContent.InferMessageDescription(operation, sendReply, MessageDirection.Output); } else if (receive.HasFault) { // We infer Receive-SendFault pair as a two-way operation with void return value CheckForDisposableParameters(operation, Constants.EmptyTypeArray); AddOutputMessage(operation, null, Constants.EmptyStringArray, Constants.EmptyTypeArray); } PostProcessOperation(operation); // Behaviors AddSerializerProvider(operation, receive.SerializerOption); AddWorkflowOperationBehaviors(operation, receive.InternalReceive.OperationBookmarkName, receive.CanCreateInstance); if (receive.InternalReceive.AdditionalData.IsInsideTransactedReceiveScope) { operation.IsInsideTransactedReceiveScope = true; EnableTransactionBehavior(operation); if (receive.InternalReceive.AdditionalData.IsFirstReceiveOfTransactedReceiveScopeTree) { operation.IsFirstReceiveOfTransactedReceiveScopeTree = true; } } return operation; } public static void AddInputMessage(OperationDescription operation, string overridingAction, Type type, SerializerOption serializerOption) { Fx.Assert(operation.Messages.Count == 0, "Operation already has input message"); bool isResponse = false; MessageDescription message = MessageBuilder.CreateMessageDescription( operation, isResponse, MessageDirection.Input, overridingAction, type, serializerOption); operation.Messages.Add(message); } public static void AddInputMessage(OperationDescription operation, string overridingAction, string[] argumentNames, Type[] argumentTypes) { Fx.Assert(operation.Messages.Count == 0, "Operation already has input message"); bool isResponse = false; MessageDescription message = MessageBuilder.CreateMessageDescription( operation, isResponse, MessageDirection.Input, overridingAction, argumentNames, argumentTypes); operation.Messages.Add(message); } public static void AddOutputMessage(OperationDescription operation, string overridingAction, Type type, SerializerOption serializerOption) { Fx.Assert(operation.Messages.Count > 0, "Operation does not have input message"); Fx.Assert(operation.Messages.Count < 2, "Operation already has output message"); bool isResponse = true; MessageDescription message = MessageBuilder.CreateMessageDescription( operation, isResponse, MessageDirection.Output, overridingAction, type, serializerOption); operation.Messages.Add(message); } public static void AddOutputMessage(OperationDescription operation, string overridingAction, string[] argumentNames, Type[] argumentTypes) { Fx.Assert(operation.Messages.Count > 0, "Operation does not have input message"); Fx.Assert(operation.Messages.Count < 2, "Operation already has output message"); bool isResponse = true; MessageDescription message = MessageBuilder.CreateMessageDescription( operation, isResponse, MessageDirection.Output, overridingAction, argumentNames, argumentTypes); operation.Messages.Add(message); } static void AddKnownTypesToOperation(OperationDescription operation, Collection knownTypes) { if (knownTypes != null) { foreach (Type knownType in knownTypes) { operation.KnownTypes.Add(knownType); } } } public static void CheckForDisposableParameters(OperationDescription operation, Type type) { if (type == null) { operation.HasNoDisposableParameters = true; } else { operation.HasNoDisposableParameters = !ServiceReflector.IsParameterDisposable(type); } } public static void CheckForDisposableParameters(OperationDescription operation, Type[] types) { Fx.Assert(types != null, "Argument cannot be null!"); operation.HasNoDisposableParameters = true; foreach (Type type in types) { if (ServiceReflector.IsParameterDisposable(type)) { operation.HasNoDisposableParameters = false; break; } } } static void EnableTransactionBehavior(OperationDescription operationDescription) { Fx.Assert(operationDescription != null, "OperationDescription is null"); OperationBehaviorAttribute attribute = operationDescription.Behaviors.Find(); if (attribute != null) { attribute.TransactionScopeRequired = true; attribute.TransactionAutoComplete = false; } else { OperationBehaviorAttribute attr = new OperationBehaviorAttribute { TransactionAutoComplete = false, TransactionScopeRequired = true }; operationDescription.Behaviors.Add(attr); } TransactionFlowAttribute transactionFlowAttribute = operationDescription.Behaviors.Find(); if (transactionFlowAttribute != null) { if (transactionFlowAttribute.Transactions != TransactionFlowOption.Allowed) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ContractInferenceValidationForTransactionFlowBehavior)); } } else { if (!operationDescription.IsOneWay) { operationDescription.Behaviors.Add(new TransactionFlowAttribute(TransactionFlowOption.Allowed)); } } } static void PostProcessOperation(OperationDescription operation) { MessageBuilder.ClearWrapperNames(operation); } static void AddSerializerProvider(OperationDescription operation, SerializerOption serializerOption) { switch (serializerOption) { case SerializerOption.DataContractSerializer: AddDataContractSerializerFormat(operation); break; case SerializerOption.XmlSerializer: AddXmlSerializerFormat(operation); break; } } static void AddDataContractSerializerFormat(OperationDescription operation) { if (operation.Behaviors.Find() != null) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.OperationHasSerializerBehavior( operation.Name, operation.DeclaringContract.Name, typeof(DataContractSerializerOperationBehavior)))); } operation.Behaviors.Add(new DataContractSerializerOperationBehavior(operation, DataContractFormatAttribute)); if (!operation.Behaviors.Contains(typeof(DataContractSerializerOperationGenerator))) { operation.Behaviors.Add(new DataContractSerializerOperationGenerator()); } } static void AddXmlSerializerFormat(OperationDescription operation) { if (operation.Behaviors.Find() != null) { throw FxTrace.Exception.AsError(new InvalidOperationException( SR.OperationHasSerializerBehavior(operation.Name, operation.DeclaringContract.Name, typeof(XmlSerializerOperationBehavior)))); } operation.Behaviors.Add(new XmlSerializerOperationBehavior(operation, XmlSerializerFormatAttribute)); if (!operation.Behaviors.Contains(typeof(XmlSerializerOperationGenerator))) { operation.Behaviors.Add(new XmlSerializerOperationGenerator(new XmlSerializerImportOptions())); } } static void AddWorkflowOperationBehaviors(OperationDescription operation, string bookmarkName, bool canCreateInstance) { KeyedByTypeCollection behaviors = operation.Behaviors; WorkflowOperationBehavior workflowOperationBehavior = behaviors.Find(); if (workflowOperationBehavior == null) { behaviors.Add(new WorkflowOperationBehavior(new Bookmark(bookmarkName), canCreateInstance)); } else { workflowOperationBehavior.CanCreateInstance = workflowOperationBehavior.CanCreateInstance || canCreateInstance; } } public static void CorrectOutMessageForOperation(Receive receive, OperationDescription operation) { // Remove the original outMessage Fx.Assert(operation.Messages.Count == 2, "OperationDescription must be two-way for CorrectOutMessageForOperation to be invoked!"); operation.Messages.RemoveAt(1); SendReply sendReply = receive.FollowingReplies[0]; sendReply.InternalContent.InferMessageDescription(operation, sendReply, MessageDirection.Output); ContractInferenceHelper.PostProcessOperation(operation); } public static void UpdateIsOneWayFlag(Receive receive, OperationDescription operation) { // Set InternalReceiveMessage.IsOneWay to false for two-way operations if (!operation.IsOneWay) { receive.SetIsOneWay(false); } } public static void AddFaultDescription(Receive activity, OperationDescription operation) { if (activity.HasFault) { foreach (SendReply sendFault in activity.FollowingFaults) { string action = null; Type type = null; action = sendFault.Action; SendMessageContent sendReply = sendFault.InternalContent as SendMessageContent; if (sendReply != null) { type = sendReply.InternalDeclaredMessageType; } else { SendParametersContent sendReplyParameters = sendFault.InternalContent as SendParametersContent; if (sendReplyParameters != null) { type = sendReplyParameters.ArgumentTypes[0]; // Exception should be the only parameter in SendFault } } Fx.Assert(type != null, "Exception type cannot be null!"); if (type.IsGenericType && type.GetGenericTypeDefinition() == FaultExceptionType) { Type faultType = type.GetGenericArguments()[0]; bool exists = false; // We expect the number of fault types to be small, so we use iterative comparison foreach (FaultDescription faultDescription in operation.Faults) { if (faultDescription.DetailType == faultType) { if (faultDescription.Action != action) { throw FxTrace.Exception.AsError(new ValidationException(SR.SendRepliesHaveSameFaultTypeDifferentAction)); } else { exists = true; break; } } } if (!exists) { FaultDescription faultDescription = MessageBuilder.CreateFaultDescription(operation, faultType, action); operation.Faults.Add(faultDescription); } } } } } public static void AddKnownTypesToOperation(Receive receive, OperationDescription operation) { Collection knownTypes = receive.InternalKnownTypes; if (knownTypes != null) { foreach (Type knownType in knownTypes) { // We expect the number of known types to be small, so we use iterative comparison if (!operation.KnownTypes.Contains(knownType)) { operation.KnownTypes.Add(knownType); } } } } public static void AddReceiveToFormatterBehavior(Receive receive, OperationDescription operation) { Fx.Assert(receive != null && operation != null, "Argument cannot be null!"); KeyedByTypeCollection behaviors = operation.Behaviors; WorkflowFormatterBehavior formatterBehavior = behaviors.Find(); if (formatterBehavior == null) { formatterBehavior = new WorkflowFormatterBehavior(); behaviors.Add(formatterBehavior); } formatterBehavior.Receives.Add(receive); } public static void RemoveReceiveFromFormatterBehavior(Receive receive, OperationDescription operation) { Fx.Assert(receive != null && operation != null, "Arguments cannot be null!"); KeyedByTypeCollection behaviors = operation.Behaviors; WorkflowFormatterBehavior formatterBehavior = behaviors.Find(); if (formatterBehavior != null) { formatterBehavior.Receives.Remove(receive); } } public static CorrelationQuery CreateServerCorrelationQuery(MessageQuerySet select, Collection correlationInitializers, OperationDescription operation, bool isResponse) { Fx.Assert(operation != null, "Argument cannot be null!"); CorrelationQuery correlationQuery = CreateCorrelationQueryCore(select, correlationInitializers); if (correlationQuery != null) { string action = !isResponse ? operation.Messages[0].Action : operation.Messages[1].Action; correlationQuery.Where = new CorrelationActionMessageFilter { Action = action }; } return correlationQuery; } // this method generates the correlationQuery for client side send and receiveReply public static Collection CreateClientCorrelationQueries(MessageQuerySet select, Collection correlationInitializers, string overridingAction, XName serviceContractName, string operationName, bool isResponse) { Fx.Assert(serviceContractName != null && operationName != null, "Argument cannot be null!"); Collection queryCollection = new Collection(); CorrelationQuery correlationQuery = CreateCorrelationQueryCore(select, correlationInitializers); if (correlationQuery != null) { if (overridingAction != null) { correlationQuery.Where = new CorrelationActionMessageFilter { Action = overridingAction }; } else { ProvideDefaultNamespace(ref serviceContractName); string defaultAction = NamingHelper.GetMessageAction(new XmlQualifiedName(serviceContractName.LocalName, serviceContractName.NamespaceName), operationName, null, isResponse); correlationQuery.Where = new CorrelationActionMessageFilter { Action = defaultAction }; } queryCollection.Add(correlationQuery); if (isResponse) { // we need an additional query with empty action to support soap1.1 reply cases CorrelationQuery noActionQuery = correlationQuery.Clone(); noActionQuery.Where = new CorrelationActionMessageFilter { Action = String.Empty }; queryCollection.Add(noActionQuery); } } return queryCollection; } static CorrelationQuery CreateCorrelationQueryCore(MessageQuerySet select, Collection correlationInitializers) { CorrelationQuery correlationQuery = null; if (select != null) { Fx.Assert(select.Count != 0, "Empty MessageQuerySet is not allowed!"); correlationQuery = new CorrelationQuery { Select = select }; } if (correlationInitializers != null && correlationInitializers.Count > 0) { foreach (CorrelationInitializer correlation in correlationInitializers) { QueryCorrelationInitializer queryCorrelation = correlation as QueryCorrelationInitializer; if (queryCorrelation != null) { Fx.Assert(queryCorrelation.MessageQuerySet.Count != 0, "Empty MessageQuerySet is not allowed!"); correlationQuery = correlationQuery ?? new CorrelationQuery(); correlationQuery.SelectAdditional.Add(queryCorrelation.MessageQuerySet); } } } return correlationQuery; } } }