Workflow Cloudbusting / Hiding code & logic in conditions in Workflow Foundation and BizTalk Workflow Services

(German version may come at a later time / Deutsche Version wird eventuell nachgereicht)

First of all: Please play around with BizTalk Workflow Services and share what you find out. I feel lonely ;)

This post deals with BizTalk Workflow Services. However, most of it also applies to general Workflow Foundation.

You might notice that in the BizTalk Workflow Services of the current BizTalk Labs SDK you do not find any activities that allow you to "set" a value or to execute custom code.

This makes implementing complex logic practically impossible since you cannot react in a meaningful way to data that you get through a send/receive.

However, there is a "dirty" trick you can use: Hide your logic in a rules-condition.
The following is a proof-of-concept and there are likely things that could be done better :)

Take the same preparations as in my intro to BizTalk Workflow Services having your command line output up and running to receive and display messages. Remember what I wrote regarding flow control for BizTalk Workflow Services.

Now look at this really simple workflow definition

RulesHackWFDiagram1

 

and it's even simpler XOML representation (sorry that it will not fit in most web browsers - please copy/paste to read):

<SequentialWorkflowActivity x:Class="WorkflowProject1.Workflow1" x:Name="Workflow1" xmlns:ns0="http://schemas.microsoft.com/2007/11/CloudWorkflowActivityLibrary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

  <ns0:CloudWhile x:Name="cloudWhile1">

    <ns0:CloudWhile.Condition>

      <RuleConditionReference ConditionName="While Condition" />

    </ns0:CloudWhile.Condition>

    <ns0:CloudSequence x:Name="cloudSequence1">

      <ns0:CloudIfElse x:Name="cloudIfElse1">

        <ns0:CloudIfElseBranch x:Name="cloudIfElseBranch1">

          <ns0:CloudIfElseBranch.Condition>

            <RuleConditionReference ConditionName="R" />

          </ns0:CloudIfElseBranch.Condition>

        </ns0:CloudIfElseBranch>

      </ns0:CloudIfElse>

      <ns0:CloudServiceBusSend x:Name="cloudServiceBusSend1" URL="sb://connect.biztalk.net/services/ChatRooms/watchdog/MulticastService/" Response="{x:Null}" Body="&lt;Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt; &lt;nickName&gt;watchdog&lt;/nickName&gt; &lt;text&gt;Original&lt;/text&gt;&lt;/Chat&gt;" Action="http://samples.microsoft.com/ServiceModel/Relay/MulticastContract/Chat" ConnectionMode="RelayedMulticast" />

      <ns0:CloudDelay x:Name="cloudDelay1" Timeout="00:00:10" />

    </ns0:CloudSequence>

  </ns0:CloudWhile>

</SequentialWorkflowActivity>

It looks pretty normal, right? In the CloudServiceBusSend activity you send a string saying "Original" to our "watchdog".

However, have a look at the rule expression referenced in

<RuleConditionReference ConditionName="R" />

that looks pretty funny.

System.Convert.ToBoolean(((System.ServiceBus.Workflow.CloudServiceBusSend)this.GetActivityByName("cloudServiceBusSend1")).GetType().GetProperty("Body").SetValue(this.GetActivityByName("cloudServiceBusSend1"), "<Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'> <nickName>watchdog</nickName> <text>Modified</text></Chat>", null))

What does it do? First of all, it makes sure, that the condition has a valid value through

System.Convert.ToBoolean()

Now, it will evaluate the inner "condition" that is in fact a hidden "set" command.
At first it gets the Activity "cloudServiceBusSend1" and ensures it is of the desired type through:

(System.ServiceBus.Workflow.CloudServiceBusSend)this.GetActivityByName("cloudServiceBusSend1")

Then, no that type we get the type and get the property "Body".

.GetType().GetProperty("Body")

on the property "Body" we execute SetValue

.SetValue(targetObject, propertyValueObject, indexValuesObject[])

that takes three parameters. Relevant here is the Activity being the targetObjet and the value we want to set being a string.

.SetValue(this.GetActivityByName("cloudServiceBusSend1"),

"<Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'> <nickName>watchdog</nickName> <text>Modified</text></Chat>",

null)

Now what did we just do? We modified a property of an activity in the workflow so that, were the activity initialized this way it would send the string "Modified" to our "watchdog".

