Windows Workflow Foundation 3
Rule Conditions against custom activities and XAML activation - how should I achieve this?
If I have a simple custom activity and I generate a XAML only workflow with a rule condition that references the activity then how can I use XAML activation to execute the workflow? Here's the workflow <SequentialWorkflowActivity x:Name="xyz" xmlns:ns0="clr-namespace:ABC.Workflow.Common.ActivityLibrary;Assembly=ABC.Workflow.Common.ActivityLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"> <ns0:ConsoleWritelineActivity Message="test" x:Name="consoleWritelineActivity1" /> <IfElseActivity x:Name="ifElseActivity1"> <IfElseBranchActivity x:Name="ifElseBranchActivity1"> <IfElseBranchActivity.Condition> <RuleConditionReference ConditionName="isATest" /> </IfElseBranchActivity.Condition> </IfElseBranchActivity> <IfElseBranchActivity x:Name="ifElseBranchActivity2" /> </IfElseActivity> </SequentialWorkflowActivity> I am using XAML activation to execute the workflow, catching WorkflowValidationFailedException, and have added the custom activity's assembly to a type provider thus, (which is inadequate, I suspect), TypeProvider typeProvider = new TypeProvider(workflowRuntime);typeProvider.AddAssembly(typeof(ConsoleWritelineActivity).Assembly);workflowRuntime.AddService(typeProvider); My workflow gets terminated with this message Unable to validate Condition \"isATest\" as there are validation errors if the rule is (this.consoleWritelineActivity1.Message == "test") i.e. <RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"> <RuleDefinitions.Conditions> <RuleExpressionCondition Name="isATest"> <RuleExpressionCondition.Expression> <ns0:CodeBinaryOperatorExpression Operator="ValueEquality" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <ns0:CodeBinaryOperatorExpression.Left> <ns0:CodePropertyReferenceExpression PropertyName="Message"> <ns0:CodePropertyReferenceExpression.TargetObject> <ns0:CodeFieldReferenceExpression FieldName="consoleWritelineActivity1"> <ns0:CodeFieldReferenceExpression.TargetObject> <ns0:CodeThisReferenceExpression /> </ns0:CodeFieldReferenceExpression.TargetObject> </ns0:CodeFieldReferenceExpression> </ns0:CodePropertyReferenceExpression.TargetObject> </ns0:CodePropertyReferenceExpression> </ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeBinaryOperatorExpression.Right> <ns0:CodePrimitiveExpression> <ns0:CodePrimitiveExpression.Value> <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">test</ns1:String> </ns0:CodePrimitiveExpression.Value> </ns0:CodePrimitiveExpression> </ns0:CodeBinaryOperatorExpression.Right> </ns0:CodeBinaryOperatorExpression> </RuleExpressionCondition.Expression> </RuleExpressionCondition> </RuleDefinitions.Conditions></RuleDefinitions> and my workflow gets terminated with this message An item with the same key has already been added if the rule is (((ABC.Workflow.Common.ActivityLibrary.ConsoleWritelineActivity)this.GetActivityByName("consoleWritelineActivity")).Message == "test") i.e. <RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"> <RuleDefinitions.Conditions> <RuleExpressionCondition Name="isATest"> <RuleExpressionCondition.Expression> <ns0:CodeBinaryOperatorExpression Operator="ValueEquality" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <ns0:CodeBinaryOperatorExpression.Left> <ns0:CodePropertyReferenceExpression PropertyName="Message"> <ns0:CodePropertyReferenceExpression.TargetObject> <ns0:CodeCastExpression> <ns0:CodeCastExpression.Expression> <ns0:CodeMethodInvokeExpression> <ns0:CodeMethodInvokeExpression.Parameters> <ns0:CodePrimitiveExpression> <ns0:CodePrimitiveExpression.Value> <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">consoleWritelineActivity</ns1:String> </ns0:CodePrimitiveExpression.Value> </ns0:CodePrimitiveExpression> </ns0:CodeMethodInvokeExpression.Parameters> <ns0:CodeMethodInvokeExpression.Method> <ns0:CodeMethodReferenceExpression MethodName="GetActivityByName"> <ns0:CodeMethodReferenceExpression.TargetObject> <ns0:CodeThisReferenceExpression /> </ns0:CodeMethodReferenceExpression.TargetObject> </ns0:CodeMethodReferenceExpression> </ns0:CodeMethodInvokeExpression.Method> </ns0:CodeMethodInvokeExpression> </ns0:CodeCastExpression.Expression> <ns0:CodeCastExpression.TargetType> <ns0:CodeTypeReference ArrayElementType="{p11:Null}" BaseType="ABC.Workflow.Common.ActivityLibrary.ConsoleWritelineActivity" Options="0" ArrayRank="0" xmlns:p11="http://schemas.microsoft.com/winfx/2006/xaml" /> </ns0:CodeCastExpression.TargetType> </ns0:CodeCastExpression> </ns0:CodePropertyReferenceExpression.TargetObject> </ns0:CodePropertyReferenceExpression> </ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeBinaryOperatorExpression.Right> <ns0:CodePrimitiveExpression> <ns0:CodePrimitiveExpression.Value> <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">test</ns1:String> </ns0:CodePrimitiveExpression.Value> </ns0:CodePrimitiveExpression> </ns0:CodeBinaryOperatorExpression.Right> </ns0:CodeBinaryOperatorExpression> </RuleExpressionCondition.Expression> </RuleExpressionCondition> </RuleDefinitions.Conditions></RuleDefinitions>
In a code only workflow there is a private variable created for each activity added to the workflow allowing you to do this.activityName to access the activities in the workflow, this will only work for code only workflows. When you are working with xoml workflow, code separation or xoml only, you need to use GetActivityByName to have access to the activity since no variables are created. Which build are you using? Can you post the StackTrace for the “An item with the same key has already been added” exception you are getting when you use GetActivityByName?
Writing a special type provider will not work. The variables don't exist in the workflow so there is nothing to resolve. A type provider is use to resolve types not variables. This is a known issue with using XAML activation that will not be fixed in V1. We are looking into make it easier in a future version.
Go with your second option since "this" is going to refer to a sequential workflow activity and not a new workflow type. In order to get this to work, you need to edit one line of your rules file in order to qualify the type name. What is happening is that the rule validation code is not finding your type in the referenced assembly so it is trying to add it into the collection. Change this: <ns0:CodeTypeReference ArrayElementType="{p11:Null}" BaseType="ABC.Workflow.Common.ActivityLibrary.ConsoleWritelineActivity" Options="0" ArrayRank="0" xmlns:p11="http://schemas.microsoft.com/winfx/2006/xaml" /> to this (Assuming an assembly name of ABC.Workflow.Common.ActivityLibrary): <ns0:CodeTypeReference ArrayElementType="{p11:Null}" BaseType="ABC.Workflow.Common.ActivityLibrary.ConsoleWritelineActivity,ABC.Workflow.Common.ActivityLibrary" Options="0" ArrayRank="0" xmlns:p11="http://schemas.microsoft.com/winfx/2006/xaml" /> This should work for you. Matt
Thanks Matt. Editing the rules xaml moves me onto another error "Cannot evaluate property \"Message\" because its target object is null". Ignoring that, for the moment, I have two other issues. 1. This xaml was serialized by my rehosted designer control which obviously knew enough about the activity in order to sucessfully create the rule. Not sure why this would be wrong. 2. I want the first style of rule because the second is too complex for customers who are going to be editing our xoml workflows. Can you set me on the right track to get this working? (I guess that I may still have an issue with the rules xaml generated by the designer, but that's another problem).
The problem is that when you are editing a default XOML workflow, "this" points to the type SequentialWorkflowActivity. This type does not have any properties, nor can you get typed access to the child activities directly, as the XOML has no "fields" defined which point to those activities. I haven't dug into the rules editor in much detail yet, but it seems very code/type focused so you might run into some problems there when trying to write rules for XOML workflows. One solution is to create a base workflow type that derives from the sequential type and then build your XOML workflows for this type. Then you can have methods on your base type that will help people build rules like a method that makes it easy to reference child activities. As for your item 1, when I did this my XOML was created from the VS hosted designer, but it still didn't work until I put the assembly name in there. I'm not sure if this is technically a bug but it causes problems. Matt
In a code only workflow there is a private variable created for each activity added to the workflow allowing you to do this.activityName to access the activities in the workflow, this will only work for code only workflows. When you are working with xoml workflow, code separation or xoml only, you need to use GetActivityByName to have access to the activity since no variables are created. Which build are you using? Can you post the StackTrace for the “An item with the same key has already been added” exception you are getting when you use GetActivityByName?
Stack trace below. (using WinFx beta 2) I believe, from other posts, that I can use the first approach but would have to write some kind of custom type provider. I cannot expect customers to write rules using GetActivityByName and casting etc. Unfortunately, I don't know how I should go about writing such a type provider. Can you help? " at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)\r\n at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)\r\n at System.Workflow.Activities.Rules.SimpleRunTimeTypeProvider.get_RemainingAssemblies()\r\n at System.Workflow.Activities.Rules.SimpleRunTimeTypeProvider.GetType(String name, Boolean throwOnError)\r\n at System.Workflow.Activities.Rules.RuleValidation.FindType(String typeName)\r\n at System.Workflow.Activities.Rules.RuleValidation.ResolveType(CodeTypeReference typeRef)\r\n at System.Workflow.Activities.Rules.CastExpression.System.Workflow.Activities.Rules.IRuleExpression.Validate(RuleValidation validation, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.RuleExpressionWalker.Validate(RuleValidation validation, CodeExpression expression, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.PropertyReferenceExpression.System.Workflow.Activities.Rules.IRuleExpression.Validate(RuleValidation validation, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.RuleExpressionWalker.Validate(RuleValidation validation, CodeExpression expression, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.BinaryExpression.System.Workflow.Activities.Rules.IRuleExpression.Validate(RuleValidation validation, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.RuleExpressionWalker.Validate(RuleValidation validation, CodeExpression expression, Boolean isWritten)\r\n at System.Workflow.Activities.Rules.RuleValidation.ValidateConditionExpression(CodeExpression expression)\r\n at System.Workflow.Activities.Rules.RuleExpressionCondition.Validate(RuleValidation validation)\r\n at System.Workflow.Activities.Rules.RuleConditionReference.Evaluate(Activity activity, IServiceProvider provider)\r\n at System.Workflow.Activities.IfElseActivity.Execute(ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.CompositeActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.ActivityExecutionFilter.Execute(Activity activity, ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.FaultAndCancellationHandlingFilter.Execute(Activity activity, ActivityExecutionContext executionContext)\r\n at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)\r\n at System.Workflow.Runtime.Scheduler.Run()"
Writing a special type provider will not work. The variables don't exist in the workflow so there is nothing to resolve. A type provider is use to resolve types not variables. This is a known issue with using XAML activation that will not be fixed in V1. We are looking into make it easier in a future version.
Related Links
missing method exception in CallExternalMethodActivity
Performance of runtime creation and execution rules in WF
Error in my WorkFlow (Response.Redirect)
Approving Multiple Records in one go..
Dynamic Update
The Executing event of all my SetStateActivity objects dont fire
Design guidance needed for WWF application
Custom pub/sub service for WF message correlation
Is there any event get fired when I changed the content in the workflow designer?
Subscription handler threw System.FormatException
How can I designate string[] field as returnValue for ReceiveActivity?
is the "workflows in memory" performance counter failing to be reset?
Creating a workflow dynamically
writing non-persistance data to Oracle from a workflow app
Is Workflow Foundation a good fit for multiple workflow processes
The workflow failed validation