Sequential Workflow & LINQ

Berikut ini adalah rangkuman Bab 2 dari buku "Foundation of WF: An Introduction to Windows Workflow Foundation" karangan Brian R. Myers. Dalam bab tersebut diterangkan bagaimana membuat Sequential Workflow dan State Machine Workflow. Pada tulisan kali ini hanya akan saya bahas mengenai sequential workflow. Semua kode yang saya tulis di sini menggunakan C# tidak seperti pada buku yang menggunakan VB.NET. Dalam buku tersebut, akses ke database mengguakan ADO.NET, namun dalam tulisan ini saya gunakan LINQ to SQL. Penggunaan LINQ to SQL dimaksudkan untuk selain mempelajari WWF juga sekalian mempelajari bagaimana akses database menggunakan LINQ.

Sequential Workflow merupakan sebuah workflow yang berjalan secara berurutan (sequential). Sebelum atifitas pertama selesai, aktifitas berikutnya tidak akan pernah dijalankan dan seterusnya sampai aktifitas terakhir.

Pertama kita buat project "Sequential Workflow Console Application" seperti terlihat pada gambar di bawah.

Setelah project dibuat, kita mulai pada Workflow Diagram. Buka file Workflow1.cs (design mode) dan akan terlihat tampilan seperti pada gambar berikut.

Pada toolbox, drag Code Activity ke dalam Sequential Diagram. Klik Code Activity yang baru saja ditambahkan. Pada jendela Properties, ubah property name menjadi "step1" dan description menjadi "Step 1 in process". Setelah semuanya terisi, akan tampil seperti pada gambar berikut.

Terlihat pada gambar di atas terdapat tanda seru merah pada sudut kanan atas Code Activity. Klik pada tanda seru tersebut dan terlihat bahwa property "ExecuteCode" belum di-set. Klik kanan pada Code Activity dan klik "Generate Handlers".

Setelah itu akan dibangkitkan semua kode event handler secara otomatis. Pada Code Activity hanya ada satu event handler "ExecuteCode" sehingga hanya akan dibangkitkan event handler untuk event "ExecuteCode". Tambahkan kode untuk menampilkan pesan "Step 1" ke layar monitor seperti pada kode di bawah.

private void Step1_ExecuteCode(object sender, EventArgs e) 
{ 
    Console.WriteLine("Step 1");
}

Selanjutnya kembali ke design mode. Tambahkan satu Code Activity lagi setelah step1. Namai Code Activity tersebut dengan step1 dan description "Step 2 in process". Generate event handlers dan tulis kode untuk menulis pesan ke layar monitor seperti step sebelumnya. Hasil diagram dan kode akan tampil seperti pada gambar dan kode berikut.

 

private void Step1_ExecuteCode(object sender, EventArgs e)
{
    Console.WriteLine("Step 1");
}

private void Step2_ExecuteCode(object sender, EventArgs e)
{
    Console.WriteLine("Step 2");
}

Sekarang kita bisa jalankan program tersebut di atas. Pada jendela console akan tampil tulisan "Step 1" dan baris berikut nya tampil tulisan "Step 2" seperti gambar berikut.

Pada Sequential Workflow, process akan selalu dilakukan secara berurutan sesuai dengan alur yang dibuat pada workflow diagram. Jika alur aktifitas dibalik maka proses juga akan mengikuti perubakan yang terjadi (dibalik).

Sequential Workflow dengan Parameter

Workflow pada penerapan di dunia nyata sering kali membutuhkan data masukan dan keluaran baik berupa nomor dokumen, status dokumen, atau hasil perhitungan dari aktifitas tertentu. Untuk memenuhi kebutuhan tersebut, dibuat parameter untuk menyertakan data tersebut pada workflow. Terdapat dua parameter yaitu parameter hanya tulis (write only) yang digunakan untuk mengeset data workflow activity dan parameter baca (read only) sebagai output parameter setelah proses aktifitas selesai dilaksanakan.

Pada file Workflow1.cs tambahkan dua buah write only property integer dengan nama Input1 dan Input2 dan satu read only property integer dengan nama OutputValue.

private int InputValue1;
public int Input1
{
    set { InputValue1 = value; }
}

