//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.ServiceModel.Activities { using System.Activities; using System.Activities.Debugger; using System.Activities.DynamicUpdate; using System.Activities.XamlIntegration; using System.Activities.Statements; using System.Activities.Validation; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Reflection; using System.Runtime; using System.Runtime.Collections; using System.ServiceModel.Activities.Description; using System.ServiceModel.Description; using System.ServiceModel.XamlIntegration; using System.Text; using System.Windows.Markup; using System.Xaml; using System.Xml; using System.Xml.Linq; [ContentProperty("Body")] public class WorkflowService : IDebuggableWorkflowTree { Collection endpoints; Collection implementedContracts; NullableKeyDictionary updateMaps; IDictionary cachedInferredContracts; IDictionary> correlationQueryByContract; IDictionary keyedByNameOperationInfo; IList knownServiceActivities; HashSet receiveAndReplyPairs; ServiceDescription serviceDescription; XName inferedServiceName; public WorkflowService() { } [DefaultValue(null)] public Activity Body { get; set; } [Fx.Tag.KnownXamlExternal] [DefaultValue(null)] [TypeConverter(typeof(ServiceXNameTypeConverter))] public XName Name { get; set; } [DefaultValue(null)] public string ConfigurationName { get; set; } [DefaultValue(false)] public bool AllowBufferedReceive { get; set; } public Collection Endpoints { get { if (this.endpoints == null) { this.endpoints = new Collection(); } return this.endpoints; } } [Fx.Tag.KnownXamlExternal] [DefaultValue(null)] public WorkflowIdentity DefinitionIdentity { get; set; } public Collection ImplementedContracts { get { if (this.implementedContracts == null) { this.implementedContracts = new Collection(); } return this.implementedContracts; } } public IDictionary UpdateMaps { get { if (this.updateMaps == null) { this.updateMaps = new NullableKeyDictionary(); } return this.updateMaps; } } internal bool HasImplementedContracts { get { return this.implementedContracts != null && this.implementedContracts.Count > 0; } } [DefaultValue(null)] internal Dictionary OperationProperties { get; set; } IDictionary OperationsInfo { get { if (this.keyedByNameOperationInfo == null) { GetContractDescriptions(); } return this.keyedByNameOperationInfo; } } internal XName InternalName { get { if (this.Name != null) { return this.Name; } else { if (this.inferedServiceName == null) { Fx.Assert(this.Body != null, "Body cannot be null!"); if (this.Body.DisplayName.Length == 0) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.MissingDisplayNameInRootActivity)); } this.inferedServiceName = XName.Get(XmlConvert.EncodeLocalName(this.Body.DisplayName)); } return this.inferedServiceName; } } } internal IDictionary> CorrelationQueries { get { Fx.Assert(this.cachedInferredContracts != null, "Must infer contract first!"); return this.correlationQueryByContract; } } public Activity GetWorkflowRoot() { return this.Body; } internal ServiceDescription GetEmptyServiceDescription() { if (this.serviceDescription == null) { WalkActivityTree(); ServiceDescription result = new ServiceDescription { Name = this.InternalName.LocalName, Namespace = string.IsNullOrEmpty(this.InternalName.NamespaceName) ? NamingHelper.DefaultNamespace : this.InternalName.NamespaceName, ConfigurationName = this.ConfigurationName ?? this.InternalName.LocalName }; this.serviceDescription = result; } return this.serviceDescription; } static void AddAdditionalConstraint(ValidationSettings workflowServiceSettings, Type constraintType, Constraint constraint) { IList constraintList; if (workflowServiceSettings.AdditionalConstraints.TryGetValue(constraintType, out constraintList)) { constraintList.Add(constraint); } else { constraintList = new List(1) { constraint, }; workflowServiceSettings.AdditionalConstraints.Add(constraintType, constraintList); } } public ValidationResults Validate(ValidationSettings settings) { Collection errors = new Collection(); ValidationSettings workflowServiceSettings = this.CopyValidationSettions(settings); if (this.HasImplementedContracts) { this.OperationProperties = CreateOperationProperties(errors); // Add additional constraints AddAdditionalConstraint(workflowServiceSettings, typeof(Receive), GetContractFirstValidationReceiveConstraints()); AddAdditionalConstraint(workflowServiceSettings, typeof(SendReply), GetContractFirstValidationSendReplyConstraints()); } ValidationResults results = null; if (this.Body != null) { results = ActivityValidationServices.Validate(this.Body, workflowServiceSettings); if (!this.HasImplementedContracts) { return results; } else { // If the user specified implemented contract, then we need to add the errors into the error collection foreach (ValidationError validationError in results.Errors) { errors.Add(validationError); } foreach (ValidationError validationWarning in results.Warnings) { errors.Add(validationWarning); } } } if (this.HasImplementedContracts) { this.AfterValidation(errors); } return new ValidationResults(errors); } bool IsContractValid(ContractDescription contractDescription, Collection validationError) { bool isValid = true; if (contractDescription.IsDuplex()) { validationError.Add(new ValidationError(SR.DuplexContractsNotSupported)); isValid = false; } return isValid; } ValidationSettings CopyValidationSettions(ValidationSettings source) { if ( source == null ) { return new ValidationSettings(); } ValidationSettings clonedSettings = new ValidationSettings { OnlyUseAdditionalConstraints = source.OnlyUseAdditionalConstraints, SingleLevel = source.SingleLevel, SkipValidatingRootConfiguration = source.SkipValidatingRootConfiguration, PrepareForRuntime = source.PrepareForRuntime, Environment = source.Environment, // Retain the same cancellation token. Otherwise we can't cancel the validation of WorkflowService objects // which can make the designer unreponsive if the validation takes a long time. CancellationToken = source.CancellationToken }; foreach (KeyValuePair> constrants in source.AdditionalConstraints) { if (constrants.Key != null && constrants.Value != null) { clonedSettings.AdditionalConstraints.Add(constrants.Key, new List(constrants.Value)); } } return clonedSettings; } void AfterValidation(Collection errors) { if (this.HasImplementedContracts) { Dictionary operationProperties = this.OperationProperties; if (operationProperties != null) { foreach (OperationProperty property in operationProperties.Values) { Fx.Assert(property.Operation != null, "OperationProperty.Operation should not be null!"); if (property.ImplementingReceives.Count < 1) { errors.Add(new ValidationError(SR.OperationIsNotImplemented(property.Operation.Name, property.Operation.DeclaringContract.Name), true)); } else if (!property.Operation.IsOneWay) { foreach (Receive recv in property.ImplementingReceives) { if (!property.ImplementingSendRepliesRequests.Contains(recv)) { // passing the receive activity without a matching SendReply as the SourceDetail errors.Add(new ValidationError(SR.TwoWayIsImplementedAsOneWay(property.Operation.Name, property.Operation.DeclaringContract.Name), true, string.Empty, recv)); } } } } } } } Dictionary CreateOperationProperties(Collection validationErrors) { Dictionary operationProperties = null; if (this.HasImplementedContracts) { operationProperties = new Dictionary(); foreach (Type contractType in this.ImplementedContracts) { ContractDescription contract = null; try { contract = ContractDescription.GetContract(contractType); if (contract != null) { if (this.IsContractValid(contract, validationErrors)) { foreach (OperationDescription operation in contract.Operations) { OperationIdentifier id = new OperationIdentifier(operation.DeclaringContract.Name, operation.DeclaringContract.Namespace, operation.Name); if (operationProperties.ContainsKey(id)) { validationErrors.Add(new ValidationError(SR.DuplicatedContract(operation.DeclaringContract.Name, operation.Name), true)); } else { operationProperties.Add(id, new OperationProperty(operation)); } } } } } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } validationErrors.Add(new ValidationError(exception.Message)); } } } return operationProperties; } public virtual IDictionary GetContractDescriptions() { if (this.cachedInferredContracts == null) { WalkActivityTree(); Fx.Assert(this.knownServiceActivities != null && this.receiveAndReplyPairs != null, "Failed to walk the activity tree!"); this.correlationQueryByContract = new Dictionary>(); // Contract inference IDictionary inferredContracts = new Dictionary(); this.keyedByNameOperationInfo = new Dictionary(); foreach (Receive receive in this.knownServiceActivities) { XName contractXName = FixServiceContractName(receive.ServiceContractName); ContractAndOperationNameTuple tuple = new ContractAndOperationNameTuple(contractXName, receive.OperationName); OperationInfo operationInfo; if (this.keyedByNameOperationInfo.TryGetValue(tuple, out operationInfo)) { // All Receives with same ServiceContractName and OperationName need to be validated ContractValidationHelper.ValidateReceiveWithReceive(receive, operationInfo.Receive); } else { // Note that activities in keyedByNameOperationInfo are keyed by // ServiceContractName and OperationName tuple. So we won't run into the case where // two opertions have the same OperationName. ContractDescription contract; if (!inferredContracts.TryGetValue(contractXName, out contract)) { // Infer Name, Namespace contract = new ContractDescription(contractXName.LocalName, contractXName.NamespaceName); // We use ServiceContractName.LocalName to bind contract with config contract.ConfigurationName = contractXName.LocalName; // We do NOT infer ContractDescription.ProtectionLevel inferredContracts.Add(contractXName, contract); } OperationDescription operation = ContractInferenceHelper.CreateOperationDescription(receive, contract); contract.Operations.Add(operation); operationInfo = new OperationInfo(receive, operation); this.keyedByNameOperationInfo.Add(tuple, operationInfo); } CorrectOutMessageForOperationWithFault(receive, operationInfo); ContractInferenceHelper.UpdateIsOneWayFlag(receive, operationInfo.OperationDescription); // FaultTypes and KnownTypes need to be collected from all Receive activities ContractInferenceHelper.AddFaultDescription(receive, operationInfo.OperationDescription); ContractInferenceHelper.AddKnownTypesToOperation(receive, operationInfo.OperationDescription); // WorkflowFormatterBehavior should have reference to all the Receive activities ContractInferenceHelper.AddReceiveToFormatterBehavior(receive, operationInfo.OperationDescription); Collection correlationQueries = null; // Collect CorrelationQuery from Receive if (receive.HasCorrelatesOn || receive.HasCorrelationInitializers) { MessageQuerySet select = receive.HasCorrelatesOn ? receive.CorrelatesOn : null; CorrelationQuery correlationQuery = ContractInferenceHelper.CreateServerCorrelationQuery(select, receive.CorrelationInitializers, operationInfo.OperationDescription, false); CollectCorrelationQuery(ref correlationQueries, contractXName, correlationQuery); } // Find all known Receive-Reply pair in the activity tree. Remove them from this.receiveAndReplyPairs // Also collect CorrelationQuery from following replies if (receive.HasReply) { foreach (SendReply reply in receive.FollowingReplies) { ReceiveAndReplyTuple pair = new ReceiveAndReplyTuple(receive, reply); this.receiveAndReplyPairs.Remove(pair); CollectCorrelationQueryFromReply(ref correlationQueries, contractXName, reply, operationInfo.OperationDescription); reply.SetContractName(contractXName); } } if (receive.HasFault) { foreach (SendReply fault in receive.FollowingFaults) { ReceiveAndReplyTuple pair = new ReceiveAndReplyTuple(receive, fault); this.receiveAndReplyPairs.Remove(pair); CollectCorrelationQueryFromReply(ref correlationQueries, contractXName, fault, operationInfo.OperationDescription); } } // Have to do this here otherwise message/fault formatters // non-WorkflowServiceHost case won't be set. Cannot do this // during CacheMetadata time because activity order in which // CacheMetadata calls are made doesn't yield the correct result. // Not possible to do it at runtime either because the ToReply and // ToRequest activities that use the formatters do not have access // to related OperationDescription. Note: non-WorkflowServiceHosts will // need to call GetContractDescriptions() to get these default formatters // wired up. receive.SetDefaultFormatters(operationInfo.OperationDescription); } // Check for Receive referenced by SendReply but no longer in the activity tree if (this.receiveAndReplyPairs.Count != 0) { throw FxTrace.Exception.AsError(new ValidationException(SR.DanglingReceive)); } // Print out tracing information if (TD.InferredContractDescriptionIsEnabled()) { foreach (ContractDescription contract in inferredContracts.Values) { TD.InferredContractDescription(contract.Name, contract.Namespace); if (TD.InferredOperationDescriptionIsEnabled()) { foreach (OperationDescription operation in contract.Operations) { TD.InferredOperationDescription(operation.Name, contract.Name, operation.IsOneWay.ToString()); } } } } this.cachedInferredContracts = inferredContracts; } return this.cachedInferredContracts; } internal void ValidateForVersioning(WorkflowService baseWorkflowService) { if (this.knownServiceActivities == null) { WalkActivityTree(); } foreach (Receive receive in this.knownServiceActivities) { XName contractXName = FixServiceContractName(receive.ServiceContractName); ContractAndOperationNameTuple tuple = new ContractAndOperationNameTuple(contractXName, receive.OperationName); OperationInfo operationInfo; if (baseWorkflowService.OperationsInfo.TryGetValue(tuple, out operationInfo)) { // All Receives with same ServiceContractName and OperationName need to be validated ContractValidationHelper.ValidateReceiveWithReceive(receive, operationInfo.Receive); ContractInferenceHelper.AddReceiveToFormatterBehavior(receive, operationInfo.OperationDescription); ContractInferenceHelper.UpdateIsOneWayFlag(receive, operationInfo.OperationDescription); } else { throw FxTrace.Exception.AsError(new ValidationException(SR.OperationNotFound(contractXName, receive.OperationName))); } } } internal void DetachFromVersioning(WorkflowService baseWorkflowService) { if (this.knownServiceActivities == null) { return; } foreach (Receive receive in this.knownServiceActivities) { XName contractXName = FixServiceContractName(receive.ServiceContractName); ContractAndOperationNameTuple tuple = new ContractAndOperationNameTuple(contractXName, receive.OperationName); OperationInfo operationInfo; if (baseWorkflowService.OperationsInfo.TryGetValue(tuple, out operationInfo)) { ContractInferenceHelper.RemoveReceiveFromFormatterBehavior(receive, operationInfo.OperationDescription); } } } void WalkActivityTree() { if (this.knownServiceActivities != null) { // We return if we have already walked the activity tree return; } if (this.Body == null) { throw FxTrace.Exception.AsError(new ValidationException(SR.MissingBodyInWorkflowService)); } // Validate the activity tree ValidationResults validationResults = null; StringBuilder exceptionMessage = new StringBuilder(); bool doesErrorExist = false; try { if (this.HasImplementedContracts) { validationResults = this.Validate(new ValidationSettings() { PrepareForRuntime = true, }); } else { WorkflowInspectionServices.CacheMetadata(this.Body); } } catch (InvalidWorkflowException e) { doesErrorExist = true; exceptionMessage.AppendLine(e.Message); } if (validationResults != null) { if (validationResults.Errors != null && validationResults.Errors.Count > 0) { doesErrorExist = true; foreach (ValidationError error in validationResults.Errors) { exceptionMessage.AppendLine(error.Message); } } } if (doesErrorExist) { throw FxTrace.Exception.AsError(new InvalidWorkflowException(exceptionMessage.ToString())); } this.knownServiceActivities = new List(); this.receiveAndReplyPairs = new HashSet(); // Now let us walk the tree here Queue activities = new Queue(); // The root activity is never "in" a TransactedReceiveScope activities.Enqueue(new QueueItem(this.Body, null, null)); while (activities.Count > 0) { QueueItem queueItem = activities.Dequeue(); Fx.Assert(queueItem != null, "Queue item cannot be null"); Activity activity = queueItem.Activity; Fx.Assert(queueItem.Activity != null, "Queue item's Activity cannot be null"); Activity parentReceiveScope = queueItem.ParentReceiveScope; Activity rootTransactedReceiveScope = queueItem.RootTransactedReceiveScope; if (activity is Receive) // First, let's see if this is a Receive activity { Receive receive = (Receive)activity; if (rootTransactedReceiveScope != null) { receive.InternalReceive.AdditionalData.IsInsideTransactedReceiveScope = true; Fx.Assert(parentReceiveScope != null, "Internal error.. TransactedReceiveScope should be valid here"); if (IsFirstTransactedReceive(receive, parentReceiveScope, rootTransactedReceiveScope)) { receive.InternalReceive.AdditionalData.IsFirstReceiveOfTransactedReceiveScopeTree = true; } } this.knownServiceActivities.Add(receive); } else if (activity is SendReply) // Let's see if this is a SendReply { SendReply sendReply = (SendReply)activity; Receive pairedReceive = sendReply.Request; Fx.Assert(pairedReceive != null, "SendReply must point to a Receive!"); if (sendReply.InternalContent.IsFault) { pairedReceive.FollowingFaults.Add(sendReply); } else { if (pairedReceive.HasReply) { SendReply followingReply = pairedReceive.FollowingReplies[0]; ContractValidationHelper.ValidateSendReplyWithSendReply(followingReply, sendReply); } pairedReceive.FollowingReplies.Add(sendReply); } ReceiveAndReplyTuple tuple = new ReceiveAndReplyTuple(pairedReceive, sendReply); this.receiveAndReplyPairs.Add(tuple); } // Enqueue child activities and delegates if (activity is TransactedReceiveScope) { parentReceiveScope = activity; if (rootTransactedReceiveScope == null) { rootTransactedReceiveScope = parentReceiveScope; } } foreach (Activity childActivity in WorkflowInspectionServices.GetActivities(activity)) { QueueItem queueData = new QueueItem(childActivity, parentReceiveScope, rootTransactedReceiveScope); activities.Enqueue(queueData); } } } XName FixServiceContractName(XName serviceContractName) { // By default, we use WorkflowService.Name as ServiceContractName XName contractXName = serviceContractName ?? this.InternalName; ContractInferenceHelper.ProvideDefaultNamespace(ref contractXName); return contractXName; } static void CorrectOutMessageForOperationWithFault(Receive receive, OperationInfo operationInfo) { Fx.Assert(receive != null && operationInfo != null, "Argument cannot be null!"); Receive prevReceive = operationInfo.Receive; if (receive != prevReceive && receive.HasReply && !prevReceive.HasReply && prevReceive.HasFault) { ContractInferenceHelper.CorrectOutMessageForOperation(receive, operationInfo.OperationDescription); operationInfo.Receive = receive; } } void CollectCorrelationQuery(ref Collection queries, XName serviceContractName, CorrelationQuery correlationQuery) { Fx.Assert(serviceContractName != null, "Argument cannot be null!"); if (correlationQuery == null) { return; } if (queries == null && !this.correlationQueryByContract.TryGetValue(serviceContractName, out queries)) { queries = new Collection(); this.correlationQueryByContract.Add(serviceContractName, queries); } queries.Add(correlationQuery); } void CollectCorrelationQueryFromReply(ref Collection correlationQueries, XName serviceContractName, Activity reply, OperationDescription operation) { SendReply sendReply = reply as SendReply; if (sendReply != null) { CorrelationQuery correlationQuery = ContractInferenceHelper.CreateServerCorrelationQuery( null, sendReply.CorrelationInitializers, operation, true); CollectCorrelationQuery(ref correlationQueries, serviceContractName, correlationQuery); } } internal void ResetServiceDescription() { this.serviceDescription = null; this.cachedInferredContracts = null; } bool IsFirstTransactedReceive(Receive request, Activity parent, Activity root) { Receive receive = null; if (parent != null && root != null) { TransactedReceiveScope rootTRS = root as TransactedReceiveScope; if (rootTRS != null) { receive = rootTRS.Request; } } return (parent == root && receive == request); } Constraint GetContractFirstValidationReceiveConstraints() { DelegateInArgument element = new DelegateInArgument { Name = "ReceiveElement" }; DelegateInArgument validationContext = new DelegateInArgument { Name = "validationContext" }; Variable> parentChainVar = new Variable>("parentChainVar"); return new Constraint { Body = new ActivityAction { Argument1 = element, Argument2 = validationContext, Handler = new Sequence { Variables = { parentChainVar }, Activities = { new GetParentChain { ValidationContext = validationContext, Result = parentChainVar }, new ValidateReceiveContract() { DisplayName = "ValidateReceiveContract", ReceiveActivity = element, WorkflowService = new InArgument() { Expression = new GetWorkflowSerivce(this) }, ParentChain = parentChainVar, } } } } }; } Constraint GetContractFirstValidationSendReplyConstraints() { DelegateInArgument element = new DelegateInArgument { Name = "ReceiveElement" }; DelegateInArgument validationContext = new DelegateInArgument { Name = "validationContext" }; return new Constraint { Body = new ActivityAction { Argument1 = element, Argument2 = validationContext, Handler = new Sequence { Activities = { new ValidateSendReplyContract() { DisplayName = "ValidateReceiveContract", ReceiveActivity = element, WorkflowSerivce = new InArgument() { Expression = new GetWorkflowSerivce(this) }, ValidationContext = validationContext } } } } }; } struct ContractAndOperationNameTuple : IEquatable { XName serviceContractXName; string operationName; public ContractAndOperationNameTuple(XName serviceContractXName, string operationName) { this.serviceContractXName = serviceContractXName; this.operationName = operationName; } public bool Equals(ContractAndOperationNameTuple other) { return this.serviceContractXName == other.serviceContractXName && this.operationName == other.operationName; } public override int GetHashCode() { int hashCode = 0; if (this.serviceContractXName != null) { hashCode ^= this.serviceContractXName.GetHashCode(); } hashCode ^= this.operationName.GetHashCode(); return hashCode; } } struct ReceiveAndReplyTuple : IEquatable { Receive receive; Activity reply; public ReceiveAndReplyTuple(Receive receive, SendReply reply) { this.receive = receive; this.reply = reply; } public bool Equals(ReceiveAndReplyTuple other) { return this.receive == other.receive && this.reply == other.reply; } public override int GetHashCode() { int hash = 0; if (this.receive != null) { hash ^= this.receive.GetHashCode(); } if (this.reply != null) { hash ^= this.reply.GetHashCode(); } return hash; } } class OperationInfo { Receive receive; OperationDescription operationDescription; public OperationInfo(Receive receive, OperationDescription operationDescription) { this.receive = receive; this.operationDescription = operationDescription; } public Receive Receive { get { return this.receive; } set { this.receive = value; } } public OperationDescription OperationDescription { get { return this.operationDescription; } } } class QueueItem { Activity activity; Activity parent; Activity rootTransactedReceiveScope; public QueueItem(Activity element, Activity parent, Activity root) { this.activity = element; this.parent = parent; this.rootTransactedReceiveScope = root; } public Activity Activity { get { return this.activity; } } public Activity ParentReceiveScope { get { return this.parent; } } public Activity RootTransactedReceiveScope { get { return this.rootTransactedReceiveScope; } } } class GetWorkflowSerivce : CodeActivity { WorkflowService workflowService; public GetWorkflowSerivce(WorkflowService serviceReference) { workflowService = serviceReference; } protected override void CacheMetadata(CodeActivityMetadata metadata) { RuntimeArgument resultArgument = new RuntimeArgument("Result", typeof(WorkflowService), ArgumentDirection.Out); metadata.Bind(this.Result, resultArgument); metadata.SetArgumentsCollection( new Collection { resultArgument }); } protected override WorkflowService Execute(CodeActivityContext context) { return workflowService; } } class ValidateReceiveContract : NativeActivity { public InArgument ReceiveActivity { get; set; } public InArgument> ParentChain { get; set; } public InArgument WorkflowService { get; set; } protected override void CacheMetadata(NativeActivityMetadata metadata) { RuntimeArgument receiveActivity = new RuntimeArgument("ReceiveActivity", typeof(Receive), ArgumentDirection.In); RuntimeArgument parentChain = new RuntimeArgument("ParentChain", typeof(IEnumerable), ArgumentDirection.In); RuntimeArgument operationProperties = new RuntimeArgument("OperationProperties", typeof(WorkflowService), ArgumentDirection.In); if (this.ReceiveActivity == null) { this.ReceiveActivity = new InArgument(); } metadata.Bind(this.ReceiveActivity, receiveActivity); if (this.ParentChain == null) { this.ParentChain = new InArgument>(); } metadata.Bind(this.ParentChain, parentChain); if (this.WorkflowService == null) { this.WorkflowService = new InArgument(); } metadata.Bind(this.WorkflowService, operationProperties); Collection arguments = new Collection { receiveActivity, parentChain, operationProperties, }; metadata.SetArgumentsCollection(arguments); } protected override void Execute(NativeActivityContext context) { Receive receiveActivity = this.ReceiveActivity.Get(context); Dictionary operationProperties; Fx.Assert(receiveActivity != null, "ValidateReceiveContract needs the receive activity to be present"); if (string.IsNullOrEmpty(receiveActivity.OperationName)) { Constraint.AddValidationError(context, new ValidationError(SR.MissingOperationName(this.DisplayName))); } else { WorkflowService workflowService = this.WorkflowService.Get(context); operationProperties = workflowService.OperationProperties; XName serviceName = workflowService.FixServiceContractName(receiveActivity.ServiceContractName); // We only do contract first validation if there are contract specified if (operationProperties != null) { string contractName = serviceName.LocalName; string contractNamespace = string.IsNullOrEmpty(serviceName.NamespaceName) ? NamingHelper.DefaultNamespace : serviceName.NamespaceName; string operationXmlName = NamingHelper.XmlName(receiveActivity.OperationName); OperationProperty property; OperationIdentifier operationId = new OperationIdentifier(contractName, contractNamespace, operationXmlName); if (operationProperties.TryGetValue(operationId, out property)) { property.ImplementingReceives.Add(receiveActivity); Fx.Assert(property.Operation != null, "OperationProperty.Operation should not be null!"); ValidateContract(context, receiveActivity, property.Operation); } else { // It is OK to add a new contract, but we do not allow adding a new operation to a specified contract. foreach (OperationIdentifier id in operationProperties.Keys) { if (contractName == id.ContractName && contractNamespace == id.ContractNamespace) { Constraint.AddValidationError(context, new ValidationError(SR.OperationDoesNotExistInContract(receiveActivity.OperationName, contractName, contractNamespace))); break; } } } } } } void ValidateTransactionBehavior(NativeActivityContext context, Receive receiveActivity, OperationDescription targetOperation) { TransactionFlowAttribute transactionFlowAttribute = targetOperation.Behaviors.Find(); Activity parent = null; // we know it's IList IList parentChain = (IList)this.ParentChain.Get(context); if (parentChain.Count > 0) { parent = parentChain[0]; } bool isInTransactedReceiveScope = false; TransactedReceiveScope trs = parent as TransactedReceiveScope; if (trs != null && trs.Request == receiveActivity) { isInTransactedReceiveScope = true; } if (transactionFlowAttribute != null) { if (transactionFlowAttribute.Transactions == TransactionFlowOption.Mandatory) { if (targetOperation.IsOneWay) { Constraint.AddValidationError(context, new ValidationError(SR.TargetContractCannotBeOneWayWithTransactionFlow(targetOperation.Name, targetOperation.DeclaringContract.Name))); } // Receive has to be in a transacted receive scope if (!isInTransactedReceiveScope) { Constraint.AddValidationError(context, new ValidationError(SR.ReceiveIsNotInTRS(targetOperation.Name, targetOperation.DeclaringContract.Name))); } } else if (transactionFlowAttribute.Transactions == TransactionFlowOption.NotAllowed) { if (isInTransactedReceiveScope) { Constraint.AddValidationError(context, new ValidationError(SR.ReceiveIsInTRSWhenTransactionFlowNotAllowed(targetOperation.Name, targetOperation.DeclaringContract.Name), true)); } } } } void ValidateContract(NativeActivityContext context, Receive receiveActivity, OperationDescription targetOperation) { SerializerOption targetSerializerOption = targetOperation.Behaviors.Contains(typeof(XmlSerializerOperationBehavior)) ? SerializerOption.XmlSerializer : SerializerOption.DataContractSerializer; if (receiveActivity.SerializerOption != targetSerializerOption) { Constraint.AddValidationError(context, new ValidationError(SR.PropertyMismatch(receiveActivity.SerializerOption.ToString(), "SerializerOption", targetSerializerOption.ToString(), targetOperation.DeclaringContract.Name, targetSerializerOption.ToString()))); } if ((!targetOperation.HasProtectionLevel && receiveActivity.ProtectionLevel.HasValue && receiveActivity.ProtectionLevel != Net.Security.ProtectionLevel.None) || (receiveActivity.ProtectionLevel.HasValue && receiveActivity.ProtectionLevel.Value != targetOperation.ProtectionLevel) || (!receiveActivity.ProtectionLevel.HasValue && targetOperation.ProtectionLevel != Net.Security.ProtectionLevel.None)) { string targetProtectionLevelString = targetOperation.HasProtectionLevel ? targetOperation.ProtectionLevel.ToString() : SR.NotSpecified; string receiveProtectionLevelString = receiveActivity.ProtectionLevel.HasValue ? receiveActivity.ProtectionLevel.ToString() : SR.NotSpecified; Constraint.AddValidationError(context, new ValidationError(SR.PropertyMismatch(receiveProtectionLevelString, "ProtectionLevel", targetProtectionLevelString, targetOperation.Name, targetOperation.DeclaringContract.Name))); } // We validate that all known types on the contract be present on the activity. // If activity contains more known types, we don't mind. if (targetOperation.KnownTypes.Count > 0) { // We require that each Receive contains all the known types specified on the contract. // Known type collections from multiple Receive activities with same contract name and operation name will NOT be merged. // We expect the number of known types to be small, therefore we choose to use simple iterative search. foreach (Type targetType in targetOperation.KnownTypes) { if (receiveActivity.KnownTypes == null || !receiveActivity.KnownTypes.Contains(targetType)) { if (targetType != null && targetType != TypeHelper.VoidType) { Constraint.AddValidationError(context, new ValidationError(SR.MissingKnownTypes(targetType.FullName, targetOperation.Name, targetOperation.DeclaringContract.Name))); } } } } this.ValidateTransactionBehavior(context, receiveActivity, targetOperation); receiveActivity.InternalContent.ValidateContract(context, targetOperation, receiveActivity, MessageDirection.Input); } } class ValidateSendReplyContract : NativeActivity { public InArgument ReceiveActivity { get; set; } public InArgument ValidationContext { get; set; } public InArgument WorkflowSerivce { get; set; } protected override void CacheMetadata(NativeActivityMetadata metadata) { RuntimeArgument receiveActivity = new RuntimeArgument("ReceiveActivity", typeof(SendReply), ArgumentDirection.In); RuntimeArgument validationContext = new RuntimeArgument("ValidationContext", typeof(ValidationContext), ArgumentDirection.In); RuntimeArgument operationProperties = new RuntimeArgument("OperationProperties", typeof(WorkflowService), ArgumentDirection.In); if (this.ReceiveActivity == null) { this.ReceiveActivity = new InArgument(); } metadata.Bind(this.ReceiveActivity, receiveActivity); if (this.ValidationContext == null) { this.ValidationContext = new InArgument(); } metadata.Bind(this.ValidationContext, validationContext); if (this.WorkflowSerivce == null) { this.WorkflowSerivce = new InArgument(); } metadata.Bind(this.WorkflowSerivce, operationProperties); Collection arguments = new Collection { receiveActivity, validationContext, operationProperties }; metadata.SetArgumentsCollection(arguments); } protected override void Execute(NativeActivityContext context) { SendReply sendReplyActivity = this.ReceiveActivity.Get(context); Dictionary operationProperties; if (sendReplyActivity.Request != null) { if (sendReplyActivity.Request.ServiceContractName != null && sendReplyActivity.Request.OperationName != null) { WorkflowService workflowService = this.WorkflowSerivce.Get(context); operationProperties = workflowService.OperationProperties; if (operationProperties != null) { XName contractXName = sendReplyActivity.Request.ServiceContractName; string contractName = contractXName.LocalName; string contractNamespace = string.IsNullOrEmpty(contractXName.NamespaceName) ? NamingHelper.DefaultNamespace : contractXName.NamespaceName; string operationXmlName = NamingHelper.XmlName(sendReplyActivity.Request.OperationName); OperationProperty property; OperationIdentifier id = new OperationIdentifier(contractName, contractNamespace, operationXmlName); if (operationProperties.TryGetValue(id, out property)) { if (!property.Operation.IsOneWay) { property.ImplementingSendRepliesRequests.Add(sendReplyActivity.Request); Fx.Assert(property.Operation != null, "OperationProperty.Operation should not be null!"); ValidateContract(context, sendReplyActivity, property.Operation); } else { Constraint.AddValidationError(context, new ValidationError(SR.OnewayContractIsImplementedAsTwoWay(property.Operation.Name, contractName))); } } } } } } void ValidateContract(NativeActivityContext context, SendReply sendReply, OperationDescription targetOperation) { sendReply.InternalContent.ValidateContract(context, targetOperation, sendReply, MessageDirection.Output); } } } }