Showing posts with label Windows Workflow Foundation. Show all posts
Showing posts with label Windows Workflow Foundation. Show all posts

Tuesday, December 2, 2014

WWF - Passing arguements to Custom Activity inside a Sequence container in coded workflow

Why workflow?

According to me Workflow is kind of DSL which helps to separate the business logic from input output operations. It provides a domain which contains the objects which are involved in the system and the business logic / rules which can be easily modified. Its very much useful when the business rules are very much dynamic and putting developers for modifying the logic for ever is not practical. One of the workflow framework is Windows Workflow Foundation. We will be using WWF in rest of this post.

Why Windows Workflow Foundation?

The big advantage of WWF is its tooling. Microsoft is providing good tooling experience in their Visual Studio and the amazing factor is that the designer can be packaged in to our WPF applications easily. In other words we can easily give our users a graphical way to alter the program flow. Its really more than giving some configuration screens or files.

The WWF uses XAML to store the workflow definition. ie When we create new workflow , open it in visual studio editor, add activities (steps) and save, we get a .xaml file. We can open it in notepad and see how the logic is captured in the form of xml.

The operations what we can do with editor can be done using code as well. 

Simple coded workflow using C# .Net

As we saw above, workflow is a DSL which has all most all the features of normal programming language. It supports variables as storage locations and arguments as input/output mechanism. Refer this link to see how simple it is to invoke an workflow which have only one step/activity.

We can connect one or more activities using Sequence Activity just like how we can do from VisualStudio. Below is one example where we are connecting more than one activity using Sequence activity when we create coded workflows.

WorkflowInvoker.Invoke(new Sequence
            {
                Activities =
                        {
                                        new WriteLine {Text = "Workflow"},
                                        new WriteLine {Text = "World"}
                        }
            });

Custom Activity

WWF is really extensible. Apart from using its built-in activities we can have our own activities as well which are knows as custom activities. Development of custom activities are as simple as inheriting from Activity class. Similarly invocation of custom activity is too simple as calling the WorkflowInvoker.Invoke() method by passing the object of custom activity. It can accept arguments as well like built in activity. One example below.


    public class MyParameterizedActivity : CodeActivity
    {
        [RequiredArgument]
        public InArgument<string> Arg1 { get; set; }
        
        protected override void CacheMetadata(CodeActivityMetadata metadata)
        {
            base.CacheMetadata(metadata);
        }
        protected override void Execute(CodeActivityContext context)
        {
            string arg = context.GetValue(Arg1);
            Console.WriteLine("Param received.Value :" + arg);
        }
    }

Code to invoke goes below

WorkflowInvoker.Invoke(new MyParameterizedActivity(), new Dictionary<string, object>()
            {
                {"Arg1","argument value"}
            });

Passing arguments into Custom Activity in a coded workflow

Whatever we saw above are some background things. The real intention of this post comes now. We saw about Sequence and custom activities. If we host this custom activity inside a sequence activity whether we will get the argument value easily into the custom activity?

Below is the easiest way people think of. But this will not work

            //The below will compile but won't run as there is no arguement for Sequence Activity
            //The error message will be "'MyParameterizedActivity': Value for a required activity argument 'Arg1' was not supplied."
            Activity act = new Sequence
            {
                Activities =
                {   new WriteLine() {Text="Test" },
                    new MyParameterizedActivity()
                    {
                    }
                },
            };
            WorkflowInvoker.Invoke(act, new Dictionary<string, object>()
            {
                    {"Arg1","arg1 value"}
            });

Basically here nobody is telling that which argument /variable needs to be mapped with the Arg1 of custom activity 'MyParameterizedActivity'. So how to overcome this?

Here comes the DynamicActivity.Using that we can specify the mapping. Below is the code snippet. Hope its not much complicated :)

// There is a Property in DynamicActivity and that holds the value.
// That value is passed to MyParameterizedActivity
            InArgument<string> InArg1 = new InArgument<string>();
            DynamicActivity<int> wf = new DynamicActivity<int>
            {
                Properties =
                {
                    new DynamicActivityProperty
                    {
                        Name = "DynamicActivityArg1",
                        Type = typeof(InArgument<string>),
                        Value = InArg1
                    },
                },
                Implementation = () => new Sequence
                {
                    Activities =
                    {
                        new WriteLine() {Text ="Writing from first console activity "},
                        new MyParameterizedActivity()
                        {
                            Arg1 = new InArgument<string>((env)=> InArg1.Get(env))
                        },
                        new WriteLine() {Text ="Writing from second console activity "}
                    }
                }
            };

            WorkflowInvoker.Invoke(wf, new Dictionary<string, object>
                {
                    { "DynamicActivityArg1", "Value for arg 1" },
                });

Why this much complication to orchestrate / sequence 2 activities. Its because the WWF don't have support to pass value from one activity to other activity. So we need to use extra variable in between.

Getting return values from custom activity hosted inside DynamicActivity

We saw the things done to pass one variable to custom activity if its hosted as a step inside another activity. Below is the code for creating a custom activity which returns a value and  hosted inside DynamicActivity.
                