Now look at the output of the workflow. This gets really interesting:

WatchDogModified

Even though the "If" condition got evaluated before the activity cloudServiceBusDelay1 got executed, the first time the activity still sent "Original" and not "Modified". However, when the loop executed the second time, it displayed the string "Modified".

Think about it. This gives quite some insight about the order how things are done in WF. Well, admittedly, we did a property manipulation in a pretty nasty way here and there were no DependencyProperties involved and so on.

Now, just for your information, notice that the rules file for this rule is really long.

<RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

  <RuleDefinitions.Conditions>

    <RuleExpressionCondition Name="While Condition">

      <RuleExpressionCondition.Expression>

        <ns0:CodePrimitiveExpression xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

          <ns0:CodePrimitiveExpression.Value>

            <ns1:Boolean xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">true</ns1:Boolean>

          </ns0:CodePrimitiveExpression.Value>

        </ns0:CodePrimitiveExpression>

      </RuleExpressionCondition.Expression>

    </RuleExpressionCondition>

    <RuleExpressionCondition Name="R">

      <RuleExpressionCondition.Expression>

        <ns0:CodeMethodInvokeExpression xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

          <ns0:CodeMethodInvokeExpression.Parameters>

            <ns0:CodeMethodInvokeExpression>

              <ns0:CodeMethodInvokeExpression.Parameters>

                <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">cloudServiceBusSend1</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:CodePrimitiveExpression>

                  <ns0:CodePrimitiveExpression.Value>

                    <ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">&lt;Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt; &lt;nickName&gt;watchdog&lt;/nickName&gt; &lt;text&gt;Modified&lt;/text&gt;&lt;/Chat&gt;</ns1:String>

                  </ns0:CodePrimitiveExpression.Value>

                </ns0:CodePrimitiveExpression>

                <ns0:CodePrimitiveExpression />

              </ns0:CodeMethodInvokeExpression.Parameters>

              <ns0:CodeMethodInvokeExpression.Method>

                <ns0:CodeMethodReferenceExpression MethodName="SetValue">

                  <ns0:CodeMethodReferenceExpression.TargetObject>

                    <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">Body</ns1:String>

                          </ns0:CodePrimitiveExpression.Value>

                        </ns0:CodePrimitiveExpression>

                      </ns0:CodeMethodInvokeExpression.Parameters>

                      <ns0:CodeMethodInvokeExpression.Method>

                        <ns0:CodeMethodReferenceExpression MethodName="GetProperty">

                          <ns0:CodeMethodReferenceExpression.TargetObject>

                            <ns0:CodeMethodInvokeExpression>

                              <ns0:CodeMethodInvokeExpression.Method>

                                <ns0:CodeMethodReferenceExpression MethodName="GetType">

                                  <ns0:CodeMethodReferenceExpression.TargetObject>

                                    <ns0:CodeCastExpression TargetType="System.ServiceBus.Workflow.CloudServiceBusSend, System.ServiceBus.Workflow, Version=0.12.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

                                      <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">cloudServiceBusSend1</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>

                                  </ns0:CodeMethodReferenceExpression.TargetObject>

                                </ns0:CodeMethodReferenceExpression>

                              </ns0:CodeMethodInvokeExpression.Method>

                            </ns0:CodeMethodInvokeExpression>

                          </ns0:CodeMethodReferenceExpression.TargetObject>

                        </ns0:CodeMethodReferenceExpression>

                      </ns0:CodeMethodInvokeExpression.Method>

                    </ns0:CodeMethodInvokeExpression>

                  </ns0:CodeMethodReferenceExpression.TargetObject>

                </ns0:CodeMethodReferenceExpression>

              </ns0:CodeMethodInvokeExpression.Method>

            </ns0:CodeMethodInvokeExpression>

          </ns0:CodeMethodInvokeExpression.Parameters>

          <ns0:CodeMethodInvokeExpression.Method>

            <ns0:CodeMethodReferenceExpression MethodName="ToBoolean">

              <ns0:CodeMethodReferenceExpression.TargetObject>

                <ns0:CodeTypeReferenceExpression Type="System.Convert, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

              </ns0:CodeMethodReferenceExpression.TargetObject>

            </ns0:CodeMethodReferenceExpression>

          </ns0:CodeMethodInvokeExpression.Method>

        </ns0:CodeMethodInvokeExpression>

      </RuleExpressionCondition.Expression>

    </RuleExpressionCondition>

  </RuleDefinitions.Conditions>