private int InputValue2;
public int Input2
{
    set { InputValue2 = value; }
}

private int OutputResult;
public int OutputValue
{
    get { return OutputResult; }
}

Selanjutnya pada event handler Step1_ExecuteCode tambahkan baris OutputResult = InputValue1 + InputValue2; setelah kode Console.WriteLine("Step 1"); seperti kode berikut.

private void Step1_ExecuteCode(object sender, EventArgs e)
{
    Console.WriteLine("Step 1");
    OutputResult = InputValue1 + InputValue2;
}

Pada program utama (Program.cs), buat Generic Dictionary untuk menampung parameter input. Perlu diingat bahwa nama parameter input harus sama persis (case sensitif) dengan nama parameter yang didefinisikan di class workflow (Workflow1.cs).

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("Input1", 45);
parameters.Add("Input2", 45);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1), parameters);
instance.Start();

Parameter output dapat dibaca hasilnya setelah workflow selesai dieksekusi. Tambahkan kode untuk menampilkan output parameter pada event handler WorkflowCompleted seperti kode berikut.

workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
{
    Console.WriteLine("Completed");
    Console.WriteLine("Output parameter: {0}", e.OutputParameters["OutputValue"]);
    waitHandle.Set();
};

Secara keseluruhan class workflow (Workflow1.cs) dan kode program utama (Program.cs) sebagai berikut.

Workflow1.cs

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace WorkflowConsoleApplication1
{
	public sealed partial class Workflow1: SequentialWorkflowActivity
	{
        private int InputValue1;
        public int Input1
        {
            set { InputValue1 = value; }
        }

        private int InputValue2;
        public int Input2
        {
            set { InputValue2 = value; }
        }

        private int OutputResult;
        public int OutputValue
        {
            get { return OutputResult; }
        }

		public Workflow1()
		{
			InitializeComponent();
		}

        private void Step1_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Step 1");
            OutputResult = InputValue1 + InputValue2;
        }

        private void Step2_ExecuteCode(object sender, EventArgs e)
        {
            Console.WriteLine("Step 2");
        }
	}
}

Program1.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

namespace WorkflowConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
                {
                    Console.WriteLine("Completed");
                    Console.WriteLine("Output parameter: {0}", e.OutputParameters["OutputValue"]);
                    waitHandle.Set();
                };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                Dictionary<string, object> parameters = new Dictionary<string, object>();
                parameters.Add("Input1", 45);
                parameters.Add("Input2", 45);

                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1), parameters);
                instance.Start();

                waitHandle.WaitOne();
            }
        }
    }
}

Contoh Aplikasi Purchase Order

Selanjutnya akan dibuat contoh aplikasi sederhana untuk melakukan input PurchaseOrder. Seperti pada project sebelumnya, kita gunakan Sequential Workflow Console Application project untuk contoh ini.

Pembuatan Database dan Tabel

Sebelum buat project, siapkan dulu database dan table yang digunakan. Buat database dengan nama "Purchasing" dan tabel "tblPurchaseOrders". Dalam contoh ini saya gunakan MS SQL 2005 Express. Script untuk membuat database dan tabel dapat dilihat pada query di bawah.

