Thursday, June 7, 2012

Best Practice Architecture with Workflow Foundation (WF)


This article demonstrates how WF can be consumed as business layer.

When to go for Workflow Foundation
1. For long-running business process.
2. Frequently Changing Business Logic or Rules.   
3. When need visual model/execution of the process.
Introduction
In This post I am going to demonstrate how you can architect enterprise application using WWF. You can find lots of learning stuff for Window Workflow Foundation but difficult to find is how to adjust and where to adjust it in architecture.
Architecture diagram
Have a look to architecture diagram, in this UI is interacting with business layer via WCF service. WF is used as part of business layer. You can also put WF service in front of WCF but then you will not know the entities of WCF in UI. As per best practice nothing should be directly exposed to UI layer, so it is not good idea to put WF as library in UI Layer. Generally people use WF for business rules, State persistence, Transaction or for some kind of time taking process so I would suggest to put it in Business layer. In my attached sample, for simplicity I am not involving entity framework and DB part. But off course you can add Entity framework with POCO classes. There are lot of articles are available on this so I don’t think you should face any kind of difficulty in implementation. In future I shall also come up with some sample on this.
Background
To implement this application, I am going to use the following technologies:
To implement this project person should have basic knowledge of .net technologies like C#, ASP.NET, WCF, WF.
Sample Proof of Concept
ADD blank solution name as ‘BestPracticeArchitectureWWF’.
Business Layer (C# Library)
Add C# library project to existing solution. Add following code for method in ‘Class1.cs’. This method will take one parameter and return it with some modification. This is the place where you can call to database by using entity framework.
public static string BusinessMethod(string parm1)
        {
            return parm1 + " Returned by Business layer.";
        }

Business Layer (WF Activity)
Add WF Activity library project to existing solution.
In Activity1.xaml add flowchart.
Drag Sequence to indicated place.
Select Sequence and declare 2 arguments in argument tab as shown in screen shot. One is to accept argument and another to return.
Now drag two more activities to Sequence as shown in above screen shot. One is Assign activity and another is Invoke Method from Primitive group panel. In Assign activity will assign Argument2 variable to Argument1 + “Modified in WF”. Invoke method is used to call business C# library. Add reference to C# library. To call library method set Target Type as class. Method name simply type ‘BusinessMethod’. No need to fill TargetObject(In TargetType and TargetObject one need to be filled). One strange thing is even though you work in c# project in Activities expression it takes only vb code.
Finally XAML of this activity will look like this. No need to modify anything in XAML but some time you rename something it fails to change in XAML then we need to look into it.
<Activity mc:Ignorable="sap" x:Class="ActivityLibrary1.Activity1" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:b="clr-namespace:BusinessClassLibrary1;assembly=BusinessClassLibrary1" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <x:Members>
    <x:Property Name="argument1" Type="InArgument(x:String)" />
    <x:Property Name="argument2" Type="OutArgument(x:String)" />
  </x:Members>
  <sap:VirtualizedContainerService.HintSize>654,676</sap:VirtualizedContainerService.HintSize>
  <mva:VisualBasic.Settings>Assembly references and imported namespaces for internal implementation</mva:VisualBasic.Settings>
  <Flowchart sad:XamlDebuggerXmlReader.FileName="C:\ManojWork\Article\BestPracticeArchitectureWWF\BestPracticeArchitectureWWF\ActivityLibrary1\Activity1.xaml" sap:VirtualizedContainerService.HintSize="614,636">
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">False</x:Boolean>
        <av:Point x:Key="ShapeLocation">270,2.5</av:Point>
        <av:Size x:Key="ShapeSize">60,75</av:Size>
        <av:PointCollection x:Key="ConnectorLocation">300,77.5 300,127.5</av:PointCollection>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <Flowchart.StartNode>
      <FlowStep x:Name="__ReferenceID0">
        <sap:WorkflowViewStateService.ViewState>
          <scg3:Dictionary x:TypeArguments="x:String, x:Object">
            <av:Point x:Key="ShapeLocation">200,127.5</av:Point>
            <av:Size x:Key="ShapeSize">200,51</av:Size>
          </scg3:Dictionary>
        </sap:WorkflowViewStateService.ViewState>
        <Sequence sap:VirtualizedContainerService.HintSize="264,352">
          <sap:WorkflowViewStateService.ViewState>
            <scg3:Dictionary x:TypeArguments="x:String, x:Object">
              <x:Boolean x:Key="IsExpanded">True</x:Boolean>
            </scg3:Dictionary>
          </sap:WorkflowViewStateService.ViewState>
          <Assign sap:VirtualizedContainerService.HintSize="242,58">
            <Assign.To>
              <OutArgument x:TypeArguments="x:String">[argument1]</OutArgument>
            </Assign.To>
            <Assign.Value>
              <InArgument x:TypeArguments="x:String">[argument1 + "Modified in WF"]</InArgument>
            </Assign.Value>
          </Assign>
          <InvokeMethod sap:VirtualizedContainerService.HintSize="242,130" MethodName="BusinessMethod" TargetType="b:Class1">
            <InvokeMethod.Result>
              <OutArgument x:TypeArguments="x:String">[argument2]</OutArgument>
            </InvokeMethod.Result>
            <InArgument x:TypeArguments="x:String">[argument1]</InArgument>
          </InvokeMethod>
        </Sequence>
      </FlowStep>
    </Flowchart.StartNode>
    <x:Reference>__ReferenceID0</x:Reference>
  </Flowchart>
</Activity>

Build the WF Activity and add reference to WCF Service.
Service Layer (WCF)
Add WCF service. In Service1.svc.cs add following code to call activity. Don’t forget to update contract in IService1.cs.
public string GetData(string value)
        {
            ActivityLibrary1.Activity1 ca = new ActivityLibrary1.Activity1();
            ca.argument1 = value.ToString();
            IDictionary<string, object> dr = new Dictionary<string, object>();
            dr.Add("argument1",value);
            var output = WorkflowInvoker.Invoke(ca,dr);
            string st = output["argument2"].ToString();
            return string.Format("You entered: {0}", st);
        }
This method takes string variable from UI and process it in WF Activity and in C# business library. Build WCF service.

UI(ASP.NET)

Add Asp.net project to existing solution. In place of ASP.NET you can use Silverlight/WPF project too. If you want to see how WCF can be called in Silverlight project see my other Article.
Add service reference to this project. Now come to Default.aspx to add some controls as per following code.
    <h2>
        WF Calling Sample
        <br />
        <asp:Label ID="Label2" runat="server" Text="Enter Your Name: ">
        </asp:Label><asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
        <br />
        <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
        <br />
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
    </h2>
Add click event for button. In code behind we are going to call WCF service.
protected void Button1_Click(object sender, EventArgs e)
        {
            Service1Client sr = new ServiceReference1.Service1Client();
            int i = 0;
            if (int.TryParse(TextBox1.Text, out i))
            {
                Label1.Text = sr.GetData(i);
            }
            else
                Label1.Text = "Please enter Number only.";
        }
Set Web Application as startup project and Default.aspx as Startup page. Run the application. Enter name and click Button to get modified from WF activity and C# Business library. Hope this idea will help you in designing applications.

Summary

In this Article you have learned how you can utilize WF activities in business layer. Hope this would be pretty simple example to elaborate architecture.
If this article helps you in designing/coding application don’t forget to hit voting option. Please comment your suggestions and improvements.
Happy Coding!!
Search Tags:
C#, MVVM, Silverlight, Silverlight4, Silverlight5, Window, WPF, WPF4, Business Layer, ASP.NET, WCF, WF, Workflow Foundation, WWF, Window Workflow Foundation, Architecture, Design, Best Practice, n-tier application, Activity, Sequence, flowchart, when to use WF, where to use WF