e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1282 lines
54 KiB
C#
1282 lines
54 KiB
C#
//----------------------------------------------------------------
|
|
// 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<Endpoint> endpoints;
|
|
Collection<Type> implementedContracts;
|
|
NullableKeyDictionary<WorkflowIdentity, DynamicUpdateMap> updateMaps;
|
|
|
|
IDictionary<XName, ContractDescription> cachedInferredContracts;
|
|
IDictionary<XName, Collection<CorrelationQuery>> correlationQueryByContract;
|
|
IDictionary<ContractAndOperationNameTuple, OperationInfo> keyedByNameOperationInfo;
|
|
|
|
IList<Receive> knownServiceActivities;
|
|
HashSet<ReceiveAndReplyTuple> 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<Endpoint> Endpoints
|
|
{
|
|
get
|
|
{
|
|
if (this.endpoints == null)
|
|
{
|
|
this.endpoints = new Collection<Endpoint>();
|
|
}
|
|
return this.endpoints;
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.KnownXamlExternal]
|
|
[DefaultValue(null)]
|
|
public WorkflowIdentity DefinitionIdentity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
|
|
public Collection<Type> ImplementedContracts
|
|
{
|
|
get
|
|
{
|
|
if (this.implementedContracts == null)
|
|
{
|
|
this.implementedContracts = new Collection<Type>();
|
|
}
|
|
return this.implementedContracts;
|
|
}
|
|
}
|
|
|
|
public IDictionary<WorkflowIdentity, DynamicUpdateMap> UpdateMaps
|
|
{
|
|
get
|
|
{
|
|
if (this.updateMaps == null)
|
|
{
|
|
this.updateMaps = new NullableKeyDictionary<WorkflowIdentity, DynamicUpdateMap>();
|
|
}
|
|
return this.updateMaps;
|
|
}
|
|
}
|
|
|
|
internal bool HasImplementedContracts
|
|
{
|
|
get
|
|
{
|
|
return this.implementedContracts != null && this.implementedContracts.Count > 0;
|
|
}
|
|
}
|
|
|
|
[DefaultValue(null)]
|
|
internal Dictionary<OperationIdentifier, OperationProperty> OperationProperties
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
IDictionary<ContractAndOperationNameTuple, OperationInfo> 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<XName, Collection<CorrelationQuery>> 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<Constraint> constraintList;
|
|
if (workflowServiceSettings.AdditionalConstraints.TryGetValue(constraintType, out constraintList))
|
|
{
|
|
constraintList.Add(constraint);
|
|
}
|
|
else
|
|
{
|
|
constraintList = new List<Constraint>(1)
|
|
{
|
|
constraint,
|
|
};
|
|
workflowServiceSettings.AdditionalConstraints.Add(constraintType, constraintList);
|
|
}
|
|
}
|
|
|
|
public ValidationResults Validate(ValidationSettings settings)
|
|
{
|
|
Collection<ValidationError> errors = new Collection<ValidationError>();
|
|
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> 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<Type, IList<Constraint>> constrants in source.AdditionalConstraints)
|
|
{
|
|
if (constrants.Key != null && constrants.Value != null)
|
|
{
|
|
clonedSettings.AdditionalConstraints.Add(constrants.Key, new List<Constraint>(constrants.Value));
|
|
}
|
|
}
|
|
|
|
return clonedSettings;
|
|
}
|
|
|
|
void AfterValidation(Collection<ValidationError> errors)
|
|
{
|
|
if (this.HasImplementedContracts)
|
|
{
|
|
Dictionary<OperationIdentifier, OperationProperty> 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<OperationIdentifier, OperationProperty> CreateOperationProperties(Collection<ValidationError> validationErrors)
|
|
{
|
|
Dictionary<OperationIdentifier, OperationProperty> operationProperties = null;
|
|
|
|
if (this.HasImplementedContracts)
|
|
{
|
|
operationProperties = new Dictionary<OperationIdentifier, OperationProperty>();
|
|
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<XName, ContractDescription> GetContractDescriptions()
|
|
{
|
|
if (this.cachedInferredContracts == null)
|
|
{
|
|
WalkActivityTree();
|
|
|
|
Fx.Assert(this.knownServiceActivities != null && this.receiveAndReplyPairs != null, "Failed to walk the activity tree!");
|
|
this.correlationQueryByContract = new Dictionary<XName, Collection<CorrelationQuery>>();
|
|
|
|
// Contract inference
|
|
IDictionary<XName, ContractDescription> inferredContracts = new Dictionary<XName, ContractDescription>();
|
|
this.keyedByNameOperationInfo = new Dictionary<ContractAndOperationNameTuple, OperationInfo>();
|
|
|
|
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<CorrelationQuery> 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<Receive>();
|
|
this.receiveAndReplyPairs = new HashSet<ReceiveAndReplyTuple>();
|
|
|
|
// Now let us walk the tree here
|
|
Queue<QueueItem> activities = new Queue<QueueItem>();
|
|
// 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<CorrelationQuery> 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<CorrelationQuery>();
|
|
this.correlationQueryByContract.Add(serviceContractName, queries);
|
|
}
|
|
|
|
queries.Add(correlationQuery);
|
|
}
|
|
|
|
void CollectCorrelationQueryFromReply(ref Collection<CorrelationQuery> 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<Receive> element = new DelegateInArgument<Receive> { Name = "ReceiveElement" };
|
|
DelegateInArgument<ValidationContext> validationContext = new DelegateInArgument<ValidationContext> { Name = "validationContext" };
|
|
Variable<IEnumerable<Activity>> parentChainVar = new Variable<IEnumerable<Activity>>("parentChainVar");
|
|
return new Constraint<Receive>
|
|
{
|
|
Body = new ActivityAction<Receive, ValidationContext>
|
|
{
|
|
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<WorkflowService>()
|
|
{
|
|
Expression = new GetWorkflowSerivce(this)
|
|
},
|
|
ParentChain = parentChainVar,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
Constraint GetContractFirstValidationSendReplyConstraints()
|
|
{
|
|
DelegateInArgument<SendReply> element = new DelegateInArgument<SendReply> { Name = "ReceiveElement" };
|
|
DelegateInArgument<ValidationContext> validationContext = new DelegateInArgument<ValidationContext> { Name = "validationContext" };
|
|
|
|
return new Constraint<SendReply>
|
|
{
|
|
Body = new ActivityAction<SendReply, ValidationContext>
|
|
{
|
|
Argument1 = element,
|
|
Argument2 = validationContext,
|
|
Handler = new Sequence
|
|
{
|
|
Activities =
|
|
{
|
|
new ValidateSendReplyContract()
|
|
{
|
|
DisplayName = "ValidateReceiveContract",
|
|
ReceiveActivity = element,
|
|
WorkflowSerivce = new InArgument<WorkflowService>()
|
|
{
|
|
Expression = new GetWorkflowSerivce(this)
|
|
},
|
|
ValidationContext = validationContext
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
struct ContractAndOperationNameTuple : IEquatable<ContractAndOperationNameTuple>
|
|
{
|
|
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<ReceiveAndReplyTuple>
|
|
{
|
|
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 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<RuntimeArgument>
|
|
{
|
|
resultArgument
|
|
});
|
|
}
|
|
|
|
protected override WorkflowService Execute(CodeActivityContext context)
|
|
{
|
|
return workflowService;
|
|
}
|
|
}
|
|
|
|
class ValidateReceiveContract : NativeActivity
|
|
{
|
|
public InArgument<Receive> ReceiveActivity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public InArgument<IEnumerable<Activity>> ParentChain
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public InArgument<WorkflowService> 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<Activity>), ArgumentDirection.In);
|
|
RuntimeArgument operationProperties = new RuntimeArgument("OperationProperties", typeof(WorkflowService), ArgumentDirection.In);
|
|
|
|
if (this.ReceiveActivity == null)
|
|
{
|
|
this.ReceiveActivity = new InArgument<Receive>();
|
|
}
|
|
metadata.Bind(this.ReceiveActivity, receiveActivity);
|
|
|
|
if (this.ParentChain == null)
|
|
{
|
|
this.ParentChain = new InArgument<IEnumerable<Activity>>();
|
|
}
|
|
metadata.Bind(this.ParentChain, parentChain);
|
|
|
|
if (this.WorkflowService == null)
|
|
{
|
|
this.WorkflowService = new InArgument<WorkflowService>();
|
|
}
|
|
metadata.Bind(this.WorkflowService, operationProperties);
|
|
|
|
Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
|
|
{
|
|
receiveActivity,
|
|
parentChain,
|
|
operationProperties,
|
|
};
|
|
|
|
metadata.SetArgumentsCollection(arguments);
|
|
}
|
|
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
Receive receiveActivity = this.ReceiveActivity.Get(context);
|
|
Dictionary<OperationIdentifier, OperationProperty> 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<TransactionFlowAttribute>();
|
|
Activity parent = null;
|
|
|
|
// we know it's IList<Activity>
|
|
IList<Activity> parentChain = (IList<Activity>)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<SendReply> ReceiveActivity
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public InArgument<ValidationContext> ValidationContext
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public InArgument<WorkflowService> 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<SendReply>();
|
|
}
|
|
metadata.Bind(this.ReceiveActivity, receiveActivity);
|
|
|
|
if (this.ValidationContext == null)
|
|
{
|
|
this.ValidationContext = new InArgument<ValidationContext>();
|
|
}
|
|
metadata.Bind(this.ValidationContext, validationContext);
|
|
|
|
if (this.WorkflowSerivce == null)
|
|
{
|
|
this.WorkflowSerivce = new InArgument<WorkflowService>();
|
|
}
|
|
metadata.Bind(this.WorkflowSerivce, operationProperties);
|
|
|
|
Collection<RuntimeArgument> arguments = new Collection<RuntimeArgument>
|
|
{
|
|
receiveActivity,
|
|
validationContext,
|
|
operationProperties
|
|
};
|
|
|
|
metadata.SetArgumentsCollection(arguments);
|
|
}
|
|
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
SendReply sendReplyActivity = this.ReceiveActivity.Get(context);
|
|
Dictionary<OperationIdentifier, OperationProperty> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|