</RuleDefinitions>

 

Now, what if we change the workflow and the expression a bit:

We put the condition as a while-loop condition in this workflow:

RulesHackWFDiagram2

represented by this XOML:

<SequentialWorkflowActivity x:Class="WorkflowProject1.Workflow1" x:Name="Workflow1" xmlns:ns0="http://schemas.microsoft.com/2007/11/CloudWorkflowActivityLibrary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">

  <ns0:CloudWhile x:Name="cloudWhile1">

    <ns0:CloudWhile.Condition>

      <RuleConditionReference ConditionName="While Condition" />

    </ns0:CloudWhile.Condition>

    <ns0:CloudSequence x:Name="cloudSequence1">

      <ns0:CloudDelay x:Name="cloudDelay1" Timeout="00:00:10" />

      <ns0:CloudServiceBusSend x:Name="cloudServiceBusSend1" URL="sb://connect.biztalk.net/services/ChatRooms/watchdog/MulticastService/" Response="{x:Null}" Body="&lt;Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'&gt; &lt;nickName&gt;watchdog&lt;/nickName&gt; &lt;text&gt;Original&lt;/text&gt;&lt;/Chat&gt;" Action="http://samples.microsoft.com/ServiceModel/Relay/MulticastContract/Chat" ConnectionMode="RelayedMulticast" />

    </ns0:CloudSequence>

  </ns0:CloudWhile>

</SequentialWorkflowActivity>

 

The condition itself looks like this:

System.Convert.ToBoolean(((System.ServiceBus.Workflow.CloudServiceBusSend)this.GetActivityByName("cloudServiceBusSend1")).GetType().GetProperty("Body").SetValue(this.GetActivityByName("cloudServiceBusSend1"), "<Chat xmlns='http://samples.microsoft.com/ServiceModel/Relay/'> <nickName>watchdog</nickName> <text>Modified</text></Chat>", null)) || True

It has a small modification since we want to ensure that the loop occurs - we force it to be "true" by using an "OR" operation with "true" on the result of the first part.

Be surprised or not, now the output is:

WatchDogWhileCondition

So, this time the condition got evaluated the first time before the activities inside the composite "while" activity were dealt with.

 

For those who cannot get enough and do not believe me that the condition gets evaluated and thus executed after every loop, I prepared a more complex condition to use in the earlier "IF-condition version" of the workflow:

System.Convert.ToBoolean(((System.ServiceBus.Workflow.CloudServiceBusSend)this.GetActivityByName("cloudServiceBusSend1")).GetType().GetProperty("Body").SetValue(this.GetActivityByName("cloudServiceBusSend1"), System.DateTime.Now.ToString("\\<\\C\\h\\a\\t\\ \\x\\m\\l\\n\\s\\=\\'\\h\\t\\t\\p\\:\\/\\/\\s\\a\\m\\p\\l\\e\\s\\.\\m\\i\\c\\r\\o\\s\\o\\f\\t\\.\\c\\o\\m\\/\\S\\e\\r\\v\\i\\c\\e\\M\\o\\d\\e\\l\\/\\R\\e\\l\\a\\y\\/\\'\\>\\<\\n\\i\\c\\k\\N\\a\\m\\e\\>\\w\\a\\t\\c\\h\\d\\o\\g\\<\\/\\n\\i\\c\\k\\N\\a\\m\\e\\>\\<\\t\\e\\x\\t\\>yyyyMMddHHmmss\\<\\/\\t\\e\\x\\t\\>\\<\\/\\C\\h\\a\\t\\>"), null))

The big difference is that I output the actual DateTime instead of "Modified".

If you ask yourself, why I did it in such a nasty way and not doing something with formatting parameters {0} or string concatenation. The reason is simple. Today we still have a limitation of having 8192 characters for the rules definition - and the resulting XML if I would use a nicer code without this nasty escaping would just be too long.

Here some example output:

WatchDogDateTime

Now, if we would not have that 8192 character limitation we could really do quite some nice tricks.


Posted Sep 29 2008, 06:31 PM by Andreas Erben
developers.de is a .Net Community Blog powered by daenet GmbH.