CREATE DATABASE [Purchasing]
GO
USE [Purchasing]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tblPurchaseOrders](
	[IntPurchaseOrderID] [int] IDENTITY(1,1) NOT NULL,
	[StrPurchaseOrderNumber] [varchar](50) NULL,
	[StrPartNumber] [varchar](50) NULL,
	[dtePurchaseDate] [smalldatetime] NULL,
	[dteExpectedDate] [smalldatetime] NULL,
	[StrBuyerLogin] [varchar](50) NULL,
	[StrBuyerName] [varchar](50) NULL,
	[IntQuantityOrdered] [int] NULL,
	[blnReceived] [bit] NULL CONSTRAINT [DF_tblPurchaseOrders_blnReceived]  DEFAULT ((0)),
	[IntQuantityReceived] [int] NULL,
	[dteReceivedDate] [smalldatetime] NULL,
 CONSTRAINT [PK_tblPurchaseOrders] PRIMARY KEY CLUSTERED 
(
	[IntPurchaseOrderID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

Setelah database dan tabel dibuat, langkah-langkah berikutnya.

Pembuatan Project Workflow

Seperti pada pembahasan sebelumnya, buat project Sequential Workflow Console Application dengan nama PurchaseOrderConsole. Setelah project dibuat, pada workflow designer tambahkan satu Code Activity dan generate event handler untuk activity tersebut.

Untuk dapat menerima input data dari luar dan mengembalikan hasilnya, dibutuhkan parameter-parameter. Kita buat beberapa parameter write only sebagi input data dan satu parameter read only sebagai output data. Buka class workflow (Workflow1.cs) dan tambahkan parameter seperti kode berikut.

#region Write only parameters
private string partNumber;
public string PartNumber
{
    set { partNumber = value; }
}

private DateTime purchaseDate;
public DateTime PurchaseDate
{
    set { purchaseDate = value; }
}

private DateTime expectedDate;
public DateTime ExpectedDate
{
    set { expectedDate = value; }
}

private string buyerLogin;
public string BuyerLogin
{
    set { buyerLogin = value; }
}

private string buyerName;
public string BuyerName
{
    set { buyerName = value; }
}

private int quantityOrdered;
public int QuantityOrdered
{
    set { quantityOrdered = value; }
}
#endregion

#region Read only parameters
private string purchaseOrderNumber;
public string PurchaseOrderNumber
{
    get { return purchaseOrderNumber; }
}
#endregion

Pada program utama (Program.cs) juga harus ditambahkan input data dari user dan dimasukkan ke class workflow mengguakan parameters seperti yang telah dijelaskan di atas.

Set input parameters

Dictionary<string, object> parameters = new Dictionary<string, object>();
Console.Write("Enter the Part Number:");
parameters.Add("PartNumber", Console.ReadLine());

Console.Write("Enter the Purchase Date:");
parameters.Add("PurchaseDate", Convert.ToDateTime(Console.ReadLine()));

Console.Write("Enter the Expected Date:");
parameters.Add("ExpectedDate", Convert.ToDateTime(Console.ReadLine()));

Console.Write("Enter the Buyer Login:");
parameters.Add("BuyerLogin", Console.ReadLine());

Console.Write("Enter the Buyer Name:");
parameters.Add("BuyerName", Console.ReadLine());

Console.Write("Enter the Quantity Ordered:");
parameters.Add("QuantityOrdered", Convert.ToInt32(Console.ReadLine()));

Karena input data dari user berupa string, maka untuk parameter dengan tipe data selain string harus dilakukan konversi terlebih dahulu ke tipe data yang sesuai. Dalam kode di atas, tipe data DateTime harus dikonversi menggunakan Convert.ToDateTime dan intenger menggunakan Convert.ToInt32.

Setelah workflow selesai dieksekusi, tampilkan hasil nomor purchase order ke layar.

workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
{
    Console.WriteLine("Purchase Order Number is: {0}", e.OutputParameters["PurchaseOrderNumber"]);
    waitHandle.Set(); 
};

Akses ke Database Menggunakan LINQ

Pada contoh ini akses ke database  tidak menggunakan ADO.NET secara langsung melainkan menggunakan LINQ to SQL. Pemilihan LINQ to SQL dikarenakan akses ke database menjadi lebih mudah dibandingkan mengguanakn ADO.NET secara langsung. Selain itu, LINQ menyediakan strong type query sehingga kesalahan yang timbul sudah bisa diketahui pada saat kompilasi.

Pertama, pada jendela Server Explore, tambahkan koneksi ke database yang telah dibuat. Masukkan nama server pada kotak Server name dan pilih database sesuai dengan database yang telah dibuat yaitu "Purchasing".

Setelah koneksi dibuat kemudian buat class LINQ to SQL. Pada jendela Solution Explorer, klik kanan pada project dan pilih Add -> New item. Pilih LINQ to SQL class dan beri nama Purchasing.dbml.

Buka file Purchasing.dbml, pada jendela Server Explorer drag tabel tblPurchaseOrders ke designer. Setelah itu pada designer akan tampil diagram tabel tblPurchaseOrders seperti pada gambar berikut.

Insert Data ke Database

Setelah class LINQ to SQL selesai dibuat selanjutnya adalah query untuk memasukkan data ke dalam database. Pada class workflow (Workflow1.cs), buat satu method AddPurchaseOrder() untuk melakukan insert data ke database. Semua data diambil dari write only parameter yang telah dibuat sebelumnya.

public void AddPurchaseOrder()
{
    PurchasingDataContext db = new PurchasingDataContext();
    tblPurchaseOrder order = new tblPurchaseOrder
    {
        StrPartNumber = partNumber,
        dtePurchaseDate = purchaseDate,
        dteExpectedDate = expectedDate,
        StrBuyerLogin = buyerLogin,
        StrBuyerName = buyerName,
        IntQuantityOrdered = quantityOrdered
    };
    db.tblPurchaseOrders.Add(order);
    db.SubmitChanges();
    purchaseOrderNumber = order.IntPurchaseOrderID.ToString().PadLeft(6, '0');
}

Terakhir tambahkan baris AddPurchaseOrder() pada event handler ExecuteCode

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    AddPurchaseOrder();
}

 

Program secara lengkah dapat dilihat pada kode-kode berikut.

Workflow.cs

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace PurchaseOrderConsole
{
	public sealed partial class Workflow1: SequentialWorkflowActivity
    {
        #region Write only parameters
        private string partNumber;
        public string PartNumber
        {
            set { partNumber = value; }
        }

        private DateTime purchaseDate;
        public DateTime PurchaseDate
        {
            set { purchaseDate = value; }
        }

        private DateTime expectedDate;
        public DateTime ExpectedDate
        {
            set { expectedDate = value; }
        }

        private string buyerLogin;
        public string BuyerLogin
        {
            set { buyerLogin = value; }
        }

        private string buyerName;
        public string BuyerName
        {
            set { buyerName = value; }
        }

        private int quantityOrdered;
        public int QuantityOrdered
        {
            set { quantityOrdered = value; }
        }
        #endregion

        #region Read only parameters
        private string purchaseOrderNumber;
        public string PurchaseOrderNumber
        {
            get { return purchaseOrderNumber; }
        }
        #endregion

		public Workflow1()
		{
			InitializeComponent();
		}

        public void AddPurchaseOrder()
        {
            PurchasingDataContext db = new PurchasingDataContext();
            tblPurchaseOrder order = new tblPurchaseOrder
            {
                StrPartNumber = partNumber,
                dtePurchaseDate = purchaseDate,
                dteExpectedDate = expectedDate,
                StrBuyerLogin = buyerLogin,
                StrBuyerName = buyerName,
                IntQuantityOrdered = quantityOrdered
            };
            db.tblPurchaseOrders.Add(order);
            db.SubmitChanges();
            purchaseOrderNumber = order.IntPurchaseOrderID.ToString().PadLeft(6, '0');
        }

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            AddPurchaseOrder();
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;

namespace PurchaseOrderConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            Console.Write("Enter the Part Number:");
            parameters.Add("PartNumber", Console.ReadLine());

            Console.Write("Enter the Purchase Date:");
            parameters.Add("PurchaseDate", Convert.ToDateTime(Console.ReadLine()));

            Console.Write("Enter the Expected Date:");
            parameters.Add("ExpectedDate", Convert.ToDateTime(Console.ReadLine()));

            Console.Write("Enter the Buyer Login:");
            parameters.Add("BuyerLogin", Console.ReadLine());

            Console.Write("Enter the Buyer Name:");
            parameters.Add("BuyerName", Console.ReadLine());

            Console.Write("Enter the Quantity Ordered:");
            parameters.Add("QuantityOrdered", Convert.ToInt32(Console.ReadLine()));

            using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
                {
                    Console.WriteLine("Purchase Order Number is: {0}", e.OutputParameters["PurchaseOrderNumber"]);
                    waitHandle.Set(); 
                };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(PurchaseOrderConsole.Workflow1), parameters);
                instance.Start();

                waitHandle.WaitOne();
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}
Share this post: | | | |
Published Thursday, November 01, 2007 4:57 PM by cahnom
Filed under: , ,

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems