|
//----------------------------------------------------------------
// 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);
}
}
}
}
|