// Custom activity class which just multiplies and stored the result
public class MyMultiplyActivity : CodeActivity
    {
        [RequiredArgument]
        public InArgument<int> Operand1 { get; set; }
        [RequiredArgument]
        public InArgument<int> Operand2 { get; set; }

        [RequiredArgument]   
        public OutArgument<int> Product { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            int a = Operand1.Get(context);
            int b = context.GetValue(Operand2);
            int c = a * b;
            Product.Set(context, c);
            //Sum.Set(context, a + b);
        }
    }

Below is the code to invoke and print the result.


            // Dynamic activity accepts 2 arguments and pass to custom activity
            InArgument<int> Operand1 = new InArgument<int>();
            InArgument<int> Operand2 = new InArgument<int>();
            DynamicActivity<int> wf = new DynamicActivity<int>
            {
                Properties =
                {
                    new DynamicActivityProperty
                    {
                        Name = "Operand1",
                        Type = typeof(InArgument<int>),
                        Value = Operand1
                    },
                    new DynamicActivityProperty
                    {
                        Name = "Operand2",
                        Type = typeof(InArgument<int>),
                        Value = Operand2
                    }
                },
                Implementation = () => new Sequence
                {
                    Activities =
                    {
                        new MyMultiplyActivity()
                        {
                            Operand1 =new InArgument<int>((env)=>Operand1.Get(env)),
                            Operand2=new InArgument<int>((env)=>Operand2.Get(env)),
                            Product=new ArgumentReference<int>{ ArgumentName = "Result" },
                        },
                        new WriteLine() {Text="Console activity after custom activity" }
                    }
                }
            };
            int result = WorkflowInvoker.Invoke<int>(wf, new Dictionary<string, object>
                {
                    { "Operand1", 25 },
                    { "Operand2", 15 }
                });
            Console.WriteLine(result);


The 'Result' is kind of built in argument name which is mapped to the returned value of Invoke() method

We can think about more and more scenarios such as how can we return 2 values from the custom activity. Not about returning as class object, instead what about having more than 1 OutArguments and how to handle them from the invoker side?

Another interesting area is to use scripts instead of typed coding. If we are using VB / C# scripts instead of typed code we can avoid some amount of code in mapping variables to activity parameters.

Tuesday, August 5, 2014

XAML as DSL - Load and saving Windows workflow foundation workflows

Context

Windows Workflow Foundation has got better after MSFT introduced the WWF 4.0. I don't think, it was stable earlier. We are now using WWF, in one of our project. The main reasons are as follows
  • There is a complex business scenario (similar to recommendation algorithm) which business experts itself, is not clear how to do it. They want to try/test the algorithm in production data using a different tool. During that test they may need to alter the algorithm and change configurations to try various possibilities. Since the production data is spread across different countries and some don't allow the data to be carried out of the country, its not feasible to have a developer going with the business testing team and alter the algorithm.
  • During the above testing conducted by business experts, they want to try different algorithms (copy and modify original) on same data and compare the results.

Why XAML as DSL

This clearly points to the need of a Domain Specific Language to code the algorithm. As mentioned in one of my previous posts about DSL, either we can develop a DSL with its own parser and all or leverage existing languages to manipulate the entities. Creating own domain specific language with its own parser is not feasible as it takes more time.We could easily use PowerShell or Javascript as DSL, but the problem is the tooling. So we took XAML as DSL to code the algorithm. Decision factors below.
  • The business want to change the algorithm on the fly.
  • Business people are not good coders and its difficult for them to understand by looking at program as text. So they need a UI to edit algorithm (Implemented using DSL)
  • There is not much time to create tooling for existing languages such as PowerShell or Javascript

Advantages

XAML as DSL is being executed by the WWF engine. If we see the WWF as technology, it has many inbuilt features we can leverage to have it as DSL. Some items listed below.
  • It stores the workflow in clear XAML format
  • It comes with out of the box workflow designer control based on WPF. This workflow editor control can be easily hosted in our WPF application.

How to load and save WWF workflow

Lets come to the topic of saving and loading windows workflow foundation .xaml workflow files. As we saw earlier, the business want to edit the algorithm on the fly. We are keeping the xaml file in file system itself , even though the application has database.Below are the code snippets

Load the WWF workflow.xaml from file

Mainly we need to create the instance of WorkflowDesigner class and invoke it's Load() method with string argument. We can pass the file name as string.

Important thing here is, we don't need to read the file ourselves and pass the workflow XAML. For doing that there is another method.

            _workflowDesigner = new WorkflowDesigner();
            _workflowDesigner.Load(_fileName);
            //Code to Load _workflowDesigner related controls such as _workflowDesigner.View,_workflowDesigner.PropertyInspectorView 
            //into the WPF container

Save modified workflow to file.

Saving the workflow into file too simple. Just invoke the method. Main thing to make sure the permissions on the file. The user who is running the process should have write permission on the file.

            _workflowDesigner.Save(_fileName);

Load and save the workflow from database or any other source

There is Text property in WorkflowDesigner class. Assign the workflow xaml content in string form to the Text property and call Load() method to load the XAML string directly. Read the XAML string from database using traditional techniques and assign to Text property. We may get the modified XAML via the same property to save back to database.

            _workflowDesigner = new WorkflowDesigner();
            _workflowDesigner.Text = GetWorkflowFromDatabaseAsString();
            _workflowDesigner.Load();

References

Loading workflow in WorkflowDesigner
Happy coding.