Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

WF - ExternalDataExchange, Local Services ve CallExternalMethodActivity

Cuma, 25 Eylül 2009 16:23 by bsenyurt

Merhaba Arkadaşlar,

Artık yazın bittiği, okulların açıldığı, şehrin kalabalığının arttığı bu günlerde birde sağnak yağışlar işin içerisine girince, insan ister istemez tatilde üzerinden denize atladığı bir iskelede olmak istiyor. Artık o iskelenin etrafında fazla insan yok ve yağmur yüzünden tahtaların üzerinde gizemli bir şekilde akan su birikintileri var; diyerek yaptığımız duygusal girişimizin aslında yazımızın ilerleyen kısmı ile bir alakası yok. Wink Ama yine böyle yağmurlu bir günde cama vuran damlacıkları izlerken Workflow Foundation ile ilişkili düşündüğüm ve aklıma gelen bir konunun çözümünü sizlerle paylaşmak niyetindeyim.

İhtiyaç : Birden fazla aktivitenin aynı fonksiyonları ortaklaşa kullanabilmeleri nasıl sağlanır? Yani bir fonksiyonun birden fazla aktivite içerisinde kullanılması gerektiği durumlarda nasıl bir yol izleyebiliriz?

Çözüm : Böyle bir ihtiyaçta metodların kod içeriklerini tüm aktivitelerde örneğin CodeActivity bileşenleri içerisinde değerlendirebiliriz. Ama bu durumda merkezileştirilmemiş ve güncelleştirmeler sırasında kullanıldığı tüm aktivitelerde düşünülmesi gereken bir çözüm üretmiş oluruz. Aslında bir yol olarak söz konusu fonksiyonellikleri ortak bir kütüphane içerisinde toplayabilir ve yine CodeActivity' ler içerisinden çağırabiliriz. Lakin bu noktada değerlendirebileceğimiz başka bir çözüm daha vardır ve gerçekten araştırılmaya değerdir. Buna göre, Local Service olarak çalışma zamanına eklenmiş bir arayüzden yararlanılabilir ve ortak fonksiyonelliklerin bu arayüz üzerinden aktiviteler ile mesajlaşması sağlanabilir.

Burada kritik olan nokta ExternalDataExchange niteliği(attribute) ile işaretleniş bir arayüzü(Interface) implemente eden bir tipin fonksiyonelliklerinin, herhangibir aktivite tarafından kullanılabilir hale gelmesidir. Tabi bu kullanımı sağlamak için CallExternalMethodActivity aktivite tipinden yararlanılması gerekir. Geliştirici olarak çalışma şeklini iyice kavramak yakalayacağımız kavramlar açısından önemlidir. Öncelikle CallExternalMethodActivity bileşeninin bir aktivite tipi olarak harici bir metodu işaret edebileceğini göz önüne almalıyız. Bu durumda tasarım zamanında(Design Time), CallExternalMethodActivity bileşeninin çağıracağı harici metodun imzasını ve nerede olduğunu bilmesi gerekmektedir ki çalışma zamanında bu bilgilerden yararlanarak, içinde bulunduğu aktivite ile harici metod arasında bir mesajlaşma sağlayabilsin. Diğer yandan, tasarım zamanında IDE' nin CallExternalMethodActivity bileşenine kullanabileceği tipleri göstermesi, basit bir plug-in düzeneğine benzetilebilir. Söz konusu bileşen kullanabileceği tipleri bulmak konusunda, ExternalDataExchange niteliğini uygulamış interface tiplerini baz almaktadır. Buna göre arayüz tipinin çalışma zamanında gerçek işlevleri içeren bir uygulayıcısı da olmalıdır. Yani söz konusu arayüzü implemente eden bir tipten bahsediyoruz. Aktiviteler birden fazla CallExternalMethodActivity bileşeni içerebileceği gibi, birden fazla ExternalDataExchange nitelikli arayüz implementasyonunu da değerlendirebilir.

Artık konuyu örnekleyerek devam etmekte yarar olacağı kanısındayım. Örneğimizi Visual Studio 2008 ortamında ve .Net Framework 3.5 odaklı olarak geliştiriyor olacağız. İlk olarak System.Workflow.Activities assembly' ını referans eden bir Class Library projesi oluşturarak işe başlayalım. Bir sınıf kütüphanesi tasarladığımızdan, herhangibir Workflow projesinde kullanılabilir ve tek  merkezden güncellenebilir bir ürünümüz söz konusudur. Bu kütüphane, ExternalDataExchange nitelikli arayüz ve implementasyonlarını yapan tipleri barındırabilir ki örneğimizde bu amaçla aşağıdaki sınıf diagramında görülen tipler değerlendirilecektir.

Kod içeriğimiz;

using System;
using System.Workflow.Activities;

namespace CommonOperations
{
    // ICommonAccounting arayüz tipinin yerel servislerden(Local Service) birisi olduğu belirtilir
 [ExternalDataExchange]
 public interface ICommonAccounting
 {
        void IncreaseRate(double rate, int categoryId);
        void DecreaseRate(double rate, int categoryId);
 }

    // Yerel servis metodlarının uygulandığı yer
    public class CommonAccounting
        :ICommonAccounting
    {
        // Host uygulamanın değerlendirebileceği basit bir olay
        public event EventHandler<AccountingResultsEventArgs> OnCompleted;

        #region ICommonAccounting Members

        public void IncreaseRate(double rate, int categoryId)
        {
            Console.WriteLine("{0} kategorisindeki maaşlar % {1} oranında arttırılacak",categoryId.ToString(),rate.ToString());
            if (OnCompleted != null)
                OnCompleted(this, new AccountingResultsEventArgs { StepType = "Increase", Rate = rate,StepOk=true });
        }

        public void DecreaseRate(double rate, int categoryId)
        {
            Console.WriteLine("{0} kategorisindeki maaşlar % {1} oranında azaltılacak", categoryId.ToString(), rate.ToString());
            if (OnCompleted != null)
                OnCompleted(this, new AccountingResultsEventArgs { StepType = "Decrease", Rate = rate,StepOk=true });
        }

        #endregion
    }

    // OnCompleted olayı içerisinde kullanılan ve olay metoduna bilgi taşıyan sınıf
    public class AccountingResultsEventArgs
        : EventArgs
    {
        public string StepType { get; set; }
        public double Rate { get; set; }
        public bool StepOk { get; set; }
    }
}

ICommonAccounting isimli arayüze ExternalDataExchange niteliği uygulanmıştır. Arayüzümüzde, işlevleri bizim için şu aşamada çok önemli olmayan iki basit operasyon tanımlaması yer almaktadır. Diğer taraftan bu arayüzü implemente eden CommonAccounting tipi içerisinde operasyonların uygulaması yer almaktadır. CommonAccounting sınıf ayrıca, kendisini kullanan aktivitelere bilgi taşıyabilmekte kullanılabilecek bir olay bildirimi de(OnCompleted) içermektedir. Bu olay içerisinde kullanılan AccountingResultEventArgs isimli EventArgs türevli tip, çalışma zamanındaki CommonAccounting nesne örneğinden, OnCompleted olayına abone olan aktiviteye StepType, Rate ve StepOk gibi bazı yardımcı bilgiler döndürmektedir. IncreaseReate ve DecreaseRate metodları içerisinde, OnCompleted olayının yüklü olması halinde çalıştırılması işlemi gerçekleştirilmektedir.

Kişisel Not : Olayları daha net kavrayabilmek için eski bir makalemden faydalanabilirsiniz.

Artık bu sınıf kütüphanesini kullanacak basit bir Workflow projesi geliştirebiliriz. Bu amaçla bir Sequential Workflow Console Application projesi oluşturduğumuzu ve geliştirdiğimiz CommonOperations isimli sınıf kütüphanesini buraya referans ettiğimizi düşünelim. Boş bir Activity öğesini projeye ekledikten sonra içeriğini aşağıdaki gibi kodlayalım.

using System.Workflow.Activities;

namespace HostApp
{
    public partial class Activity1
        : SequenceActivity
    {
        public double IncreaseRate { get; set; }
        public double DecreaseRate { get; set; }
        public int CategoryId { get; set; }

        public Activity1()
        {
            InitializeComponent();
        }
    }
}

Burada tanımlanan IncreaseRate, DecreaseRate ve CategoryId özellikleri, CallExternalMethodActivity bileşenlerinin kullanacağı harici metodlara aktarılacak aktivite seviyesindeki değerleri taşımak üzere kullanılmaktadır. Şimdi tasarım zamanında, Activity1 içerisine örnek bir CallExternalMethodActivity bileşenini sürükleyerek devam edebiliriz. Bu işlemin ardından bileşenin InterfaceType özelliğinden yararlanarak hangi arayüzü kullanacağını aşağıdaki şekilden görüldüğü gibi seçebiliriz.

Görüldüğü gibi ICommonAccounting arayüzü otomatik olarak gelmiştir. Böylece hangi operasyonların kullanılabileceği, bu operasyonlara hangi parametrelerin verilmesi gerektiği bilinmektedir. Bizde akışımıza örnek olarak iki CallExternalMethodActivity bileşeni ekleyip özelliklerini aşağıdaki gibi ayarlayarak devam edebiliriz.

callExternalMethodActivity1 bileşeninin özellikleri;

callExternalMethodActivity2 bileşeninin özellikleri;

Görüldüğü gibi her iki bileşen için ICommonAccounting arayüzü seçilmiş, buna göre sırasıyla IncreaseRate ve DecreaseRate operasyonlarının kullanılacağı belirtilmiştir. Ayrıca söz konuzu operasyonların parametreleri, otomatik olarak özellikler penceresine gelmiştir(rate ve categoryId). Bu özelliklerde aslında, Activity1 tipi içerisinde tanımlanmış olan IncreaseRate,DecreaseRate ve CategoryId özelliklerini işaret edecek şekilde bizim tarafımızdan ayarlanmaktadır. Dolayısıyla WF çalışma zamanında, Activity1 içerisindeki ilgili özelliklere atanabilecek olan değerler, CallExternalMethodActivity bileşenleri ile CommonAccounting nesnesinin ilgili metodlarına gönderilerek işlenebilecektir. Eğer WF Çalışma zamanını host eden sınıf, CommonAccounting tarafından tanımlanmış OnCompleted olayınıda yüklerse, CallExternalMethodActivity bileşenlerinin çalıştırdığı harici metodlardan bazı bilgileri kendi ortamına alarak değerlendirebilecektir(AccountingResultEventArgs yardımıyla). Bu yapı için WF çalışma zamanına özel bazı kodlamaların yapılmasıda gerekmektedir. İşte uygulama kodlarımız;

using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using CommonOperations;

namespace HostApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                #region Yerel Servisi Bildirme İşlemi

                // Yerel servisler için eklenmesi gereken servistir
                ExternalDataExchangeService service = new ExternalDataExchangeService();
                // ExternalDataExchangeService örneği Workflow çalışma zamanına eklenir
                workflowRuntime.AddService(service);

                // ExternalMetadaExchange nitelikli interface tipini implemente eden asıl nesne örneklenir
                CommonAccounting accounter = new CommonAccounting();
                // HostApp uygulamasının ele alacağı OnCompleted olayı yüklenir ve anonymous method yardımıyla değerlendirilir.
                accounter.OnCompleted += delegate(object sender, AccountingResultsEventArgs e)
                {
                    // Örnek metodlardan gelen sonuçlar listelenir.
                    Console.WriteLine("\n\tİşlem tipi {0}\n\tRate {1}\n\tİşlem sonucu {2}", e.StepType, e.Rate.ToString(),e.StepOk.ToString());
                };

                //accounter isimli ExternalMetadaExchange nitelikli interface tipini implemente eden asıl nesne örneği, ExternalDataExchangeService örneğine eklenir.
                service.AddService(accounter);

                #endregion

                AutoResetEvent waitHandle = new AutoResetEvent(false);
                // Workflow tamamlandığında devreye giren olay metodu
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {
                    waitHandle.Set();
                };
                // Exception gibi nedenlerle Workflow sonlandığında devreye giren olay metodu
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                // Yerel servis içerisindeki metodların kullanacağı parametrelere işaret eden özellikler yüklenir.
                Dictionary<string, object> parameters = new Dictionary<string, object>
                {
                    {"IncreaseRate",1.12},
                    {"DecreaseRate",2.25},
                    {"CategoryId",1}
                };

                // Aktivite nesnesi örneklenir, özellikleri için ilk değerleri yüklenir.
                WorkflowInstance mathActivity = workflowRuntime.CreateWorkflow(typeof(HostApp.Activity1),parameters);
                // Aktivite başlatılır
                mathActivity.Start();
                // Asenkron işleyişi ispat etmek için
                Console.WriteLine("İşlemler başladı");
                // İşlemler tamamlanmadıysa bekle
                waitHandle.WaitOne();
            }
        }
    }
}

Görüldüğü üzere Local Service' in tanımlanmasını takiben, ExternalDataExchange nitelikli tipe ait nesne örneklenmiş ve yerel servise bildirilmiştir. Ayrıca CommonAccounting nesnesinin OnCompleted olayı yüklenmiş ve Program' ın bu olaya abone olması sağlanmıştır. Activity1 nesnesine ait özellikleri set etmek için Dictionary<string,object> koleksiyonundan yararlanılmış ve son olarak aktivitemiz başlatılmıştır. İşte çalışma zamanı sonuçları.

Evet...Önce belirli oranda arttırım yapıp sonra azaltım yapmak son derece saçma gözükmektedir Undecided Ancak yakalamamız gereken nokta elbetteki bu değildir. Önemli olan, bir aktivite' nin kendi sınırları dışındaki fonksiyonellikleri kullanabilmek için yerel servislerden nasıl yararlanıldığı ve bunun için ExternalDataExchange niteliğinin nasıl değerlendirildiğidir. Üstelik bu değerlendirme, WF tasarım zamanı içinde önem arz eder. Tekrardan görüşünceye dek hepinize mutlu  günler dilerim. 

UsingExternalCode.rar (50,98 kb)

Tags:  
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WF - XAML Bazlı Workflow Örnekleri Geliştirmek

Çarşamba, 6 Mayıs 2009 20:23 by bsenyurt

Merhaba Arkadaşlar,

Geçtiğimiz günlerde Workflow 4.0 ile ilişkili araştırmalarıma devam ederken, özellikle dekleratif olarak tanımlanabilen WF servislerindeki önemli bir noktayı farkettim. Bu, aynı zamanda WF 4.0 ile birlikte gelen en önemli yenilikler arasındaydı. (Hatta WF motorunun-Engine- değişmesi veya temel aktivite kütüphanesinde(Base Activity Library), ata tip olarak WorkflowElement isimli yeni bir sınıfın getirilmesi kadar önemliydi) Bir workflow örneğinin sadece XAML içeriğinden oluşacak şekilde koda ihtiyaç duymadan tasarlanabilmesi(design), derlenebilmesi(Compile) ve gerektiğinde çalışma zamanında basit bir notepad uygulaması ile değiştirilerek güncellenebilmesi...Burada özellikle derleme konusu son derece dikkat çekici. Nedeni mi?

Nedeni araştırmak için elbette basit bir senaryo üzerinden ilerlemem gerekiyordu. Bu yüzden dün gece biraz geç bir vakittede olsa üşenmeden kodlamaya başladım. Senaryoya göre, .Net 3.5 açısından olaya bakıp, sadece XAML içeriğinden oluşacak bir Workflow örneğini oluşturmak ve çalıştırmak istiyordum. Bu sebeple öncelikle, örnek bir aktivite tipi geliştirmeye karar verdim. ProductOrderActivity isimli aktivite bileşenini, ayrı bir Workflow Activity Library projesi içerisinde aşağıdaki sınıf diagramında olduğu gibi tasarladım.

Kod içeriği

using System;
using System.ComponentModel;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;

namespace NorthwindActivities
{
 public class ProductOrderActivity
        : SequenceActivity
 {
        public static DependencyProperty ProductNumberProperty = DependencyProperty.Register("ProductNumber", typeof(string), typeof(ProductOrderActivity));
        public static DependencyProperty PartCountProperty = DependencyProperty.Register("PartCount", typeof(int), typeof(ProductOrderActivity));
        public static DependencyProperty OrderDateProperty = DependencyProperty.Register("OrderDate", typeof(DateTime), typeof(ProductOrderActivity));

        [Description("Ürün Numarası")]
        [Category("Sipariş Parametreleri")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string ProductNumber
        {
            get
            {
                return ((string)(base.GetValue(ProductOrderActivity.ProductNumberProperty)));
            }
            set
            {
                base.SetValue(ProductOrderActivity.ProductNumberProperty, value);
            }
        }

        [Description("Parça sayısı")]
        [Category("Sipariş Parametreleri")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public int PartCount
        {
            get
            {
                return ((int)(base.GetValue(ProductOrderActivity.PartCountProperty)));
            }
            set
            {
                base.SetValue(ProductOrderActivity.PartCountProperty, value);
            }
        }

        [Description("Sipariş Tarihi")]
        [Category("Sipariş Parametreleri")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public DateTime OrderDate
        {
            get
            {
                return Convert.ToDateTime(base.GetValue(ProductOrderActivity.OrderDateProperty));
            }
            set
            {
                base.SetValue(ProductOrderActivity.OrderDateProperty, value);
            }
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Console.WriteLine("{0} numaralı üründen {1} adet sipariş işlemi...",ProductNumber,PartCount.ToString());
            Console.WriteLine("{0}  tarihine kadar sipariş edilmelidir.",OrderDate.ToShortDateString());
            return base.Execute(executionContext);
        }
 }
}

Aktivite aslında basit olarak bir ürünün belirli bir tarihe kadar, istenen miktarda sipariş edilmesi adımını yürüten bir modele sahipti. Tabiki sembolik olarak. Bu sebepten ProductNumber, PartCount ve OrderDate isimli özellikleri bulunmaktaydı. Bu özellikler,, aktivitenin başka aktivitiler içermesi veya başka aktivitelere bağlanması(Binding) gibi ihtiyaçlara sahip olabileceğinden DependencyProperty tipi ile ilişkilendirilmiştim. Özellikler, tasarım zamanında Visual Studio IDE' si tarafından değerlendirileceğinden, Description, Category, Browsable ve DesignerSerializationVisibility gibi niteliklerlede sahipti. Aktivite icra edildiğinde ise ezilen(override) Exeute metodu içeriği çalıştırılmaktadır. Bu kısımda işin modeline göre bir takım işlemler yapılması gerekmekte. Ben sembolik olarak sadece ekrana bazı bilgiler yazdırmayı hedefledim.

Şimdi gelelim bu aktiviteyi kullanacağımız örnek Workflow uygulamasına. Bu amaçla testleri kolayca yapabileceğim bir Sequential Workflow Console Application projesi oluşturdum. Projenin, ProductOrderActivity aktivitesini kullanabilmesi içinde, tanımlandığı NorthwindActivities kütüphanesini referans ettim.

Artık ön hazırlıklar tamamlanmıştı. Sırada XAML bazlı Sequential Activity öğesinin eklenmesi vardı. Yanlız burada yapacağımız seçimin önemli olduğunu vurgulamak isterim. Nitekim amacımız kod içermeyen ve XAML içeriğine sahip bir Workflow örneği geliştirmek olduğundan, proje öğelerinden Sequential Workflow (with no code) tipini seçmemiz gerekiyor. Tabi eğer State Machine Workflow tipinden bir proje söz konusuysa, State Machine Workflow(with no code) öğesinin seçmemiz gerekiyor.

Bunun sonucunda projeye aşağıdaki şekilde görülen ProductOrderFlow isimli XOML uzantılı bir öğenin eklendiği görülür.

Ancak görüldüğü gibi bu oluşum sırasında xoml uzantılı içerik dışında cs uzantılı bir kod içeriğide üretilmektedir. Şunu hemen hatırlatayım. Amacımız kesin olarak kod dosyasından bağımsız bir Workflow örneği oluşturmaktır. Peki bunun için ne yapmalıyız? Aslında şu an için çözüm son derece basit. cs uzantılı dosya silinir Laughing Bende aynen böyle yaptım. Tabi şu anda Workflow içerisinde herhangibir aktivite kullanılmamakta. Ancak dikkat edilmesi gereken önemli bir nokta daha var. Bu Workflow için bir kod bloğu olmadığından, içeride kullanacağımız aktivitelerin Codebehind dosyası içerisine kod atmayacak şekilde kullanılmaları gerekmekte. Söz gelimi bir CodeActivity bileşenini kullanmak istediğimizde, bu bileşenin çalıştırılması sonucu devreye girecek metodun, cs kod dosyası içerisinde yer alması gerekmektedir. Oysaki şu anki teorimize göre böyle bir dosya bulunmamaktadır(olmamalıdır). Buda bizi, özel aktivite tiplerinin yazılmasına itmektedir. Ama elbetteki kod dosyasına ihtiyaç duymayan bazı aktivite bileşenleri burada ele alınabilir. Örneğin DelayActivity aktivitesi. Bu bilgilerden yola çıkarak ProductOrderFlow.xoml içeriğini tasarım zamanında aşağıdaki gibi oluşturdum.

Şekildende görüldüğü gibi, Workflow içerisinde önce delayActivity bileşeni ve peşinden yazdığım productOrderActivity bileşeni çalıştırılmakta. productOrderActivity1 bileşeninin OrderDate, PartCount ve ProductNumber isimli özelliklerine ise sembolik değerler aktarılmış durumdadır. Şimdi xoml içeriğini XML Editor yardımıyla açarsak, aşağıdaki içeriğin oluşturulduğunu görürüz.

<SequentialWorkflowActivity x:Class="NorthwindActivities.ProductOrderFlow" x:Name="ProductOrderFlow" xmlns:ns0="clr-namespace:NorthwindActivities;Assembly=NorthwindActivities, 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">
 <DelayActivity TimeoutDuration="00:00:05" x:Name="delayActivity1" />
 <ns0:ProductOrderActivity x:Name="productOrderActivity1" ProductNumber="PART-1001" PartCount="100" OrderDate="2009-05-07T00:00:00.0000000" />
</SequentialWorkflowActivity>

Görüldüğü gibi Workflow içerisinde aktivitilerin tamamı, XAML içeriği olarak oluşturulmuştur. Bu, zihinlerde yeni ufuklar açacak kadar önemli bir ayrıntır. Çünkü, istenirse bu içerikte yer alan elementlerin yerleri değiştirilerek akışın şekline müdahele edilebilir(Koda girmeye gerek kalmadan). Yada başka elementler basit bir notepad programı yardımıyla içeriğe dahil edilip akışa yeni adımların eklenmesi sağlanabilir. Hatta bu içerik belki bir depolama ortamında saklanarak farklı görsel uygulamaların bu akışları ele alabilmesi, değiştirebilmesi sağlanabilir(Oslo, Quadrant kavramına gitmeye çalıştığımı sanıyorumki anlamışsınızdır)

Sonrasında aşırı heyecan yapmaya gerek olmadığını farkedip devam etmeye karar verdim. Bu nedenle projeyi derleyerek yoluma devam etmek istedim. Ancak oldukça ilginç bir durumla karşılaştım. Proje içerisinde birden ProductOrderFlow.xoml.cs isimli kod dosyası ortaya çıktı.Sealed Gecenin karanlığında sanki bir korku filminde yaşanan gerilimi hissetmiştim. Ensemden soğuk bir ter damlası ilerlerken, bu hortlağın nereden çıktığını düşünüyordum. Aslında bu son derece doğaldı. Nitekim proje derlendiğinde, xoml dosyası da hesaba katıldığından, cs dosyası otomatik olarak üretilmekteydi. Bu tabiki istediğim bir durum değildi. Bu nedenle ProductOrderFlow.xoml dosyasının Build Action özelliğinin değerini None olarak belirlemek yeterliydi. Tabiki sonrasında(öncesinde) cs dosyasını silmeyi unutmamak da gerekiyordu.

Evettt...Herşey hazır gibi. Mi acaba? Aslında unuttuğum önemli bir nokta var. Söz konusu Workflow nasıl çalıştırılacak? Nitekim, build işlemi sırasında ProductOrderFlow.xoml içeriğini devre dışı bıraktığımızdan, bunun çalışma zamanında bir şekilde yükleniyor olması gerekiyor. Ancak derlenmiş kod içerisinde bu Workflow' a ait bir tip tanımlamasıda yer almadığından(çünkü Build Action=None olarak belirlendi) çalışma zamanında xoml dosyasının içeriğinin ele alınması gerekmekte. Bunu sağlamak için tek yapılması gereken çalışma zamanı kodlamasını aşağıdaki gibi değiştirmek.

using System;
using System.Threading;
using System.Workflow.Runtime;
using System.Xml;

namespace NorthwindActivities
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {
                    waitHandle.Set();
                    Console.WriteLine("İşlemler tamamlandı");
                };
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };

                XmlReader reader = XmlReader.Create("..\\..\\ProductOrderFlow.xoml");
                WorkflowInstance instance = workflowRuntime.CreateWorkflow(
                    reader
                    );
                instance.Start();

                waitHandle.WaitOne();
            }
        }
    }
}

Koddanda görüldüğü gibi yapılması gereken, xoml içeriğini XmlReader nesnesi yardımıyla ortama almak ve WorkflowInstance örneğinin oluşturulması sırasında CreateWorkflow metoduna parametre olarak vermektir. Uygulamayı bu haliyle çalıştırdığımda aşağıdaki sonucu elde ettim.

Sanıyorumki ben dahil herkes, uygulamanın çalışmasını bekliyordu. Ancak yukarıda görüldüğü gibi bir istisna(Exception) aldım. Karabasan devam ediyordu sanki. Çözümü bulmam biraz zamanımı aldı. Aslında problem, xoml içeriğinde yer alan

x:Class="NorthwindActivities.ProductOrderFlow"

bildirimiydi. Çalışma zamanının kızması son derece doğaldı. Hak vermem gerekiyordu. Hata mesajındanda anlaşılacağı üzere bir doğrulama(Validation) sorunu vardı. Bunun kaynadğında ise ProductOrderFlow tipi yer almakta. Derken tepemde bir ampül yanıverdi.Wink cs dosyasını çıkarmış ve xoml içeriğini uygulamaya dahil etmemiştim. Dolayısıyla söz konusu tip zaten yoktu ve çalışma zamanı, akışı doğrulamaya çalışırken tam bu noktada çatlıyordu. Neden böyle olmuştu peki? Tabiki Visual Studio ortamında sadece XAML içeriğinden oluşan bir akış geliştirme desteği bulunmamaktaydı ve ben kod parçalı oluşturulan akışın üzerinde değişiklikler yapıyordum. Yani varsayılan modele karşı gelmiştim. Haliyle xoml içeriğini aşağıdaki gibi değiştirmem gerekti.

<SequentialWorkflowActivity x:Name="ProductOrderFlow" xmlns:ns0="clr-namespace:NorthwindActivities;Assembly=NorthwindActivities, 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">
 <DelayActivity TimeoutDuration="00:00:05" x:Name="delayActivity1" />
 <ns0:ProductOrderActivity x:Name="productOrderActivity1" ProductNumber="PART-1001" PartCount="100" OrderDate="2009-05-07T00:00:00.0000000" />
</SequentialWorkflowActivity>

Artık tekrar testi yapabilirdim. Programı çalıştırdığımda aşağıdaki sonuçla karşılaştım.

Nihayet Smile 

Artık xoml içeriği ile biraz oynayabilirdim. Bu amaçla xoml dosyasını notepad ile açtım ve aşağıdaki hale getirdim.

Görüldüğü gibi productOrderActivity2 isimli yeni bir bileşeni akış içerisine dahil edip özelliklerine sembolik değerler atadım. Bundan sonra program kodunu derlemeden çalıştırdığımdaysa aşağıdaki ekran görüntüsü ile karşılaştım.

Görüldüğü gibi yeni eklenen bileşende başarılı bir şekilde çalıştırıldı. Sonuç olarak; bir workflow örneğinin koddan bağımsız olacak şekilde tasarlanabilmesi, XAML içeriğinin basit bir editor yardımıyla değiştirilip akışın güncellenebilmesi sağlanabilmektedir. Burada önemli olan noktalardan birisi, söz konusu xoml dosyalarının, çalışma zamanında değerlendirilip yürütülmeleridir.

Şimdi şöyle bir senaryoyu göz önüne alalım. Buradaki gibi tamamen XAML bazlı akışların bir depoda saklandığını düşünelim. Örneğin SQL sunucusu üzerinde veya bir x veri depolama sisteminde. Sonrasında ise, bu akışları kullanan(içeren) süreçler ve programların görsel bir araç yardımıyla tasarlanabildiğini ve değiştirilebildiğini göz önüne alalım. Hatta bu akışların istenirse export edilip farklı uygulama alanlarına import edilebildiklerini farz edelim...Derken zaten Microsoft' un gitmek istediği noktada yer alan bir kaç temel ihtiyaçtan bir kısmını özetlemiş oluyoruz.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

UsingXAML.rar (43,63 kb)

Tags:   ,
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WF için SQL Persistence Hizmetinin Kullanımı

Pazartesi, 13 Nisan 2009 07:44 by bsenyurt

Workflow örneklerine ait aktivitelerde dikkat edilmesi gereken noktalardan biriside uzun süreli çalışmalara neden olacak akışlarında söz konusu olmasıdır. Bu noktada SQL tabanlı olarak, Workflow örneklerinin kalıcı olarak saklanması yaygın olarak kullanılan tekniklerden birisidir. Bu konuyu .Net Framework 3.5 sürümü üzerinde detaylı olarak inceledim ve konuyla ilgili olaraktan şu makaleyi yazdım.

Bu arada WF 4.0 içerisinde özellikle Persistence işlemleri için getirilen bir aktivite tipi olduğunuda belirtmek isterim. Diğer taraftan Dublin kod adlı Windows Application Server ile birlikte IIS üzerine gelen eklentiler ile, WF Servislerinin, persistence, tracing, monitoring,throttling gibi önemli kıstaslarını yönetmemiz daha kolay ve güçlü bir şekilde olacak. (Bu konularla ilişkili olaraktan ilerleyen zamanlarda bir kaç görsel ders yayınlıyor olacağım.

Tags:  
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

SQL Persistence Hizmeti

Cuma, 6 Mart 2009 04:41 by bsenyurt

Workflow Foundation yardımıyla kod akışlarının modellenebilmesi ve herhangibir .Net uygulaması içerisinden host edilerek çalıştırılabilmesi mümkündür. Bu kavram işin içerisine servisler girdiğinde çok daha genişlemektedir. Nitekim servisler yardımıyla Workflow örneklerinin host edildikleri uygulama dışındaki ortamlar ile haberleşebilmeleri mümkün olmaktadır. Hatta servislerin kendi içlerinde Workflow aktivitilerini kullanabilmeleri ve böylece belirli kod akışlarını yürütebilmeleride mümkündür. Bu cümleden sonra durup düşünüldüğünde Workflow Foundation kavramının akışları, platform bağımsız ortamlara taşıyabileceği sonucuda ortaya çıkmaktadır. Diğer taraftan Workflow uygulamaları sadece dış ortamlar ile haberleşmek için servislerden yararlanmazlar. İlaveten, Workflow çalışma zamanını(Runtime) ilgilendiren ve özellikle aktivitilerin dayanıklı olarak saklanmasını(Durable Persistence), çalışma hayatlarının izlenmesini(Tracking), adımlar arası geçişlerin özel olarak planlanmasını(Scheduling) sağlayan ve .Net Framework içerisinde önceden tanımlanmış servislerde söz konusudur.

NOT : Workflow servisleri, çalışma zamanına eklenerek workflow örneklerine yeni kabiliyetler kazandırılmasını sağlamak amacıyla kullanılırlar. Örneğin PersistenceServices ve TrackingServices, SQL veritabanlarını varsayılan olarak kullanan çalışma zamanı servisleridir. PersistenceServices ile workflow' ların kalıcı olarak saklanabilmesi sağlanabilir. TrackingServices yardımıylada çalışma zamanı workflow örneklerinin izlenebilmesi mümkündür. Bir başka örnek olarak workflow çalışma zamanı davranışlarını değiştirmemizi sağlayan Manual Scheduler servisi göz önüne alınabilir. Bu servislerin bir kısmının kullanılabilmesi için, çalışma zamanına bilinçli olarak eklenmeleri gerekmektedir. Bir başka deyişle etkinleştirilmeleri gerekir.

İşte bu yazımızdaki konumuz, uzun süreli çalışma ihtimali olan bir Workflow' un belirli koşullarda kalıcı olarak fiziki bir ortamda saklanmasının, SQL Persistence Service yardımıyla nasıl gerçekleştirilebileceğidir. SQL Persistence Service sayesinde, bir Workflow' un faaliyetsiz kalması(Idle) halinde bellek yerine, tablo bazlı bir ortamda saklanabilmesi ve bu durum sona erdiğinde söz konusu depolama alanından tekrar ayağa kaldırılarak çalışmaya devam etmesi mümkün olmaktadır. Konuyu daha kolay kavrayabilmek adına ilk önce Workflow çalışma zamanının kendi ve yönettiği WF örnekleri ile ilişkili yaşam döngüsünü incelemekte yarar vardır. Bu yaşam döngüsünün kolayca ele alınabilmesi için WorkflowRuntime sınıfı içerisine çeşitli olaylar eklenmiştir.

Bilindiği üzere WorkflowRuntime sınıfı çalışma zamanında WF örneklerinin yönetiminden sorumludur. Bu yönetim işlemi sırasında WorkflowRuntime sınıfı üzerinden ele alınabilecek 14 farklı olay metodu vardır. Söz konusu olayların bir kısmı sadece çalışma zamanını ilgilendirirken, bir kısmıda WF örneklerinin yaşam döngülerine(LifeCycle) adanmıştır. Buna göre ServicesExceptionNotHandled, Started ve Stopped olayları Workflow çalışma zamanı olayları olarak düşünülebilir.

Workflow Çalışma Zamanı Olayları
ServicesExceptionNotHandled Workflow çalışma zamanı servislerinden herhangibirinde kontrol altına alınmamış bir istisna(Exception) oluştuğunda devreye giren olaydır.
Started Workflow çalışma zamanı motoru, üzerine eklenmiş servisler ile başarılı bir şekilde başlatıldığında devreye girer. Burada çalışma zamanına eklenen servislerin başarılı bir şekilde başlatıldıklarına dair bir bilgilendirme yapması söz konusudur.
Stopped Started olayına benzer olaraktan, WF çalışma zamanı motorunun, kendi üzerinde yer alan ve çalışmakta olan tüm servislerin başarılı bir şekilde durdurulması sonrasında tetiklenir. Servisler başarılı bir şekilde durdurulduklarına dair WF çalışma zamanı motoruna bilgilendirmede bulunurlar.

Aşağıdaki tabloda açıklamaları verilmiş olan olaylar ise, WF çalışma zamanının yönettiği Workflow örneklerinin durumlarının(State) değiştiği hallerde tetiklenmektedir.

Workflow Örneğine Adanmış Olaylar
WorkflowAborted Workflow örneği devre dışı bırakıldığında tetiklenir. Özellikle Persistence servisi kullanıldığında önem kazınır. Nitekim devre dışı bırakılan servisin kalıcı olarak saklanması ve tekrar kaldığı yerden ayağa kaldırılması(Resume) mümkün olabilmektedir.
WorkflowCompleted Bir Workflow örneği tamamlanıp bellekten henüz kaldırılmadan önce devreye giren olaydır. Bu olaya ait metod yardımıyla host uygulamaya, tamamlanan Workflow örneğinin output parametrelerini döndürmek mümkündür.
WorkflowCreated Workflow örneği oluşturulduğunda ancak Start metodu ile çalıştırılmadan az önce tetiklenir.
WorkflowIdled Bir workflow örneği dışarıdan beklediği bir etki veya Delay aktivitesi nedeni ile içerisinde yer alan herhangibir aktiviteyi işletmediği durumlarda tetiklenir. Özellikle Idle olma durumunda Persistence hizmetlerinin kullanımı önem kazanır.
WorkflowLoaded Persistence servisi kullanıldığı durumlarda, Workflow örneğinin herhangibir aktivitesi çalıştırılmadan önce ve belleğe yüklenmesi sonrasında devreye giren olaydır.
WorkflowPersisted Persistence servisin kullanılması halinde bir workflow örneğinin saklanmak üzere kaydedilmesi sonrasında tetiklenir. Workflow persistence servisi varsayılan olarak SQL veritabanını kullandığından, söz konusu kaydetme işlemi tablo üzerinde gerçekleştirilmektedir.
WorkflowResumed Bir erteleme nedeni ile Suspended moda geçen bir örneğin tekrar ayağa kalkması sonrasında ve kaldığı yerden devam ederken herhangibir aktivite çalıştırılmadan önce devreye giren olaydır.
WorkflowStarted Workflow örneği yürütülmeye başlatıldığında tetiklenir. Bu başlangıç kök aktivitenin(Root Activity) çalıştırılması sonrasında meydana gelmektedir.
WorkflowSuspended Workflow örneği, Suspend metoduna yapılan çağrı veya Suspend aktivitesine gelinmesi nedeniyle Suspended moduna geçtiğinde tetiklenen olaydır.
WorkflowTerminated Workflow örneği yok edildikten ama bellekten atılmadan az önce çalışan olaydır. Workflow, Terminate metodu yardımıyla, Terminate aktivitesine gelinmesi nedeniyle veya ele alınmamış bir istisna(Unhandled Exception) yüzünden Terminated durumuna geçebilir. Eğer persistence servis kullanılıyorsa, Terminate edilen workflow örneğine ait tüm kayıtlar ilgili depolama alanından kaldırılır. SQL tabanlı persistence servisi göz önüne alındığında bu, tablolar üzerinde gerekli silme işlemlerinin yapılması anlamına gelmektedir.
WorkflowUnloaded Persistence servisleri kullanıldığında, Workflow örneği depolama alanına kaydedildikten sonra ama bellekten kaldırılmadan az önce tetiklenen olaydır.

Aslında WF Çalışma Zamanı Motorunun(WF Runtime Engine) kendisinin bir State Machine olduğu rahatlıkla düşünülebilir. Nitekim, yönetmekte olduğu örneklerin durumları arasındaki geçişleri kontrol altına almakta ve bununla ilişkili olayları yönetmektedir. Temel olarak workflow örnekleri Created, Running, Suspended, Completed ve Terminated olmak üzere 5 farklı duruma sahip olabilir. Bu durum aşağıdaki diyagram ile özetlenebilir.

İlk etapta, Persistence servisinin devrede olmadığı durumlarda standart olarak çalışan olay metodlarını ele alacağımız bir örnek geliştirerek devam edebiliriz. Bu amaçla örnek bir Sequential Workflow Console Application oluşturarak başladığımızı düşünebiliriz. Söz konusu örnek içerisinde Costflow isimli bir Sequential Activity kullanılmakta olup adımları aşağıdaki şekilde görüldüğü gibidir.

Söz konusu aktivite aynı zamanda dışarıdan parametre alıp, bir sonuç üretmektedir. Aktivitenin faaliyetsiz(Idle) moda geçtiğini görmek için sembolik olarak Delay aktivitesinden yararlanılmaktadır. Söz konusu aktivititede sadece duraksama süresi özelliği 10 saniye olarak set edilmiştir.



Costflow aktivitesine ait kod içeriği aşağıdaki gibi tasarlanabilir.

using System;
using System.Workflow.Activities;

namespace WFCostFactory
{
    public enum WorkType
    {
        Consumer,
        Corporate
    }
    public sealed partial class Costflow
            : SequentialWorkflowActivity
    {
        #region Workflow özellikleri(Properties)

        // Dış ortamdan gelen parametreler
        public int TotalDays { get; set; }
        public decimal CostValue { get; set; }
        public WorkType WorkT { get; set; } // Dış ortama sonuç olarak döndürülen parametre

        #endregion

        public Costflow()
        {
            InitializeComponent();
        }

        // CodeActivity tarafından çalıştırılan örnek fonksiyonellik
        private void Calculate(object sender, EventArgs e)
        {
            Console.WriteLine("Calculate Metodu. Maliyet hesaplama işlemleri yapılır");
            switch (WorkT)
            {
                case WorkType.Consumer:
                    CostValue = TotalDays * 1.10M;
                    break;
                case WorkType.Corporate:
                    CostValue = TotalDays * 1.15M;
                    break;
                default:
                    CostValue = 1;
                    break;
            }
        }
    }
}

Söz konusu aktiviteyi host eden Console uygulamasına ait kod içeriği ise aşağıdaki gibidir. Burada dikkat edilmesi gereken nokta WorkflowRuntime örneğine ait tüm olayların yüklenmiş olmasıdır. Bu olayların çoğu örneğimizde devreye girmeyecektir. Ancak hangi durumlarda devreye gireceği yukarıdaki tablolarda belirtilmiştir.

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

namespace WFCostFactory
{
    class Program
    {
        static WorkflowRuntime wfRuntime = null;
        static AutoResetEvent wHandle = null;

        static void Main(string[] args)
        {
            // Workflow Runtime nesnesi örneklenir
            using(wfRuntime = new WorkflowRuntime())
            {
                wHandle = new AutoResetEvent(false);

                #region Event Tanımlamaları

                wfRuntime.Started += new EventHandler<WorkflowRuntimeEventArgs>(wfRuntime_Started);
                wfRuntime.ServicesExceptionNotHandled += new EventHandler<ServicesExceptionNotHandledEventArgs>(wfRuntime_ServicesExceptionNotHandled);
                wfRuntime.Stopped += new EventHandler<WorkflowRuntimeEventArgs>(wfRuntime_Stopped);
                wfRuntime.WorkflowAborted += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowAborted);
                wfRuntime.WorkflowCreated += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowCreated);
                wfRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowIdled);
                wfRuntime.WorkflowLoaded += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowLoaded);
                wfRuntime.WorkflowPersisted += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowPersisted);
                wfRuntime.WorkflowResumed += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowResumed);
                wfRuntime.WorkflowStarted += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowStarted);
                wfRuntime.WorkflowSuspended += new EventHandler<WorkflowSuspendedEventArgs>(wfRuntime_WorkflowSuspended);
                wfRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(wfRuntime_WorkflowTerminated);
                wfRuntime.WorkflowUnloaded += new EventHandler<WorkflowEventArgs>(wfRuntime_WorkflowUnloaded);
                wfRuntime.WorkflowCompleted+=new EventHandler<WorkflowCompletedEventArgs>(wfRuntime_WorkflowCompleted);

                #endregion

                // Workflow nesne örneği oluşturulur
                // TotalDays ve WorkT özellikleri için ilk değerler set edilir
                WorkflowInstance instance = wfRuntime.CreateWorkflow(
                    typeof(WFCostFactory.Costflow)
                    , new Dictionary<string,object>
                        {
                            {"TotalDays",20}
                            ,{"WorkT",WorkType.Corporate}
                        }
                    );
                // Workflow örneği başlatılır
                instance.Start();
                // İşlemler tamamlana kadar bekle
                wHandle.WaitOne();
            }
        }

        static void wfRuntime_WorkflowUnloaded(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowUnloaded", e.WorkflowInstance.InstanceId.ToString());
        }
   
        static void wfRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2} Exception Message : {3}", DateTime.Now, "WorkflowTerminated",             e.WorkflowInstance.InstanceId.ToString(), e.Exception.Message);
            wHandle.Set();
        }

        static void wfRuntime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowSuspended", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowStarted(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowStarted", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowResumed(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowResumed", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowPersisted(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowPersisted", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowLoaded(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowLoaded", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowIdled(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowIdled", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowCreated(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowCreated", e.WorkflowInstance.InstanceId.ToString());
        }

        static void wfRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowCompleted", e.WorkflowInstance.InstanceId.ToString());
            Console.WriteLine("Maliyet : {0}", e.OutputParameters["CostValue"].ToString());
            wHandle.Set();
        }

        static void wfRuntime_WorkflowAborted(object sender, WorkflowEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, InstanceId : {2}", DateTime.Now, "WorkflowAborted", e.WorkflowInstance.InstanceId);
        }

        static void wfRuntime_Stopped(object sender, WorkflowRuntimeEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, IsStarted : {2}", DateTime.Now, "WFRuntime_Stopped", e.IsStarted.ToString());
        }
   
        static void wfRuntime_ServicesExceptionNotHandled(object sender, ServicesExceptionNotHandledEventArgs e)
        {
            Console.WriteLine("{0} : InstanceId : {1} Event : {2}, Exception Message : {3}", DateTime.Now, e.WorkflowInstanceId.ToString(),"WF        Runtime_ServicesExceptionNotHandled",e.Exception.Message);
        }

        static void wfRuntime_Started(object sender, WorkflowRuntimeEventArgs e)
        {
            Console.WriteLine("{0} : Event : {1}, IsStarted : {2}", DateTime.Now, "WFRuntime_Started", e.IsStarted.ToString());
        }
    }
}

Örneği ilk etapta bu haliye çalıştırdığımızda aşağıdaki ekran çıktısı ile karşılaşırız.

İlk analizimi yapabiliriz artık. Dikkat edileceği üzere ilk olarak Workflow çalışma zamanına ait Started olayı tetiklenmiş ve IsStarted değeri true olarak set edilmiştir. Hatırlanacağı üzere Workflow motorunun kullandığı veya başlattığı tüm servislerin IsStarted özelliğine etkisi vardır ve bu özelliğin değeri true kalmadığı sürece çalışma zamanı başlatılamayacaktır. Bu işlemin arkasından Workflow nesnesi örneklendiği için WorkflowCreated ve WorkflowStared olayları sırasıyla çalışmaktadır. Süreç devam etmekteyken Delay aktivitesi devreye girmiştir. İşte bu noktada Workflow örneği faaliyetsiz kalarak, çalışma zamanı moturu tarafından bellekte tutulmaya devam edilmektedir. Bu anda WorkflowIdled olayı tetiklenmiştir.

NOT : Özellikle olay metodlarının bazıları içerisindeden GUID tipinden InstanceId değerlerinin elde edilebiliyor olması önemlidir ki bu sayede hangi WF örneğinin faaliyetsiz kaldığı kolayca anlaşılabilmektedir. Tahmin edeceğiniz üzere bu Id değeri persistence ortamları içinde benzersiliği sağlamak açısından önemlidir.

Faaliyetsiz kalma durumu Delay aktitivitesinde belirtilen süre sonlanıncaya kadar devam eder. Süre sonunda ise Workflow örneği çalışmasına kaldığı yerden tekrar başlayacaktır. İşte bizim en büyük amacımız bu faaliyetsiz kalma anında söz konusu WF örneğini SQL Persistence Service yardımıyla fiziki bir ortama kaydetmektir.

Varsayılan olarak WF örneklerinin durumu bellekte saklanır. Bir başka deyişle, workflow herhangibir sebeple faaliyetsiz duruma geçtiğinde söz konusu örnek bellekte asılı olarak kalır ve beklemeye başlar. Ancak gerçek hayat senaryolarında çalışmakta olan Workflow örneklerinin uzun süre asılı kalmasıda söz konusu olabilir. Bu, özellikle faaliyetine devam etmesi için onu bekleten operasyona bağlıdır. Dolayısıyla bu tip vakalarda Workflow örneklerinin kalıcı olarak saklanmaları tercih edilebilir. Kalıcılıkta esas olan Workflow örneğinin o anki durumu ve değerleri ile fiziki bir depolama alanına atılmasıdır. Bu noktada çoğunlukla SQL gibi veritabanı kaynaklarının kullanılması tercih edilir. Diğer taraftan elbetteki Persistence servisleri özelleştirilebilir ve farklı veri kaynaklarına kaydetme işlemleri gerçekleştirilebilir.

Workflow Foundation, çalışma zamanındaki WF örneklerinin bellekten kaldırılıp çalışmasının durdurulması sonrasında, kalıcı olarak saklanabilmeleri için önceden geliştirilmiş bir Persistence servisi sunmaktadır. Hangi tip Persistence kullanılırsa kullanılsın, Workflow çalışma zamanı, kalıcı olarak saklanan Workflow örneğine(örneklerine) gelen mesajları takip de eder. Bu sayede gerektiği anda, üzerinde yer alan Persistence servisi devreye alarak, ilgili WF örneğinin tekrardan belleğe yüklenmesini sağlayabilir. Workflow çalışma zamanı, Persistence servisini belirli durumlar gerçekleştirildiğinde çağırmaktadır. Bu durumlar;

  • WF örneği belirli bir nedenden askıya alındığında yani faaliyetsiz hale geçtiğinde(Idle).
  • WF örneği yok edilmeden(Terminate) önce.
  • WF örneği tamamlanmadan(Complete) önce.
  • Workflow örneği üzerinde Unload, TryUnload metodları çağırıldığında.
  • PersistOnCloseAttribute niteliği ile imzalanmış olan bir aktivitenin tamamlanması sonrasında. Özellikle transaction kullanan aktivitiler bu niteliğe sahiptir. Diğer taraftan içerisinde transaction kullanılacak olan aktivitilerinde bu nitleği uygulaması gerekir.

WorkflowPersistenceService abstract sınıfından türetme yapılarak istenirse özel persistence sınıflarıda yazılabilmektedir. Biz örneğimizde SqlWorkflowPersistenceService tipinden yararlanarak WF örneklerini SQL veritabanı üzerinde saklamaya çalışacağız. Şimdi bu durumu incelememiz gerekiyor. Ancak depolama alanı için SQL tarafında gerekli hazırlıkların yapılması gerekmektedir. Bu noktada .Net Framework ile birlikte hazır olarak gelen WF SQL betikleri(Scripts) kullanılabilir. Söz konusu SQL betikleri varsayılan olarak örneğin Windows XP işletim sisteminin kurulu olduğu bir makinede C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN klasöründe yer almaktadır.

Burada yer alan SqlPersistenceService_Logic ile SqlPersistenceService_Schema betikleri, Persistence depolama alanı için gerekli tablo(Table), saklı yordam(Stored Procedure) gibi veritabanı nesnelerini oluşturmakla görevlidir. Söz konusu betikler, bir SQL sunucusu üzerinde çalıştırılıp kullanılabileceği gibi, istenirse ilgili Workflow çalışma zamanını Host eden uygulamanın erişebileceği dosya bazlı bir veritabanı üzerindende çalıştırılabilir. Biz örneğimizde ikinci seçeneği kullanacağız. Yani, söz konusu depolama alanı için SQL Express Edition temelli bir veritabanı dosyasını ele alacağız. Bu amaçla ilk olarak Solution' ımıza bir Database Project ekleyerek devam edebiliriz. Proje eklenmesi sırasında bize aşağıdaki ekran görüntüsünde olduğu gibi kullanmak istediğimiz veritabanı sorulacaktır.

Elimizde böyle bir veritabanı olmadığını göz önüne alaraktan Add New Reference seçeneğine tıklayalım. Sürekli kullandığımız standart bağlantı ekleme iletişim kutusu ile karşılacağız. Burada önemli olan veri kaynağı olarak Microsoft SQL Server Database File (SqlClient) tipinin seçilmesidir. Sonrasında ise veritabanımıza bir isim vererek devam edebiliriz.

Ok düğmesine bastığımızda söz konusu veritabanı yoksa eğer, oluşturmak isteyip istemediğimize dair bir soru sorulacaktır. Bu oluşturma işlemi sırasında unutulmaması gereken noktalardan biriside SQL Express servisinin çalışıyor olması zorunluluğudur. Eğer servis çalışmıyorsa tahmin edileceği üzere söz konusu veritabanı oluşturulamayacaktır. Database projesi oluşturulduktan sonra yukarıda değindiğimiz SQL betiklerini Create Scripts klasörü altına ekleyerek devam edebiliriz. Bu işlemler sonrasında proje içeriği aşağıdakine benzer olacaktır.

(Buradaki gibi bir veritabanı projesinin oluşturulması aslında şart değildir. Bu sadece söz konusu veritabanının yönetimin kolaylaştırılmasını sağlayan ve belirli bir düzeni tesis eden bir opsiyon olarak görülmelidir. Genel olarak gerçek hayat uygulamalarında depolama alanı olarak sunucu bazlı veritabanları tercih edilir. Sizlere tavsiyem aynı örneği SQL sunucusu üzerinde gerçekleştirmeye çalışmanızdır.)

Sıradaki işlem, söz konusu SQL betiklerinin çalıştırılmasıdır. Bu betikler çalıştırıldıktan sonra CostFactoryPersistenceDb isimli veritabanının içeriği aşağıdaki şekilde görüldüğü gibi oluşturulacaktır.

Burada temel olarak Workflow örneklerinin saklanması(Insert), kilitlenmesi(lock), elde edilmesi(retrieve) veya silinmesi(delete) ile ilişkili gerekli Stored Procedure' ler ve tablolar yer almaktadır. Artık depolama alanıda tanımlandığına göre, Workflow uygulamamız için gerekli kod değişikliklerini yapabiliriz. Bu amaçla host uygulama üzerinde SqlWorkflowPersistenceService' in oluşturulması ve çalışma zamanına eklenmesi gerekmektedir. İşte örnek kodlarımız;

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

namespace WFCostFactory
{
    class Program
    {
        static WorkflowRuntime wfRuntime = null;
        static AutoResetEvent wHandle = null;

        static void Main(string[] args)
        {
            // Workflow Runtime nesnesi örneklenir
            using(wfRuntime = new WorkflowRuntime())
            {
                // Varsayılan ayarları ile persistence servisi örneklenir
                SqlWorkflowPersistenceService persistenceService = new SqlWorkflowPersistenceService
                (
                    @"Data Source=.\SQLEXPRESS;AttachDbFilename=C:\Documents and Settings\Burak Selim Senyurt\My Documents\CostFactoryPersistenceDb.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True"
                );
                // Oluşturulan servis çalışma zamanına bildirilir.
                wfRuntime.AddService(persistenceService);
                //Diğer kod satırları...

İlk olarak System.Workflow.Runtime.Hosting isim alanında(namespace) yer alan SqlWorkflowPersistence hizmetine ait bir nesne örneği oluşturulur. Nesne örneklenirken yapıcı metod(Constructor) içerisinde persistence için kullanılacak veri depolama alanın bağlantı bilgisi verilmektedir. Bu en basit yapıcı metodu versiyonudur. Diğer versiyonlarını kullanarak farklı başlangıç ayarlamaları yapılabilir. Söz gelimi Workflow örneklerinin Idle moda geçtiklerinde bellekten kaldırılıp kaldırılmayacakları, birden fazla Workflow çalışma zamanı moturunun aynı WF örneklerini kullanmaları halinde, birbirlerini kesmemeleri için kilit sürelerinin(Lock Time) ne olacağı gibi kriterlerde yapıcı metod parametreleri ile belirlenebilir.

NOT : Servis tanımlaması ile ilgili ayarlar istenirse konfigurasyon dosyasında da yapılabilir. Bunun için host uygulamaya ait konfigurasyon dosyasında örneğin aşağıdaki tanımlamaların yapılması yeterlidir.

Tabi host uygulama içerisinde WorkflowRuntime nesne örneği oluşturulurken wfRuntime=new WorkflowRuntime("WorkflowRuntime"); şeklinde bir kullanım söz konusudur. Burada parametre olarak app.config dosyasındaki section adı verilmektedir. Bu ad benzersizdir. Yani farklı bir isim olamaz. Diğer taraftan yapıcı metodun bu versiyonunun çalıştırılabilmesi için(örneğin geliştirdiğimiz Console uygulamasında) mutlaka System.Configuration assembly' ının projeye referans edilmesi gerekmektedir.

Artık uygulamamızı test etmeye başlayabiliriz. Konuyu kolay takip edebilmek amacıyla geliştirdiğiniz örneği Debug ederken adım adım ilerlemenizi öneririm. Öncelikle programın çalışması sonrasındaki ekran görüntüsüne bakalım.

Dikkat edileceği üzere Workflow örneği faaliyetsiz hale geçtikten sonra(WorkflowIdled olayının tetiklenmesi sonrası) sırasıyla WorkflowPersisted, WorkflowUnloaded olayları çalışmıştır. Bir başka deyişle faaliyetsiz kalan Workflow örneği veritabanındaki ilgili tablolara yazılmıştır. Diğer taraftan faaliyetsiz kalma süresi dolduğunda ve Workflow akışı tekrar devam etmek istediğinde sırasıyla WorkflowLoaded ve WorkflowPersisted olayları tetiklenmiştir. Yani WF örneği tablodan tekrar yüklenerek yürütülmeye devam etmiş ve son olarakta tamamlanmıştır. Özellikle Workflow faaliyetsiz hale geldiğinde InstanceState isimli tabloda aşağıdaki ekran görüntüsüne benzer olacak şekilde bir satır açıldığı ve Delay süresi sona erdikten sonra ise WF örneğinin tekrar ayağa kaldırılmasıyla birlikte söz konusu satırın silindiği görülür.

Tahmin edileceği üzere bu satır saklanan WF örneğine ait bilgileri serileştirerek tutmaktadır. Bu nedenle özellikle WF içerisinde kullanılan tiplerin, eğer SQL Persistence hizmeti kullanılıyorsa serileştirilebilir olmalarına dikkat etmek gerekmektedir. Bu durumu analiz etmek için Costflow aktivitesine Customer isimli tipten bir özellik eklenmiştir.

using System;
using System.Workflow.Activities;

namespace WFCostFactory
{
    public enum WorkType
    {
        Consumer,
        Corporate
    }
    public class Customer
    {
        public string Name { get; set; }
        public int Id { get; set; }
    }
    public sealed partial class Costflow : SequentialWorkflowActivity
    {
        #region Workflow özellikleri(Properties)

        // Dış ortamdan gelen parametreler
        public int TotalDays { get; set; }
        public decimal CostValue { get; set; }
        public WorkType WorkT { get; set; } // Dış ortama sonuç olarak döndürülen parametre
        public Customer Owner { get; set; }

        #endregion

        public Costflow()
        {
            InitializeComponent();
        }
        // Diğer kod satırları

Burada hemen bir noktayı vurgulamak isterim. Söz konusu örnek bu haliyle çalıştırıldığında serileştirme ile ilişkili herhangibir hata mesajının alınmadığı görülecektir. Bunun nedeni Customer tipine ait nesne örneğinin WF içerisinde kullanılmamış olmasıdır. Bu nedenle Workflow nesnesi host uygulamada örneklenirken aşağıdaki kod değişikliğini yapmamız çalışma zamanında serileştirme hatasını almamız için gerekli ve yeterlidir.

WorkflowInstance instance = wfRuntime.CreateWorkflow(
        typeof(WFCostFactory.Costflow)
        , new Dictionary<string,object>
        {
            {"TotalDays",20}
            ,{"WorkT",WorkType.Corporate}
            ,{"Owner",new Customer{ Id=1000, Name="Burak Selim Şenyurt"}}
        }
);

Örnek bu haliyle çalıştırıldığında aşağıdaki görüntü ile karşılaşılır.

Dikkat edileceği üzere, WorkflowPersisted olay metodunun hemen arkasından WorkflowTerminated olayı tetiklenmiş ve oluşan istisna(Exception) mesajı ekrana yazdırılmıştır. Bir başka deyişle Workflow örneği tabloya yazdırılamamış ve istisna fırlatarak sonlanmıştır. İşte bu durumun sebebi Owner isimli Customer tipinin binary formatta serileştirilebilir tanımlanmamasıdır. Bu nedenle Customer tipinin Serializable niteliği(Attribute) ile aşağıdaki kod parçasında görüldüğü gibi işaretlenmesi gerekir.

[Serializable]
public class Customer
{
    public string Name { get; set; }
    public int Id { get; set; }
}

Uygulama tekrar denendiğinde sorunsuz olarak çalıştığı hatta WF örneği oluşturulurken parametre olarak verilen Customer nesnesinin, WorkflowCompleted olayında (tabloda saklanıp tekrar elde edilmesi ile birlikte) tedarik edilebildiği görülebilir.

Görüldüğü üzere Workflow örneklerinin, çalışma zamanında belirli koşulların sağlanması şartıyla bir depolama alanında saklanması ve sonradan tekrardan ayağa kaldırılıp yürütülmesi SQL Persistence Service yardımıyla son derece kolay bir şekilde gerçekleştirilebilmektedir. Elbetteki gerçek hayat koşullarında SQL dışı kaynakların kullanılmasıda istenebilir. Bu gibi vakalarda söz konusu hizmet için özel bir geliştirme yapılması gerekmektedir. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

PersistenceInceleme.rar (33,50 kb)

Tags:   ,
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF ile WF Entegrasyonu - 2

Çarşamba, 23 Nisan 2008 02:32 by bsenyurt

Bir önceki yazımızda WCF(Windows Communication Foundation) servislerinin, WF(Windows WorkFlow) uygulamaları içerisinden nasıl çağırıldığını incelemiştik. Bu yazımızda ise tam tersine, bir Workflow örneğinin servis olarak nasıl sunulabileceğini analiz ediyor olacağız. Bazı durumlarda kod akışlarının birer servis olarak istemcilere sunulması gerekebilir. Burada söz konusu kod akışlarının Servis Yönelimli Mimarinin(Service Oriented Architecture) imkanlarından yararlanıyor olması isteği ön plana çıkmatadır. Çok doğal olarak servis gibi yayınlanan akış tipleri(Workflow Instance), istemci ile olan mesajlaşmalarında SOA temelli olanakları kullanabilir hale gelmektedir. Bu noktada WCF ile WF entegrasyonu göz önüne alınmalıdır.

WCF servisleri WF uygulamaları içerisinden çağırılırken ağırlıklı olarak .Net Framework 3.5 ile birlikte gelen SendActivity bileşeni kullanılmaktadır. WF örneklerinin servis olarak yayınlamasında ise başrol oyuncusu yine .Net Framework 3.5 ile birlikte gelen ReceiveActivity isimli activity bileşenidir. Özellikle Visual Studio 2008 kullanılarak servis destekli Workflow kütüphaneleri kolay bir şekilde geliştirilebilmektedir. Bu amaçla Visual Studio 2008 ortamına Sequential Workflow Service Library ve State Machine Workflow Service Library proje şablonları eklenmiştir. Tabi çok doğal olarak geliştirilen iş akışı servislerinin bir uygulama tarafından barındırılması(Hosting) ve yayınlanmasıda gerekmektedir. Host seçenekleri WF servisleri içinde aynıdır. IIS üzerinde, WAS(Windows Activation Service) yardımıyla veya Self-Hosting seçeneklerine göre barındırma ve yayınlama yapılabilir. Önemli olan noktalardan birisie normal WCF servislerinden farklı olaraktan Host çalışma ortamını WorkflowServiceHost tipinin yönetmesidir.

WF uygulamasının servis olarak yayınlanması için istemcilere bir sözleşme(Contract) bildirimi yapılması gerekmektedir. Çok doğal olarak bu sözleşme(Contract) bir arayüz(Interface) olarak tanımlanmalıdır. Arayüz içerisinde yer alan operasyonlar dışarıya ReceiveActivity bileşeni ile sunulabilirler. Buna göre sözleşme içerisinde tanımlanan her operasyon için(OperationContract niteliği ile imzalanmış metodlar) birer ReceiveActivity oluşturulmalıdır. Bu noktada karşılaşılan önemli sorulardan biriside, istemcinin bu operasyona nasıl parametre göndereceği veya cevap alacağıdır. Nitekim bir WCF kütüphanesinde çoğunlukla asıl işi yapan bileşen, sözleşme tipinin uygulandığı bir sınıftır(Class). WF açısından bakıldığında ise bu görevi Workflow sınıfı içerisinde tanımlanan özellikler(Properties) yada alanlar(fields) üstlenmektedir. Bir başka deyişle, ReceiveActivity irtibatta olduğu operasyon için gerekli parametreler ile haberleşmek adına söz konusu özellik veya alanlardan yararlanır. Dolayısıyla asıl iş ReceiveActivity içerisinden yapılmalıdır. ReceiveActivity bileşeninin composite bir bileşen olarak tanımlanmasının sebebide budur. ReceiveActivity içerisine örneğin CodeActivity gibi bileşenler dahil edilerek operasyonun asıl işinin yapılacağı kod bloklarının işletilmesi sağlanabilir. İstemci açısından olaya bakıldığında yine bir proxy nesnesinin servis ile olan haberleşmeyi sağladığı ortadadır. Aslında aşağıdaki şekil durumu biraz daha kolay bir şekilde açıklamaktadır.

Şekle göre WorkflowActivity sınıfının ReceiveActivity bileşenleri, dışarıya servis üzerinden sunulan operasyonlar ile ilişkili talep alma ve cevap gönderme işlemlerini üstlenmektedir. Çok doğal olarak servisi bir EndPoint üzerinden dışarıya sunmak gerekmektedir. EndPoint tanımında dikkat edilmesi gereken noktalardan biriside, bağlayıcı tipin(Binding Type) basicHttpContextBinding, netTcpContextBinding yada wsHttpContextBinding bileşenlerinden birisi olmaslıdır. Bu bağlayıcı tiplerin ortak özelliği servis destekli iş akışları(Service-Enabled Workflow) ile haberleşilebilmesini sağlamalarıdır. Ancak istenirse özel bağlayıcılara ContextBindingElement tipi uygulanarak workflow destekli hale getirilmeleride sağlanabilir. İstemci uygulama çok doğal olarak servis ile olan haberleşme sırasında proxy sınıfından yararlanmak durumundadır.

Not : WF ve WCF entegrasyonun bir sonucu olarak istemciler bir Workflow aktivitesini servis bazlı olacak şekilde çağırıp kullanabilmektedir. Böylece istemciler bir kod akışı sürecini servis tabanlı düşünerek ele alabilirler. Buna JSON(JavaScriptObjectNotation) gibi mesajlaşma desteklerinin eklenebileceği düşünüldüğünde bir Workflow örneğinin herhangibir platform üzerinden kullanılabilmeside mümkün hale gelmektedir. Bu WCF tanımında yer alan "her hangibir CLR tipinin servis olarak yayınlanabilmesi" ilkesinin bir sonucu olarak görülebilir.

Bu kadar karmaşık ve teorik bilgiden sonra örnek bir uygulama üzerinden hareket ederek WF uygulamaları içerisinde servis yayınlamasının nasıl yapılabileceğini adım adım incelemeye çalışalım. İşe ilk olarak servis sözleşmesini ve operasyonlar için gerekli aktivite bileşenlerini içerecek olan kütüphaneyi tasarlamak ile başlamak doğru olacaktır. Bu amaçla Visual Studio 2008 ortamında bir Sequential Workflow Service Library projesi açtığımızı düşünelim.(Bu şablon New Project->WCF sekmesi altında yer almaktadır.)

Söz konusu servis kütüphanesine(WFSiparisKutuphanesi) bakıldığında ilk dikkati çeken nokta referanslar kısmına Workflow desteği için eklenen assembly' lardır.

Bu noktada şablon olarak getirilen IWorkflow1.cs, Workflow1.cs ve App.config isimli dosyalar silinmiştir. Örnekte kullanılan servis sözleşmesi içeriği ise aşağıdaki sınıf diagramında görüldüğü gibidir.

using System;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace WFSiparisKutuphanesi
{
    [ServiceContract(Namespace = "http://www.bsenyurt.com/UrunSiparisServisi", Name = "UrunSiparisServisi")]
    public interface ISiparisSozlesmesi
    {
        [OperationContract]
        string SiparisVer(UrunBilgisi urun);
    }

    [DataContract]
    public class UrunBilgisi
    {
        [DataMember]
        public string UrunKodu { get; set; }
        [DataMember]
        public int Adet { get; set; }
        [DataMember]
        public DateTime SiparisTarihi { get; set; }
    }
}

ISiparisSozlesmesi isimi servis sözleşmesi(Service Contract) SiparisVer isimli tek bir operayon sunmaktadır. SiparisVer metodu parametre olarak UrunBilgisi sınıfına ait bir nesne örneği almaktadır. Bu nesneye ait veri, çalışma zamanında istemci tarafından servise doğru geleceğinden serileştirilebilir olması gerekmektedir. Bu sebepten doğal olarak veri sözleşmesi(Data Contract) olacak şekilde tasarlanmıştır. SiparisVer isimli operasyon aynı zamanda geriye string tipinden bir değerde döndürmektedir. Böylece dışarıya sunulacak olan servis sözleşmesi tanımlanmıştır. Artık bu sözleşmeyi kullanacak olan aktivite sınıfının yazılması gerekmektedir. Örnekte bunun için bir adet Sequential Workflow Activity sınıfı ele alınacaktır. Bunun için projeye Add->Sequential Workflow seçeneği ile yeni bir akış sınıfı eklenmelidir.

Bu adımda Sequential Workflow(with code seperation) seçeneği işaretlenerek devam edilebilir. Böylece dizayn tarafı ile kodu birbirinden ayrı tutacak bir akış tipi oluşturulacaktır.

WFSiparis.xoml (XOML-eXtensible Object Markup Language) tasarım zamanında gerekli aktivite tiplerini barındıracaktır. WFSiparis isimli sınıf SequentialWorkflowActivity tipinden türemektedir. Servis sözleşmesi içerisinde yer alan SiparisVer metodu bu akış nesnesi içerisinde ReceiveActivity bileşeni tarafından ele alınmalıdır. Bununla birlikte SiparisVer metodunun UrunBilgisi ve döndüreceği değer için birer özellik/alan(Property/Field) içermesi gerekmektedir. Öncelikli olarak bir ReceiveActivity bileşeni tasarım ekranına sürüklenip bırakılmalıdır. ReceiveActivity bileşeni SendActivity bileşenine benzer olaraktan ServiceOperationInfo özelliğini içermektedir. Çok doğal olarak bu özellikten yararlanılarak hangi operasyonun ele alınacağı ve kullanılacak(yada otomatik olarak üretilecek) olan parametreler belirlenmelidir. ServiceOperationInfo özelliğinde yer alan üç nokta düğmesine basıldıktan sonra aşağıdaki arayüz ile karşılaşılır.

Burada yine Import seçeneği kullanılarak aşağıdaki ekran görüntüsünde olduğu gibi ilgili servis sözleşmesinin seçilmesi gerekmektedir.

Çok doğal olarak ISiparisSozlesmesi otomatik olarak gelecektir. Bu noktada Workflow içerisinde birden fazla servis sözleşmesi tutulabileceği de unutulmamalıdır. Bu seçim işlemini takiben aşağıdaki ekran görüntüsü elde edilir ve artık operasyon seçimi ve bunun için gerekli sınıf özelliklerinin oluşturulması adımına geçilebilir.

Dikkat edileceği üzere Parameters kısmında String tipinden yönü Out olan ve UrunBilgisi tipinden yönü In olan birer parametre görülmektedir. Bu parametrelerin akış içerisinde ele alınması için karşılığı olan özelliklerin veya alanların sınıf içerisine ya manuel yada otomatik olarak dahil edilmesi şarttır. Sonuç olarak aşağıdaki ekran görüntüsü ile karşılaşılacaktır.

Burada ReturnValue ve urun özellikleri için otomatik özellik ürettirilmesi sağlanabilir. Söz gelimi aşağıdaki ekran görüntüsünde örnek olarak SiparisVer metodunun geri dönüş tipi için otomatik özellik ürettirilmesinin nasıl sağlandığı gösterilmektedir.

Aynı işlem urun özelliği içinde yapıldıktan sonra receiveActivity1 için son durum aşağıdaki gibi olacaktır.

Çok doğal olarak bu yapılan değişiklikler sonrasında WFServis isimli sınıfın içeriği aşağıdaki gibi değişecektir.

namespace WFSiparisKutuphanesi
{
    public partial class WFSiparis : SequentialWorkflowActivity
    {
        public static DependencyProperty receiveActivity1_urun1Property = DependencyProperty.Register("receiveActivity1_urun1", typeof(WFSiparisKutuphanesi.UrunBilgisi), typeof(WFSiparisKutuphanesi.WFSiparis));
        public static DependencyProperty receiveActivity1__ReturnValue_1Property = DependencyProperty.Register("receiveActivity1__ReturnValue_1", typeof(System.String), typeof(WFSiparisKutuphanesi.WFSiparis));

        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [CategoryAttribute("Parameters")]
        public UrunBilgisi receiveActivity1_urun1
        {
            get
            {
                return ((WFSiparisKutuphanesi.UrunBilgisi)(base.GetValue(WFSiparisKutuphanesi.WFSiparis.receiveActivity1_urun1Property)));
            }
            set
            {
                base.SetValue(WFSiparisKutuphanesi.WFSiparis.receiveActivity1_urun1Property, value);
            }
        }

        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [CategoryAttribute("Parameters")]
        public string receiveActivity1__ReturnValue_1
        {
            get
            {
                return ((string)(base.GetValue(WFSiparisKutuphanesi.WFSiparis.receiveActivity1__ReturnValue_1Property)));
            }
            set
            {
                base.SetValue(WFSiparisKutuphanesi.WFSiparis.receiveActivity1__ReturnValue_1Property, value);
            }
        }
    }
}

Artık ReceiveActivity içerisine istemcilere döndürülecek olan cevap için gerekli activity bileşeninin eklenmesi adımına geçilebilir. Sonuç itibariyle istemciler SiparisVer metoduna çağrıda bulunduktan sonra bir aktivitenin işletilmesi gerekmektedir. Bu aktivite ReceiveActivity içerisinde tanımlanabilir. Söz gelimi CodeActivity bu işlem için idealdir. Operasyona istemciden gelen bilgi, aktivite sınıfının receiveActivity1_urun1 özelliği üzerinden elde edilebilir. Metoddan istemciye döndürülecek olan string değer ise receiveActivity1_ReturnValue1 özelliğine set edilmelidir. Bu atama işlemide CodeActivity bileşeni içerisinde yapılabilir. (Burada CodeActivity kullanılması şart değildir. Önemli olan aktivite sınıfı içerisine eklenen özellikler yardımıyla istemciden operasyona gelen parametre bilgilerinin alınabilmesi veya geriye döndürelecek bir sonucun üretilebilmesi ve istemci tarafından ele alınabilmesidir.) Şimdi ReceiveActivity içerisine bir CodeActivity eklediğimizi düşünelim.

CodeActivity1 bileşeninin ExecuteCode özelliğine örnek olarak SiparisiIsle değerini verip kod içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim. (Şu anda amaç WF servislerinin nasıl yazılacağını görmek olduğundan Console tabanlı host ve istemci uygulamalar yazılacaktır. Bu nedenle CodeActivity bileşeninin işaret edeceği metod içerisinden o andaki talep bilgilerini değerlendirmek amacıyla Console ekranına bilgi yazdırılmaktadır.)

private void SiparisiIsle(object sender, EventArgs e)
{
    // Operasyona gelen istemci çağrısında UrunBilgisi nesne örneğine ait veriler bulunmaktadır. Bu verilere aktivite sınıfının özellikleri üzerinden erişilebilir.
    Console.WriteLine("Adet talebi : {0} Urun Numarası : {1} İstek Tarihi : {2}", receiveActivity1_urun1.Adet, receiveActivity1_urun1.UrunKodu, receiveActivity1_urun1.SiparisTarihi.ToString());
    receiveActivity1__ReturnValue_1 = "Istek Alınmıştır"; // İstemciye operasyonda dönecek olan sonuç
}

Artık servisi barındıracak olan Host uygulamanın yazılmasına başlanabilir. Host uygulama daha öncedende bahsedildiği gibi WCF mimarisinin izin verdiği herhangibir çeşitte olabilir (IIS, Console, WPF, WAS, Windows Service...). Örnekte Host uygulama basit bir Console projesi olarak geliştirilmektedir. Host uygulamada önemli olan noktalardan birisi, Workflow Service kütüphanesi ile birlikte, Workflow ve WCF çalışma ortamları için gerekli assembly' lara ihtiyaç olduğudur. Bu nedenle ilk etapta Console uygulamasında System.ServiceModel, System.Workflow.Activites, System.Workflow.ComponentModel, System.WorkflowServices kütüphanelerinin referans edilmesi gerekmektedir. Host uygulama için önem arz eden noktalardan biriside çalışma zamanı ortamı için gerekli konfigurasyon ayarlarıdır. Aynen WCF servislerinin yazılmasında olduğu gibi config dosyalarından yararlanılabilir yada kod bazında gerekli ayarlamalar yapılabilir. Örnekte kullanılan config dosyası içeriği aşağıdaki gibidir.

Host Uygulama App.config;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service behaviorConfiguration="WFSiparisBehavior" name="WFSiparisKutuphanesi.WFSiparis">
                <endpoint address="" binding="wsHttpContextBinding" name="WfSiparisWsHttpEndPoint" contract="WFSiparisKutuphanesi.ISiparisSozlesmesi" />
                <endpoint address="mex" binding="mexHttpBinding" name="MexEndPoint" contract="IMetadataExchange" />
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost:10001/WFSiparisServisi" />
                    </baseAddresses>
                </host>
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="WFSiparisBehavior" >                   
                    <serviceMetadata httpGetEnabled="true" />
                         <serviceDebug includeExceptionDetailInFaults="true"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>

Konfigurasyon dosyasında görüldüğü gibi iki adet EndPoint tanımlaması yapılmaktadır. Bunlardan birisi bağlayıcı olarak wsHttpContextBinding tipini kullanmaktadır. Diğer EndPoint ise base address üzerinden metadata erişimine izin veren bir Mex(Metadata Exchange) EndPoint olarak tanımlanmıştır. (Elbette host uygulamanın IIS üzerinde tutulduğu bir senaryoda Mex EndPoint bildirimine gerek yoktur.) Host uygulamanın Main metoduna ait kodlar ise aşağıdaki gibi geliştirilebilir.

using System;
using System.ServiceModel;
using System.Workflow.Runtime;
using WFSiparisKutuphanesi;

namespace Sunucu
{
    class Program
    {
        static void Main(string[] args)
        {
            // Workflow servisi için gerekli çalışma ortamının hazırlanmasını WorkflowServiceHost tipi üstlenir
            // Parametre olarak yayınlanacak servis bazlı kullanılacak olan aktivite sınıfı belirtilir.
            WorkflowServiceHost host = new WorkflowServiceHost(typeof(WFSiparis));
            // Host uygulama açıldığından devreye girecek olay metodu
            host.Opened += delegate(object sender, EventArgs arg)
            {
                Console.WriteLine("Host opened");
            };
            // Host uygulama kapatıldığında devreye girecek olan olay metodu
            host.Closed += delegate(object sender, EventArgs arg)
            {
                Console.WriteLine("Host Closed");
            };
            host.Open(); // Host açılır
            Console.WriteLine("Servis çalışıyor. Kapatmak için bir tuşa basın");
            Console.ReadLine();
            host.Close(); // Host kapatılır
        }
    }
}

Artık istemci uygulamanın yazılmasına geçilebilir. Ama öncesinde istemci için gerekli proxy tipinin ve konfigurasyon dosyası içeriğinin üretilmesi gerekmektedir. İki seçenek vardır. Svcutil aracı ve Visual Studio 2008 ortamında ele alınabilen Add Service Reference. Svcutil aracı komut satırından aşağıdaki ekran görüntüsünde yer aldığı gibi kullanılabilir. Tabiki bu işlem sırasında Host uygulamanın çalışıyor olması ve servisin dışarıdan erişilebilir durumda bulunması gerekmektedir.

Visual Studio 2008 ortamında Add Service Reference seçeneği yardımıyla da Proxy ve config üretimi gerçekleştirilebilir. Elbette bu yaklaşımda da Host uygulamanın çalışıyor olması gerekmektedir.

Örneğimizde yer alan proxy ve config dosyalarının üretimi için Add Service Reference yaklaşımı kullanılmıştır. Yukarıdaki ekran görüntüsündende takip edilebileceği gibi istemci uygulama base address ile belirtilen Url adresi üzerinden servis sözleşmesine erişebilmekte ve yayınlanan servis operasyonlarını görebilmektedir. İstemci uygulamada basit bir Console projesi olarak tasarlanmıştır ve Main metodunun kod içeriği aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Istemci.WFSiparisServisi;

namespace Istemci
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Sipariş için bir tuşa basın");
            Console.ReadLine();

            // SiparisVer metodu için gerekli parametre üretilir
            UrunBilgisi urn = new UrunBilgisi()
                {
                    Adet = 10,
                    UrunKodu = "AB-100",
                    SiparisTarihi = DateTime.Now
                };

            // Proxy üretimi gerçekleştirilir
            UrunSiparisServisiClient servis = new UrunSiparisServisiClient();
            Console.WriteLine("Talep gönderiliyor");
            // WF Servis operasyonu çağırılır
            string cevap=servis.SiparisVer(urn);
            // Operasyon sonucu gösterilir
            Console.WriteLine(cevap);
   
            Console.ReadLine();
        }
    }
}

Önce Host uygulama sonrasında istemci uygulama çalıştırılarak test edildiğinde aşağıdaki ekran görüntüsü elde edilir. Görüldüğü gibi istemci uygulama Workflow operasyonunu başarılı bir şekilde kullanabilmiştir.

Hatta birden fazla istemci uygulama çalıştırıldığındada WF servisinin başarılı bir şekilde her istemciye cevap verdiği görülmektedir.

Ancak dikkat edilmesi gereken önemli bir durum vardır. Aynı istemci uygulama tarafından ikinci bir sipariş isteği geldiğinde, bir başka deyişle SiparisVer metodu aynı proxy örneği üzerinden ikinci bir kez çağırıldığında ne olacaktır? Bunun için koda aşağıdaki satırları eklediğimizi düşünelim.

cevap = servis.SiparisVer(new UrunBilgisi() { Adet = 8, SiparisTarihi = DateTime.Now, UrunKodu = "KL-450" });
Console.WriteLine(cevap);

Uygulama test edildiğinde çalışma zamanında ikinci SiparisVer metodu çağrısında aşağıdaki istisna(Exception) mesajının alındığı görülür.(Detaylı hata mesajının istemci tarafındanda görülebilmesi için servis tarafında <serviceDebug includeExceptionDetailInFaults ="true"/> bilgisinin eklenmiş olması gerekmektedir.)

Bunun sebebi gelen talep sonrasında sunucu tarafında ilgili istemci için üretilen Workflow servis örneğinin cevap verdikten sonra artık olmayışıdır. Bu nedenle ikinci talep aslında istemcinin daha önceden kullandığı Workflow servis örneği için yaptığı istektir. Oysaki host uygulama tarafında bu Workflow servis örneğinin işi önceki talebin sonuçlanması ile bitmiştir. (Tabi burada kolaya kaçılarak pratik bir çözüm olarak istemci uygulama içerisinde her operasyon çağrısı öncesinde yeni bir proxy üretimi yoluna gidilebilir ki bu tavsiye edilen bir yol değildir.) İşte burada uzun süreli durağan olması gereken bir Workflow servisi örneği söz konusudur. Yani Workflow örneğinin durumunu koruması gerekmektedir. Persistence servisleri kullanılarak bu sorun çözümlenebilir. Bu amaçla SqlWorkflowPersistenceService tipinden yararlanılarak ilgili durağanlığın gerçekleştirilmesi sağlanabilir. Tabi bu amaçla öncelikli olarak SQL üzerinde ilgili veritabanının oluşturulması ve tabloların hazırlanması gerekmektedir. Sonrasında ise ilgili PersistenceService' in örneklenip Workflow servisi çalışma ortamına kod yardımıyla yada konfigurasyon bazında bildirilmesi gerekmektedir. Bu konu yazının kapsamı dışına çıktığından burada ele alınmayacaktır.

Böylece geldik bir makalemizin daha sonuna. Bu makalede basit olarak Workflow örneklerinin birer servis olarak nasıl yayımlanabileceğini adım adım incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

WFServisleri.rar (89,02 kb)

Tags:   ,
Categories:   WCF | WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WCF ile WF Entegrasyonu - 1

Perşembe, 17 Nisan 2008 02:24 by bsenyurt

Bilindiği üzere Window Communication Foundation ve Windows Workflow Foundation, .Net Framework 3.0 ile birlikte gelen önemli teknolojilerdendir. WCF servis yönelimli mimariye(Service Oriented Architecture) yeni bir yaklaşım getirip, dağıtık mimari uygulama geliştirme kavramlarını bir çatı altında toplayarak güçlü, daha platform bağımsız ve güvenilir bir ortamda geliştirme yapılabilmesini olanaklı kılan bir alt yapı sunmaktadır. WF ise, birden fazla adımdan oluşan kod süreçlerinin iş akışı(Workflow) tarzında olay güdümlü(Event-Driven) yada sırasal (Sequential) olacak şekilde tasarlanarak çeşitli .Net uygulamalarında kullanılabilmelerini mümkün kılan bir alt yapı tesit etmektedir. Workflow, süreçlere ait akışların hazırlanmasında ağırlıklı olarak aktivite tiplerini(Activity Types) kullanmaktadır. Bu aktivitelerin dallara ayrılması, çatallanması, birbirlerine katılması gibi pek çok işlem de, Workflow ortamı tarafından sunulmaktadır. WF ile geliştirilen iş akışları kısa süreceği gibi uzunda(Long-Running Workflows) sürebilir. Bu nedenle WF ortamı özellikle uzun süren akışların, sistem yeniden başlatmaları(reboot) gibi vakalara karşı ayakta durabilmesi için kalıcı olarak saklama işlemlerine destek de vermektedir. WF mimarisinin yetenekleri sadece bununlada sınırlı değildir. Örneğin Transaction desteği diğer yetenekleri arasında gösterilebilir.

Not : Windows Workflow Foundation ile Windows Communication Foundation arasındaki entegrasyon gerçek anlamda .Net Framework 3.5 ve Visual Studio 2008 ile birlikte sağlanmıştır.

WF aslında, .Net Framework 3.5 ve Visual Studio 2008 ile gelen yenilikler sayesinde WCF ortamının gerçek anlamda bir tamamlayıcısı teknoloji olarak görülmektedir. Bu anlamda WF uygulamaları içerisinden WCF servislerinin çağırılması ve kod akışının ilgili servis noktaları(Service EndPoint) üzerinden yürütülmesi sağlanabilmektedir. Tam tersine WF içerisindende servis yayınlaması yapılabilmektedir. Bu entegrasyon sayesinde süreçlerin istemcilere güçlü(Robust), güvenilir(Reliable) ve güvenli(Secure) bir şekilde sunulmasıda sağlanmaktadır. Öyleki bu entegrasyonun doğal sonucu olarak iş mantığının(Logic) farklı formatlarda(MTOM, SOAP, Binary, JSON, X509 vb...) dolaşımı, IIS(Internet Information Services), WAS(Windows Activation Service) ve Windows Service gibi ortamlar üzerinden host edilme imkanı gibi pek çok fonksiyonellik ele alınabilmektedir.

WCF ile WF entegrasyonu sırasında servis ile olan etkileşimin modellenebilmesi için Send, Receive gibi aktivitelerden yararlanılmaktadır. Send aktivitesi sayesinde WF içerisinden bir WCF servisine mesaj gönderilmesi sağlanabilir ki bu noktada Proxy nesneleride devreye girmektedir. Diğer taraftan Receive aktivitesi sayesinde workflow' un kendisinin bir servis gibi sunulabilmesi olanaklı hale gelmektedir.

SendActivity ve ReceiveActivity tipleri .Net Framework 3.5 ile birlikte gelmiştir.

Aktivasyon alt yapısının host edilmesi içinse ServiceHostBase abstract sınıfından türeyen ve .Net Framework 3.5 ile birlikte gelen WorkflowServiceHost tipinden yararlanılmaktadır. Servis ve istemci arasındaki önemli konulardan biriside korelasyonun sağlanmasıdır. Bu bir anlamda istemcinin doğru servis örneği ile iletişime geçebilmesi demektir ki bunun sağlanabilmesi için eklenmiş olan yeni davranış(Behavior) ve bağlayıcılar(Bindings) söz konusudur.

Bu teorik bilgilerden sonra örnekler ile devam edelim. İlk olarak bir WF uygulaması içerisinde bir WCF servisinin nasıl çağırılabileceğini incelemeye çalışacağız. WF içerisinden bir WCF servis noktasına ulaşmak için SendActivity, InvokeWebServiceActivity, CodeActivity aktivite tiplerinden yararlanılabilir. Bunlardan en güçlü olanı SendActivity dir. InvokeWebServiceActivity Web servislerinin proxy sınıfı aracılığıyla çağırılmasını sağlamaktadır. WCF tarafından asmx modeline uygun yayınlama yapılabildiğinden bu aktivite tipide tercih edilebilir. Nevarki SendActivity tipine göre herhangibir üstünlüğü bulunmamaktadır. SendActivity WCF servisi ile senkron(synchronous) olarak haberleşilmesini sağlar ve tek yönlü(One-Way), talep-cevap(Request-Response), talep-hata(Request-Fault) desenlerini ele alır. Tek yönlü desene göre servise talepte bulunulduktan sonra bir cevap beklenmez. Talep-Cevap desenine göre ise servisten yapılan isteğe bir sonuç gelinceye kadar beklenir(Synchronous). Son desene göre ise ya cevap gelir yada hata mesajı(Fault Message). Bunların dışında özel aktiviteler(Custom Activity) yazılarak iş mantığının söz konusu aktivite tip içerisine gömülmesi ve servisin ele alınmasıda sağlanabilir.

Elbette ilk olarak bir servis kütüphanesinin(WCF Service Library) ve host uygulamanın tasarlanması gerekmektedir. Örnekte kullanılacak olan servis kütüphanesinin içeriği aşağıdaki gibidir.

Product sınıfı;

[DataContract]
public class Product
{
    [DataMember]
    public int ProductId { get; set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public double ListPrice { get; set; }
}

Product sınıfı Production.Product tablosundaki herhangibir satıra ait ProductID,Name ve ListPrice bilgilerini taşımak üzere tasarlanmış ve bu sebepten bir veri sözleşmesi(DataContract) olacak şekilde tanımlanmıştır.

IProductManager arayüzü(Interface);

[ServiceContract(Name="Urun Servisi",Namespace="http://www.bsenyurt.com/UrunServisi")]
public interface IProductManager
{
    [OperationContract]
    Product GetProduct(int productId);
}

Servis sözleşmesi(Service Contract) Product tipinden nesne örnekleri döndüren tek bir operasyon tanımı içermektedir.

ProductManager sınıfı;

public class ProductManager
            : IProductManager
{
    #region IProductManager Members

    public Product GetProduct(int productId)
    {
        Product prd = null;
        using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
        {
            SqlCommand cmd = new SqlCommand("Select ProductId,Name,ListPrice From Production.Product Where ProductId=@PrdId", conn);
            cmd.Parameters.AddWithValue("@PrdId", productId);
            conn.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            if (reader.Read())
                prd = new Product { ProductId = Convert.ToInt32(reader["ProductId"]), Name = reader["Name"].ToString(), ListPrice = Convert.ToDouble(reader["ListPrice"]) };
            reader.Close();
        }
        return prd;
    }

    #endregion
}

ProductManager sınıfında yazılan GetProduct metodu, parametre olarak gelen değere göre Production.Product tablosundan bir satır verisini çekmektedir. Eğer parametre olarak gelen id değerine bağlı bir satır varsa ProductId,Name,ListPrice değerlerinin toplandığı Product nesne örneği geri döndürülmektedir. Sınıf kütüphanesinin tanımlanmasından sonra host servis uygulamasının yazılması gerekmektedir. Örnekte Host, basit bir Console uygulaması olacak şekilde aşağıdaki gibi tasarlanmıştır.

Program sınıfı içeriği;

using System;
using System.ServiceModel;
using ProductServices;

namespace Sunucu
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(ProductManager));
            host.Open();
            Console.WriteLine("Sunucu dinlemede...");
            Console.ReadLine();
            host.Close();
        }
    }
}

App.config içeriği;

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="UrunServisiBehavior">
                    <serviceMetadata/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="UrunServisiBehavior" name="ProductServices.ProductManager">
                <endpoint address="" binding="netTcpBinding" bindingConfiguration="" name="UrunServisiTcpEndPoint" contract="ProductServices.IProductManager" />
                <endpoint address="mex" binding="mexTcpBinding" name="UrunServisiMexEndPoint" contract="IMetadataExchange" />
                <host>
                    <baseAddresses>
                        <add baseAddress="net.tcp://localhost:9001/UrunServisi" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

İlerlemeden önce App.config dosyası içeriğinin incelenmesinde yarar vardır. Servis uygulaması iki adet EndPoint sunmaktadır. UrunServisiTcpEndPoint isimli servis noktası TCP(netTcpBinding bağlayıcısını kullandığına dikkat edelim) bazlı olacak şekilde bir yayınlama yapmaktadır. address bilgisi verilmemiş olmasına rağmen host elementi içerisinde tanımlanan baseAdress bilgisi kullanılmaktadır. Diğer EndPoint ise IMetadaExchange arayüzünü servis sözleşmesi olarak kullanan ve mexTcpBinding bağlayıcısını baz alarak mex isimli bir adres üzerinden yayınlama yapan bir servis noktası sunmaktadır. Bu EndPoint sayesinde baseAddress elementi ile bildirilen TCP adresi üzerinden Metadata Exchange işlemi gerçekleştirilebilir. Bir başka deyişle çalışmakta olan servis uygulamasına bağlı adres üzerinden proxy nesnesi üretimi gerçekleştirilebilir. Önemli olan noktalardan biriside proxy üretiminin sağlanmasıdır. Bu nedenle servise metadata yayınlaması için bir servis davranışı(Service Behavior) eklenmiştir. Böylece servis operasyonlar servis dışına sunulabilir hale gelmiştir.

WF uygulaması, dışarıdan bir WCF servisini çağırmak için proxy nesnesinden yararlanmaktadır. Bu proxy nesnesi Visual Studio 2008 ortamında Add Service Reference ile kolay bir şekilde eklenebileceği gibi svcutil aracı yardımıylada aşağıdaki şekilde olduğu gibi çekilebilirde.

Hangisi tercih edilirise edilsin örneğe göre servis uygulamasının çalışır durumda olması gerekmektedir.

Artık WF uygulamasının yazılmasına başlanabilir. Örnekte Sequential Workflow Console Application şablonu kullanılmaktadır. Proje açıldıktan sonra servis uygulamasının çalışıyor olmasına dikkat edereken, Add Service Reference seçeneği ile proxy sınıfının üretilmesi ve WF uygulamasına eklenmesi sağlanabilir.

Bu işlemin arkasından WF uygulaması içerisinde aşağıdaki gibi servisa ait bilgilerin indirildiği ve proxy sınıfının(Reference.cs içerisinde yer almaktadır) üretildiği görülebilir.

Örnekte WF içerisinden servisi çağırmak için SendActivity tipi kullanılacaktır. İlk olarak Workflow1 tasarım ortamına SendActivity bileşeni sürüklenmelidir.

SendActivity bileşeninin önemli olan bazı üyeleri vardır. Bunlardan ServiceOperationInfo özelliği yardımıyla etkileşimde bulunulacak olan servisin ve ilgili operasyonunun seçilmesi sağlanır. AfterResponse alanının işaret ettiği olay sayesinde, servis tarafından cevap geldikten sonra işletilmesi istenen kodların icra edilmesi sağlanmaktadır. Benzer şekilde BeforeSend alanının işaret ettiği olay sayesinde, servise mesaj gönderilmeden önce işletilmesi gereken bir kod mantığı var ise, bunun icra edilmesi sağlanmaktadır. İstenirse özel servis adresleri CustomAdress özelliği ile tanımlanabilir. Örnekte ilk olarak ServiceOperationInfo özelliğinden yararlanılarak kullanılacak operasyon seçilmelidir.

Import düğmesine basıldıktan sonra ekrana gelen ara birimde, projeye az önce referans edilmiş olan serviste görülecektir(ServiceReference1). Bu işlemin hemen arkasından UrunServisi tipine çift tıklanırsa aşağıdaki ekran görüntüsü elde edilir.

Görüldüğü gibi servise ait GetProduct operasyonu eklenmiştir. Parameters kısmında, operasyonun aldığı ve geri döndürdüğü değişkenlere ait bir takım bilgiler yer almaktadır. Buna operasyon productId isimli Int32 tipinden bir parametre almakta ve Product tipinden bir sonuç döndürmektedir. OK düğmesine basıldıktan sonra Parameters kısmında yer alan değişkenlerin Properties penceresine birer özellik olaraktan eklendiği izlenebilir.

Bu özelliklerin yanında yer alan üç nokta düğmelerine basıldığında, ilgili alanların servis operasyonuna bağlanması için gerekli özellik/alan(Property/Field) tanımlamalarının yapılacağı bir ekran ile karşılaşılır. Bu ekrandan yararlanılarak Workflow sınıfı içerisinde yazılmış var olan özelliklere/alanlara bağlama yapılabileceği gibi Bind to a new member sekmesinden faydalanılarak yeni özelliklerin anında oluşturulmasıda sağlanabilir. Örneğin ReturnValue özelliği için aşağıdaki ekran görüntüsünde yer alan seçimler kullanılmış ve anında sendActivity1_ReturnValue1 isimli bir üyenin oluşturulması sağlanmıştır.

Aynı işlem productId isimli aktivite özelliği içinde yapılmalıdır. Bu işlemlerin ardından Workflow1 sınıfı içerisine DependencyProperty tipinden iki yeni özelliğin aşağıdaki gibi eklendiği görülür.

namespace WfdenServis
{
    public sealed partial class Workflow1: SequentialWorkflowActivity
    {
        public Workflow1()
        {
            InitializeComponent();
        }

        public static DependencyProperty sendActivity1__ReturnValue_1Property = DependencyProperty.Register("sendActivity1__ReturnValue_1", typeof(WfdenServis.ServiceReference1.Product), typeof(WfdenServis.Workflow1));

        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [CategoryAttribute("Parameters")]
        public WfdenServis.ServiceReference1.Product sendActivity1__ReturnValue_1
        {
            get
            {
                return ((WfdenServis.ServiceReference1.Product)(base.GetValue(WfdenServis.Workflow1.sendActivity1__ReturnValue_1Property)));
            }
            set
            {
                base.SetValue(WfdenServis.Workflow1.sendActivity1__ReturnValue_1Property, value);
            }
        }

        public static DependencyProperty sendActivity1_productId1Property = DependencyProperty.Register("sendActivity1_productId1", typeof(System.Int32), typeof(WfdenServis.Workflow1));

        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(true)]
        [CategoryAttribute("Parameters")]
        public Int32 sendActivity1_productId1
        {
            get
            {
                return ((int)(base.GetValue(WfdenServis.Workflow1.sendActivity1_productId1Property)));
            }
            set
            {
                base.SetValue(WfdenServis.Workflow1.sendActivity1_productId1Property, value);
            }
        }
    }
}

Son olarak SendActivity bileşeninin ChannelToken özelliği ayarlanarak EndPoint bilgilerinin verilmesi ve WF' in hangi servis ile nasıl mesajlaşacağının ele alınması gerekmektedir. Bu bilgiler çok doğal olarak Add Service Reference işlemi sonrası gelen App.config dosyası içerisinde yer almaktadır. Bu amaçla ChannelToken özelliğine bir isim verildikten sonra gelecek olan EndpointName özelliğine App.config dosyası içerisindeki ilgili servis noktasının adı yazılmalıdır. Sonrasında ise ChannelToken nesnesinin kapsamı(Scope) belirlenir. Bu kapsam OwnerActivityName özelliği yardımıyla set edilmektedir. Örnekte söz konusu özellikler aşağıdaki ekran görüntüsünde olduğu gibi belirlenebilir.

Artık test işlemleri başlatılabilir. Console formatında bir Workflow uygulaması geliştirildiğinden Main metodu içerisinde gerekli parametre tanımlama ve sonuç alma işlemleri kolaylıkla gerçekleştirilebilir. Bu amaçla Main metodu aşağıdaki gibi genişletilebilir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using WfdenServis.ServiceReference1;

namespace WfdenServis
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
                    {
                        waitHandle.Set();
                        Product result = e.OutputParameters["sendActivity1__ReturnValue_1"] as Product;
                        if(result!=null)
                            Console.WriteLine("{0} : {1} {2}", result.ProductId.ToString(), result.Name, result.ListPrice.ToString("C2"));
                        else
                            Console.WriteLine("Ürün bulunamadı");
                    };

                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                    {
                        Console.WriteLine(e.Exception.Message);
                        waitHandle.Set();
                    };

                    Dictionary<string, object> parametreler = new Dictionary<string, object>() { { "sendActivity1_productId1", 680 } };
                    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WfdenServis.Workflow1),parametreler);
   
                    instance.Start();
       
                    waitHandle.WaitOne();
            }
        }
    }
}

Bilindiği gibi WorkflowInstance nesne örneği oluşturulurken Dictionary<string,object> tipinden olan ikinci parametre ile iş akışı içerisindeki özelliklere değer aktarımı gerçekleştirilebilmektedir. Bu sebepten örnekte parametreler isimli Dictionary<string,object> tipinden bir koleksiyon tanımlanmış ve sendActivity1_productId1 ismi ile 680 değeri verilmiştir. sendActivity1_productId1 isimli özellik hatırlanacağı gibi SendActivity bileşeninin bir parçasıdır ve GetProduct metodunun aldığı productId alanına eş düşmektedir. Bu sayede servis tarafındaki ilgili operasyona parametre gönderimi gerçekleştirilmektedir. Servis tarafındaki işlemler sonlandığında tasarlanan iş akışına göre otomatik olarak WorkflowCompleted olayı tetiklenmektedir. Bu olayın WorkflowCompletedEventArgs tipinden olan e parametresine ait OutputParameters özelliğinin işaret ettiği koleksiyon, iş akışına ait tüm özelliklere ulaşılabilmesini sağlamaktadır ki bunlardan biriside GetProduct isimli servis metodunun dönüş değerini işaret eden ve SendActivity bileşenine ait olan sendActivity1_ReturnValue_1 isimli anahtardır(key). Bu anahtarın sonucu Product tipinden olacağı için as anahtar kelimesi ile bir dönüştürme işlemi yapılmakta ve sonuç null değilse elde edilen değişkene ait bilgiler ekrana yazdırılmaktadır. Uygulamanın test edilebilmesi için öncelikli olarak servis tarafının çalışıyor olması gerekmektedir. Buna göre iş akışı uygulamasının 680 productId değeri için vereceği çıktı aşağıdaki gibi olacaktır.

Elbette tasarlanan akışın tipine bağlı olaraktan, servise ait operasyonların adımların arasında herhangibir yerde tetiklenmesi ve arkasından sonuçların alınması için başka aktivitelerin(örneğin CodeActivity) iş akışına eklenmeside söz konusudur. Söz gelimi yazılan son örnekte ek bir CodeActivity bileşeni kullanılabilir. Bunun için tasarım zamanında aşağıdaki gibi bir CodeActivity bileşeni eklendiğini ve ExecuteCode özelliğindede UrunuGetir isimli bir metod bildirimi yapıldığını varsayalım.

Buna göre UrunuGetir metodunun içeriği aşağıdaki gibi tasarlanıp SendActivity çağrısından sonra GetProduct operasyonunun sonuçlarının alınması sağlanabilir. (Burada sendActivity1_ReturnValue_1 değerine özelliğine doğrudan erişilmesi son derece doğaldır nitekim metodun tanımlandığı yer Workflow1 sınıfının içidir.)

private void UrunuGetir(object sender, EventArgs e)
{
    WfdenServis.ServiceReference1.Product prd = sendActivity1__ReturnValue_1 as WfdenServis.ServiceReference1.Product;
    Console.WriteLine(prd.Name + " " + prd.ListPrice.ToString("C2"));
}

Görüldüğü gibi SendActivity bileşeni sayesinde bir iş akışı nesnesi içerisinden WCF tabanlı servis çağırılması, bu servisten sunulan operasyonların icra edilmesi, varsa operasyon sonuçlarının alınması gibi aksiyonlar kolay bir şekilde gerçekleştirilebilmektedir. Bu yazıda bir WCF servisinin, WF içerisinden nasıl çağırılabileceğinin temelleri üzerinde durulmuş ve basit bir örnek adım adım işlenmeye çalışılmıştır. Bir sonraki makalede ise bir WF uygulamasının servis olarak nasıl sunulacağı konularına değinilmeye çalışılacaktır. Böylece geldik bir makalemizin daha sonunda. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

WFileWCF.rar (77,62 kb)

Tags:   ,
Categories:   WCF | WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

Adım Adım State Machine Workflow Geliştirmek

Salı, 15 Ocak 2008 05:27 by bsenyurt

Öyle iş akışları vardırki, süreç(Process) içerisinde yer alan adımlar arasındaki geçişler herhangibir zamanda ve herhangibir olayın meydana gelmesi sonrasında mümkün olur. Çoğunlukla terminolojide Sonlu Durum Makinesi(Finite State Machine) olarak geçen bu yaklaşıma göre, herhangibir nesnel varlığın zaman içerisinde sahip olabileceği durumlar işaret edilmektedir. Çok doğal olarak bu durum, programatik ortamda yer alan iş problemlerinin çözümündede göz önüne alınmaktadır. İşte bu makalemizde Sonlu Durum Makinesi(Finite State Machine) kavramını irdelemeye ve Windows Workflow Foundation içerisindeki kullanımını araştırmaya çalışacağız. Başlamadan önce Sonlu Durum Makinesi(Finite State Machine) kavramını anlamaya çalışamakta yarar vardır.

Öncelikli olarak sonlu kelimesinin kullanılmasının sebebi söz konusu nesnel varlığın sahip olabileceği durumların(State) sayılı olmasıdır. Bir başka deyişle bu yaklaşıma göre bir makinenin sahip olabileceği durumların sayısı bellidir. Diğer taraftan makinenin zaman içerisinde sahip olabileceği haller onun durumlarını(States) ifade etmektedir. Makine doğal olarak bu durumlara sahip olan nesnel yapıyı temsil etmektedir. Sonlu durum makinelerinde(Finite State Machine) durumlar arasındaki geçişler bir aksiyon sonucu gerçekleşir. Tahmin edileceği üzere söz konusu aksiyonlar çoğunlukla bir olaydır ve genellikle insan etkileşimi sonrası gerçekleşmektedir. Ne varki burada insan etkileşimi sonucu olay tetiklenmesi zorunluluk değildir. İnsan etkileşimi olması nedeniyle, olaylar herhangibir zaman dilimi içerisinde meydana gelebilir. Bu tip vakalara mühendislik eğitimlerinde verilmekte olan iki basit örnek vardır. Hepimizin yakından tanıdığı, metro istasyonlarında, duraklarda, okullarda veya bazı kafelerde gördüğümüz otomat makineleri ile herkese açık olan çamaşırhanelerde yer alan ve jeton ile çalışan yıkama makineleri bu iki örneği oluşturmaktadır.

NOT : Finite State Machine' lerde insan ile olan etkileşim ön planda olup, durumlar(States) arası geçişler çoğunlukla insanlar tarafından tetiklenen olaylara(Events) bağlıdır.

Otomat makinesinin kendisi göz önüne alındığında zaman içerisinde sahip olabileceği bazı durumlar(State) vardır. Örneğin makine çalışmıyordur, çalışıyordur, para veya jeton bekliyordur, seçilen ürünü veriyordur yada tadilattadır. Burada bahsedilen hallerin tamamı birer durum(State) olarak irdelenir. Bu durumlar arasındaki geçişler için çoğunlukla makinenin bazı araçları insanlar tarafından kullanılır. Dikkat edilecek olursa makine zaman içerisinde herhangibir anda herhangibir durumuna geçebilir. Tabiki bazı durumlara geçişler sırasında bazı koşulların sağlanması gerekebilir. Benzer senaryo yıkama makinesi örneği içinde geçerlidir. (İlerlemeden önce para veya jeton ile çalışan bir çamaşır makinesinin zaman içinde sahip olabileceği durumları ve bu durumlar arasındaki geçişler için gereken olayların neler olabileceğini kağıt üzerinde tasarlamaya çalışmanızı öneririm.)

Finite State Machine' ler otomat, çamaşır makinesi gibi gerçek hayat örnekleri dışında üretim hattında yer alan sanayi makinelerinin otomasyon süreçlerinde, oyun programlamada yer alan karakterlerin zaman içerisindeki hareket dağılımlarında, transaction içerisinde çalışan bir para aktarma veya kredilendirme sürecinde ve benzer senaryolarda göz önüne alınabilir. Modelin böylesine popüler olması, özellikler programatik ortamlarda kolay bir şekile anlaşılabilmelerini sağlamak amacıyla UML formasyonunda da ifade edilmesini gerektirmiştir. Söz gelimi aşağıdaki ekran görüntüsünde otomat makinesinin Finite State Machine diagramı yer almaktadır.

Normal şartlarda bu diagramlarda ekranda görülen kırmızı baloncuklar elbetteki yer almaz. Herşeyden önce Finite State Machine içerisinde yer alan makinenin mutlaka bir başlangıç durumu(Initial State) ve son durumu(Finalization State veya Terminal State olarak adlandırılır- iç içe iki yuvarlağın olduğu parça ile ifade edilir) vardır. Başlangıç durumunda makinenin ilk konumdaki hali göz önüne alınır. Son durumda ise makinenin var olan durumlar sona erdikten sonraki hali ele alınmaktadır. Şekilde başlangıç durumundan, Para Bekleniyor isimli duruma geçiş yapılabilmektedir. Bu geçiş için gereken, makineyi kullanan kişinin para atmasıdır. Para Bekleniyor durumuda, para yeterli oluncaya kadar kendisine geçiş yapılmasına sağlayacak şekilde bir olaya sahiptir. Para yeterli olduktan sonra ise Ürün Seçimi Bekleniyor isimli duruma geçiş yapılabilir. Burada ise kişi ürünü seçmekte ve sonrasında makine seçilen ürünü hazırlayarak Bitiş durumuna girmektedir. Bu senaryoda pek çok durum göz ardı edilmiştir. Örneğin seçim yapıldıktan sonra makinenin arıza yapıp ürünü vermemesi halinde girilecek durum veya para üstü verme durumları gibi.

Hemen ikinci bir örnek ile devam edelim. Söz gelimi bir Windows uygulaması üzerinden kontrol edilen uzaktan kumandalı bir otomobilin sahip olabileceği durumlara ait bir Finite State Machine diagramı söz konusu olabilir. Bu diagram aşağıdakine benzer bir şekilde ele alınabilir.

Burada otomobilin kendisi programatik ortamda bir nesne ile ifade edilebilir. Durumlar(States) arasındaki geçişleri sağlayan ise Windows uygulamasından tetiklenen nesne olayları(Events) olabilir. Arabanın şekle göre sahip olabileceği durumlar belirlidir. Bu durumlarda söz konusu olabilecek olaylarda aynı şekilde belirli ve sayılıdır. Bir durumdan başka bir durumua geçiş veya geçişlerde birden fazla sayıda olay söz konusu olabilir. Söz gelimi Motor Çalışıyor durumundayken, OnIlerle, OnMotoruDurdur veya OnGeriGit gibi olaylar tetiklenerek üç farklı duruma geçiş yapılması sağlanabilir. Buda bize durumlar arasındaki geçişlerde birden fazla olayın söz konusu olabileceğini göstermektedir. Hatta bazı noktalarda ortak olaylarda söz konusudur. Örneğin araba ileri veya geri giderken OnDur olayı tetiklenerek durması sağlanabilir. OnDur olay hem Geri Gidiyor hemde Ilerliyor durumları(States) için geçerli ortak bir olaydır.

Sanıyorumki buraya kadar anlatılanlar sayesinde Finite State Machine yaklaşımı hakkında biraz fikir sahibi olunmuştur. Bundan sonraki kısımlarımızda ise .Net Framework 3.0 ile gelen Windows Workflow Foundation açısından Finite State Machine modeline bakıyor olacağız. Nitekim söz konusu model gerçek hayat örneklerine benzer olacak şekilde programatik ortamdaki nesnel yapılar içinde söz konusu olabilmektedir. Bu noktada WWF bize kolaylaştırıcı bir yaklaşım sunmakta ve State Machine tarzı iş süreçlerinin .Net uygulamalarında rahatça ele alınabilmelerine olanak tanımaktadır.

Windows Workflow Foundation(WWF) iki temel iş akışı modelini ele alır. Sequential Workflows ve State Machine Workflows. Daha öncedende bahsedildiği gibi Sequential Workflows tipinden olan iş akışlarında adımlar yada aktiviteler arasındaki geçişlerin nasıl ve ne zaman olacağı bellidir. Hatta bu geçişler sırasında koşulların kullanılması çok sık rastlanan bir durumdur. Stata Machine Workflow tipindeki iş akışlarında ise daha öncedende değinildiği gibi adımlar veya durumlar arasındaki geçişler dış olayların tetiklenmesine bağlıdır. State Machine Workflow akışlarından, aktivitenin kendisi StateMachineWorkflowActivity sınıfından örneklenmektedir. (Workflow mimarisindeki herşeyin birer aktivite(Activity) tipi olduğunu hatırlayalım) StateMachineWorkflowActivity tipinin .Net içerisindeki yerine bakıldığında aşağıdaki sınıf diagramında(Class Diagram) yer alan hiyerarşide olduğu görülür.

Dikkat edileceği üzere StateMachineWorkflowActivite sınıfıda eninde sonunda bir Activity tipidir. Kendi içerisinde tanımlanmış olan üyelerden bir kaçını açıklayarak devam edelim. CompletedStateName özelliği ile, bitiş durumu(Finalization State) ifade edilir. Bu özellik önemlidir nitekim Workflow' un hangi durumdan sonra sonlanacağını belirtmektedir. Ancak yazılmadığı vakalarda vardır. Benzer şekilde InitialStateName özelliği başlangıçtaki durum aktivitesini işaret etmektedir. Çalışma zamanında istenirse o anda makinenin bulunduğu durum adı CurrentStateName özelliği ile elde edilebilir. Benzer şekilde PreviousStateName özelliği ile o anda bulunulan durumdan bir önceki durum adı elde edilebilir ki bu hangi durumdan gelindiğini öğrenmek için kullanılabilir. Buradaki örnek özellikler(Properties) dışında üst sınıflardan gelen pek çok üye(Member) yer almaktadır. Amacımız şu an için bu tipin tüm üyelerini öğrenmek değildir. Bunun yanında bir StateMachineWorkflowActivity tasarlanırken içerisinde çoğunlukla aşağıdaki şekilde yer alan tipler kullanılır.

Burada belkide en kritik ve değerli tip StateActivity sınıfıdır. StateActivity, aslında makinenin içerisinde bulunacağı durumları işaret etmektedir. Çok doğal olarak bir StateActivity içerisinde StateInitializationActivity yada StateFinalizationActivity tanımlanabilir. Ancak bir StateActivity içerisinde bunlardan sadece bir tane bulunabilir. Öte yandan StateActivity içerisinde, StateInitializationActivity veya StateFinalizationActivity tiplerinin tanımlanması zorunlu değildir. Bunlar opsiyonel olarak ele alınmaktadır. Durumlar(States) arasındaki geçişler için EventDrivenActivity tipi kullanılmaktadır. StateActivity içerisinde birden fazla EventDrivenActivity nesnesi tanımlanabilir. Nitekim daha öncedende bahsettiğimiz gibi, bir durumda(State) söz konu olabilecek birden fazla olay(Event) olabilir. Her EventDrivenActivity mutlaka olayları alabilecek bir aktivite tipi içerir. Bunu sağlayan HandleExternalEventActivity tipidir. Söz konusu aktivite tipini takiben herhangibir başka aktivitede gelebilir. Örneğin bir olayın tetiklenmesinin ardından host uygulama üzerinden çağırılabilecek harici metodların ele alınması, kod işletilmesi gibi işlemler yapılabilmektedir. Şekilde dikkat edileceği üzere EventDrivenActivity içerisinde son olarak SetStateActivity kullanılmaktadır. Bu tip sayesinde bulunulan durumdan diğer bir duruma geçilmesi sağlanmaktadır.

Şekilde dikkat edilmesi gereken noktalardan biriside StateActivity tipleri dışında ve StateMachineWorkflowActivity içerisinde kalan alanda EventDrivenActivity bileşenlerinin tanımlanabilmesidir. Bazı hallerde durumlar(States) arasındaki geçişlerde kullanılmayan olaylar(Events) söz konusu olabilir. Söz gelimi otomobilin selektör yapması herhangibir anda herhangibir duruma geçiş yapılmasını gerektirmeyecek bir vaka olarak ele alınır. Bu sebepten bu vakaya ilişkin olayı ele alacak EventDrivenActivity nesnesinin bir StateActivity içerisinde tanımlanmasına da gerek yoktur. 

Bazı durumlarda StateActivity bileşenleri kendi içlerindede birden fazla StateActivity içerebilir. Bu genellikle içerideki aktivitelerin aynı olayları ele aldığı durumlarda söz konusudur. Bu tip aktiviteler Recursive Compositon Activities olarakda adlandırılmaktadır. Aşağıda şekilde bu durum ele alınmaya çalışılmaktadır.

Burada her iki StateActivity tipi içerisinde yer alan HandleExternalEventActivity nesneleri aynı olay ile ilgilenmektedir. Böyle bir durumda söz konusu StateActivity aşağıdaki şekilde görüldüğü gibide tasarlanabilir. (Otomobil örneği göz önüne alındığında Geri gitme veya ileri gitme durumları içerisinden Durma durumuna geçilmesi için aynı olaylar ele alınmaktadır.)

Görüldüğü gibi StateActivity1 ve StateActivity2 nesne örnekleri genel bir StateActivity nesnesi içerisine alınmıştır. Bununla birlikte her ikisinin EventDrivenActivity nesneleri dışarı alınarak tek bir noktada toplanmıştır. Nitekim her iki alt aktivitede aynı EventDrivenActivity nesnelerini ele almaktadır.

Bu kadar teorik bilgiden sonra bir örnek yaparak devam etmekte yarar vardır. Makale yazılmadan önce yapılmış olan araştırmalarda Microsoft' un Otomat makinesi örneğini kullandığı, APress' in ise bir arabanın durumlarını ele aldığı gözlenmiştir. Bizde senaryo olarak APress tarafından ele alınan Otomobil örneğini kendimize göre adım adım geliştirmeye ve anlamaya çalışacağız. İşe ilk olarak yeni bir State Machine Workflow Library projesi açarak başlayalım. Bunun için Visual Studio 2008 ortamında New Project->WF sekmesinden ilgili proje şablonunu(Project Template) seçmemiz yeterlidir.

Proje oluşturulduğunda otomatik olarak Workflow1.cs dosyası tasarım penceresinde açılacak ve aşağıdaki ekran görüntüsü oluşacaktır.

Burada görüldüğü gibi Workflow1.cs içerisinde varsayılan olarak bir InitialState bileşeni bulunmaktadır. Şimdi örneğin temasını oluşturan arabanın zaman içerisindeki durumları göz önüne alınabilir. Bu durumları listeledikten sonra ise gerekli tiplerin hazırlanmasına başlanabilir. Herşeyden önce arabanın zaman içerisindeki durumları arasındaki geçişleri sağlayacak olan olayların veri değişimi sağlayacak şekilde tasarlanmış bir arayüz(Interface) içerisinde yer alması sağlanmalıdır. Bu amaçla projeye aşağıda sınıf diagramı(Class Diagram) ve kod çıktısı yer alan arayüz(Interface) eklenir.

[ExternalDataExchange]
public interface IArabaHizmetleri
{
    event EventHandler<ExternalDataEventArgs> ArabayiCalistir;
    event EventHandler<ExternalDataEventArgs> MotoruDurdur;
    event EventHandler<ExternalDataEventArgs> Dur;
    event EventHandler<ExternalDataEventArgs> Ilerle;
    event EventHandler<ExternalDataEventArgs> GeriGit;
    event EventHandler<ExternalDataEventArgs> ArabadanCik;
    event EventHandler<ExternalDataEventArgs> SelektorYap;

    void OnMesajGonder(string message);

Bu arayüz(Interface) basit olarak iş akışına yerel bir servis(Local Service) üzerinden sunulabilecek üye bildirilmlerini içermektedir ki bunlar çoğunlukla olay ve metod tanımlamalarıdır. Diğer taraftan arayüz tipi ExternalDataExchange niteliği(attribute) ile işaretlenmiştir. Bu niteliğin(Attribute) uygulanması sayesinde arayüz tipi, iş akışları tarafından yerel bir servis(Local Service) olarak kullanılabilir hale gelir. Arayüz(Interface) içerisinde durum geçişleri(State Transitions) için gerekli temel olay tanımlamaları yer almaktadır. Örneğin arabanın ilerlemesi için Ilerle yada geriye gitmesi için GeriGit olaylarına ait bildirimler bulunmaktadır. Bununla birlikte arayüz, OnMesajGonder isimli bir metod bildirimi de içermektedir. Bu metod iş akışı(Workflow) tarafından, host uygulamaya mesaj göndermek amacıyla kullanılacaktır.

NOT : Bilindiği gibi arayüzler(Interface), sadece üye bildirimleri içeren tiplerdir. Polimorfik yapıları vardır ve çoklu kalıtıma(Multi Inheritance) destek verirler. Çoğunlukla türetme(Inheritance) için kullanılır ve türeyen üyelerin mutlaka uyması gereken kuralları bildirirler. Plug-In tabanlı programlamada, tip genişletmelerinde, ortak sözleşmelerin sunulmasında(Söz gelimi WCF gibi SOA-Service Oriented Architecture mimarilerinde) vb... gibi senaryolarda sıklıkla kullanılırlar.

Arayüzün tanımlanmasından sonra bunu uygulayan sınıfın tasarlanması gerekmektedir. Bu sınıf(Class) aynı zamanda yerel bir servis(Local Service) olacaktır. Söz konusu sınıf ve MesajGonder için kullanılan yardımcı olay parametresinin içeriği aşağıdaki gibidir.

MesajAlindiEventArgs sınıfı;

[Serializable]
public class MesajAlindiEventArgs : ExternalDataEventArgs
{
    private string _bilgi;

    public string Bilgi
    {
        get { return _bilgi; }
        set { _bilgi = value; }
    }
    public MesajAlindiEventArgs(Guid ornekId, string bilgi)
            : base(ornekId)
    {
        _bilgi = bilgi;
    }

ExternalDataEventArgs sınıfının tüm yapıcı metod(Constructor Method) versiyonları Guid tipinden bir ilk parametre alırlar. Bu sebepten base anahtar kelimesi kullanılarak MesajAlindiEventArgs sınıfına gelen Guid değerinin üst sınıf örneğine gönderilmesi sağlanmaktadır.

ArabaYerelServisi sınıfı;

public class ArabaYerelServisi :IArabaHizmetleri
{
    #region IArabaHizmetleri Members

    public event EventHandler<ExternalDataEventArgs> ArabayiCalistir;
    public event EventHandler<ExternalDataEventArgs> MotoruDurdur;
    public event EventHandler<ExternalDataEventArgs> Dur;
    public event EventHandler<ExternalDataEventArgs> Ilerle;
    public event EventHandler<ExternalDataEventArgs> GeriGit;
    public event EventHandler<ExternalDataEventArgs> ArabadanCik;
    public event EventHandler<ExternalDataEventArgs> SelektorYap;

    public void OnMesajGonder(string message)
    {
        if (MesajAlindi != null)
        {
            MesajAlindiEventArgs args = new MesajAlindiEventArgs(WorkflowEnvironment.WorkflowInstanceId, message);
            MesajAlindi(this, args);
        }
    }

    #endregion
   
    #region Host uygulama tarafından kullanılan üyeler
   
    public event EventHandler<MesajAlindiEventArgs> MesajAlindi;

    public void OnArabayiCalistir(ExternalDataEventArgs args)
    {
        if (ArabayiCalistir != null)
            ArabayiCalistir(null, args);
    }

    public void OnMotoruDurdur(ExternalDataEventArgs args)
    {
        if (MotoruDurdur != null)
            MotoruDurdur(null, args);
    }

    public void OnDur(ExternalDataEventArgs args)
    {
        if (Dur != null)
            Dur(null, args);
    }

    public void OnIlerle(ExternalDataEventArgs args)
    {
        if (Ilerle != null)
            Ilerle(null, args);
    }

    public void OnGeriGit(ExternalDataEventArgs args)
    {
        if (GeriGit != null)
            GeriGit(null, args);
    }

    public void OnSelektorYap(ExternalDataEventArgs args)
    {
        if (SelektorYap != null)
            SelektorYap(null, args);
    }

    public void OnArabadanCik(ExternalDataEventArgs args)
    {
        if (ArabadanCik != null)
            ArabadanCik(null, args);
    }

    #endregion
}

ArabaYerelSinifi isimli sınıf(Class), ilgili arayüzü(Interface) uygulamak dışında Host uygulama tarafından tetiklenebilecek metodlarda içermektedir. Bu metodlar kendi içlerindende akışa ait durum geçişleri için gerekli olayların tetiklenmesinde kullanılmaktadır. Sınıf içerisinde yer alan OnMesajGonder isimli metod parametre olarak string tipinden bir değişken almaktadır. Bu parametre değeri iş akışından(Workflow) gelmekte olup Host uygulamaya iletilmektedir. Bu iletim sırasında MesajAlindi isimli olay devreye girmektedir ki bu olay sadece iş akışını barındıran Host uygulama tarafından ele alınabilir. Tahmin edileceği üzere OnMesajGonder metodunun parametre değeri iş akışı tasarlanırken belirlenecektir. MesajAlindi olayı tetiklenirken parametre olarak ExternalDataEventArgs sınıfından türemiş olan MesajAlindiEventArgs sınıfı kullanılmaktadır.

Bu işlemlerin tamamlanmasının ardından Durum Makinesinin(State Machine) tasarlanmasına başlanabilir. Bu amaçla Workflow1.cs üzerinden gerekli düzenlemelerin yapılması gerekmektedir. İlk olarak arabanın sahip olabileceği tüm durumlar StateActivity bileşenleri yardımıyla iş akışı üzerine alınırlar. Başlangıçta StateActivity bileşenlerinin Name özelliklerinin değerlerinin aşağıdaki tabloda yer aldığı gibi değiştirildiğini düşünelim.

StateActivity Bileşeni
Name Özelliği Değeri
Kısa Bilgi
MotorCalismiyor Arabanın motorunun çalışmadığı durumu işaret eder.
MotorCalisiyor Arabanın motorunun çalışmak olduğu durumu işaret eder.
ArabaIlerliyor Arabanın ileri doğru hareket ettiği durumu işaret eder.
ArabaGeriGidiyor Arabanın geriye doğru hareket ettiği durumu işaret eder.
ArabadanCikilmistir Arabadan inildikten sonraki durumu işaret eder.

Bunun sonrasında iş akışına(Workflow) ait ekran görüntüsü aşağıdaki gibi olacaktır.

Son işlemleri takiben iş akışının başlangıç ve bitiş durumları belirlenebilir. Bunun için Workflow1' in özellikler(Properties) penceresinden InitialStateName ve CompletedStateName özelliklerine ilgili değerlerin verilmesi gerekmektedir. Senaryo gereği MotorCalismiyor başlangıç ve ArabadanCikilmistir bitiş durumlarını(State) bildirmektedir.

Artık ilk durumdan diğerine geçisi sağlayacak olan olay aktivitesi tanımlanabilir. Bu amaçla MotorCalismiyor isimli StateActivity içerisine bir adet EventDrivenActivity bileşeni sürüklenir. EventDrivenActivity bileşeninin Name özelliğine MotorCalistirOlayi adı verilebilir. Sonuç olarak ekran görüntüsü aşağıdaki gibi olacaktır.

Daha öncedende bahsettiğimiz gibi EventDrivenActivity içerisinde genel olarak 3 farklı aktivite kullanılır. Bu senaryoda söz konusu olayın tetiklenmesi için HandleExternalEventActivity bileşeni ele alınmalıdır. Diğer taraftan yerel servis üzerinden harici metod çağrısı için CallExternalMethodActivity bileşeni değerlendirilir. Son olarak olayın tetiklenmesi sonrası geçilecek olan durumu işaret etmek için SetStateActivity bileşeni kullanılmalıdır. EventDrivenActivity bileşeni içerisine bahsedilen kontrolleri oluşturmak için MotorCalistirOlayi üzerinde çift tıklanması yeterlidir. Sonuç olarak ilk durum için tasarlanan EventDrivenActivity bileşeninin içeriği aşağıdaki ekran görüntüsünde yer aldığı gibi olacaktır.

İlk olarak HandleExternalActivity bileşeni ile başlayalım. Bu bileşenin InterfaceType özelliğine tetiklenecek olan olayın bildirimini içeren arayüz adı verilmelidir. Bu amaçla üç nokta düğmesine basıldığında aşağıdakine benzer bir arabirim ile karşılaşılır. Bu arabirimden aynı proje içerisindeki veya farklı bir projedeki ExternalDataExchange niteliğini uygulayan arayüzler görülebilir. Örneğimizdede IArabaHizmetleri arayüzü aktif olarak gelmektedir.

Arayüz seçimi yapıldıktan sonra EventName özelliğinde ele alınabilecek olan, bir başka deyişle interface tipi içerisinde bildirilmiş olan olayların listesi gelecektir. Örneğimizdeki ilk durum için ArabayiCalistir olayı seçilmelidir. EventDrivenActivity için söz konusu olan durum aşağıdaki gibidir.

Gelelim CallExternalMethodActivity bileşenine. Bu bileşen içinde yine arayüz(Interface) seçimi yapılmalıdır. InterfaceType özelliğine yapılan atamanın ardından çalıştırılacak olan harici metodun adı MethodName özelliğinden seçilir. Örnekte harici metodun aldığı string bir parametrede söz konusudur. Bu parametrede message isimli özelliğe atanan değer ile belirtilir.

Burada dikkat edilmesi gereken noktalardan biriside, OnMesajGonder metodunun parametrik yapısına uygun olacak şekilde bir özelliğin IDE' deki Properties penceresine eklenmiş olmasıdır. Örnekte message isimli olan parametre, özellik penceresine birer bir aynı olacak şekilde gelmiştir. Harici metod çağırılmasınıda tamamladıktan sonra geçilecek olan durum bileşenini belirlemek gerekmektedir. Bunun içinde SetStateActivity bileşeninin TargetStateName özelliğine StateActivity adının atanması yeterlidir. İlk durumda aracın motoru çalıştırıldıktan sonra MotorCalisiyor durumuna(State) geçilmektedir.

İstenirse ilk durum için StateInitializationActivity bileşeni de eklenebilir. Böylece makinenin ilk konumdaki durumu için gerekli hazırlıkların yapılması sağlanabilir. Söz gelimi örnek senaryoda OnMesajGonder metodunun harici olarak çağırılması ve mesaj olarakta "Araba hazır" denilmesi sağlanabilir. Bunun için MotorCalismiyor aktivitesi içerisine bir adet StateInitializationActivity bileşeni atanması ve bu bileşenin içerisinede bir adet CallExternalMethodActivity bileşeni eklenerek InterfaceType, MethodName ve message özelliklerinin değerlerinin belirlenmesi yeterlidir.

Böylece ilk durum tamamıyle hazırdır. Akışın şu andaki görüntüsü aşağıdaki gibi olacaktır.

Burada yapmış olduğumuz adımların aynılarını diğer durumlar içinde gerçekleştirmeliyiz. Makalemizin dahada uzamaması için buradaki adımların gösterilmesini atlıyoruz. Gereken ayarlamalar yapıldıktan sonra iş akışının son hali aşağıdaki ekran görüntüsündeki gibi olmalıdır. Dikkat edileceği üzere olası durum geçişleri ince ok çizgiler ile daha belirgin haldedir.

Burada ekstradan selektör yapma durumununda, State Machine üzerinde ayrı bir EventDrivenActivity olarak tanımlanması gereklidir. Bu aktivite içerisinde sadece HandleExternalEventActivity ve CallExternalMethodActivity bileşenlerinin kullanılması yeterlidir. Dikkat edileceği üzere SetStateActivity kontrolünün kullanılması gerekli değildir. Nitekim selektör yapma olayının arkasından geçilecek herhangibir durum göz önüne alınmamaktadır. Bu nedenle SelektorYapOlayı isimli EventDrivenActivity içerisinde aşağıdaki bileşenlerin tasarlanması yeterlidir.

Host uygulamaya geçmeden önce şu durumda göz önüne alınmalıdır. Tasarım penceresine bakıldığında ArabaGeriGidiyor ve ArabaIlerliyor StateActivity bileşenleri içerisinde aynı EventDrivenActivity nesnelerinin kullanıldığı görülmektedir ki buda aracı durdurma olayını işaret etmektedir. Bu nedenle makalemizin başındada belirttiğimiz gibi bu StateActivity bileşenlerinin ortak bir StateActivity içerisine alınması düşünülebilir. Bu sebepten tasarım ekranına ortak bir StateActivity bileşeni sürüklenip, diğerlerini içine alması sağlanmalıdır. Aşağıdaki ekran görüntüsü bu durumu açık bir şekilde ifade etmektedir.

Öncelikli olarak HareketEdiyor isimli StateActivity içerisine ArabaGeriGidiyor ve ArabaIlerliyor isimli StateActivity nesneleri sürüklenmiştir. Sonrasında ise bunlardan herhangibirisinde yer alan ArabayiDurdurOlayi isimli EventDrivenActivity bileşeni HareketEdiyor isimli StateActivity içerisine çıkartılmış ve diğerininki silinmiştir.

Artık host uygulamanın yazılmasına başlanabilir. Örneğimizde host uygulaması basit bir WPF(Windows Presentation Foundation) programı olarak tasarlanacaktır. Söz konusu WPF uygulamasının State Machine Workflow kütüphanesi(Library) dışında, System.Workflow.Activities, System.Workflow.Components ve System.Workflow.Hosting assembly' larınıda referans etmesi gerekmektedir. YarisPisti isimli WPF uygulamamızın Window1 penceresine ait ekran görüntüsü ve XAML(eXtensible Application Markup Language) içeriği ise aşağıdaki gibidir.

XAML içeriği;

<Window x:Class="YarisPisti.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300">
    <Grid>
        <Label Height="42" Margin="28,0,22,32" Name="lblGelenMesaj" VerticalAlignment="Bottom" Content="Durum Bilgisi"></Label>
        <Button Height="23" HorizontalAlignment="Left" Margin="26,25,0,0" Name="btnYeniAraba" VerticalAlignment="Top" Width="88" Click="btnYeniAraba_Click">Yeni Araba</Button>
        <Button Height="23" Margin="26,60,0,0" Name="btnMotoruCalistir" VerticalAlignment="Top" Click="btnMotoruCalistir_Click" HorizontalAlignment="Left" Width="88">Çalıştır</Button>
        <Button Height="23" HorizontalAlignment="Left" Margin="28,97,0,0" Name="btnMotoruKapat" VerticalAlignment="Top" Width="86" Click="btnMotoruKapat_Click"> Motoru Kapat</Button>
        <Button HorizontalAlignment="Right" Margin="0,129,44,110" Name="btnSelektorYap" Width="84" Click="btnSelektorYap_Click">Selektör</Button>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,26,44,0" Name="btnIlerle" VerticalAlignment="Top" Width="83" Click="btnIlerle_Click">İlerle</Button>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,58,44,0" Name="btnGeriGit" VerticalAlignment="Top" Width="83" Click="btnGeriGit_Click">Geri Git</Button>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,95,44,0" Name="btnDur" VerticalAlignment="Top" Width="83" Click="btnDur_Click">Dur</Button>
        <Button HorizontalAlignment="Left" Margin="28,129,0,110" Name="btnArabadanIn" Width="86" Click="btnArabadanIn_Click">İn</Button>
    </Grid>
</Window>

Window1 penceremizin kod içeriği ise aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ArabaStateMachineLib;
using System.Workflow.Runtime;
using System.Workflow.Activities;

namespace YarisPisti
{
    public partial class Window1 : Window
    {
        // Workflow çalışma ortamı için gerekli nesne tanımlanır
        WorkflowRuntime _wf;
        ArabaYerelServisi _arabaServisi; // Yerel Servis nesnesi tanımlanır
        Guid ornekId = Guid.Empty; //ExternalDataEventArgs sınıfı parametre olarak Guid almaktadır. Bu sebepten ornekId isimli bir değişlen tanımlanmıştır.

        public Window1()
        {
            InitializeComponent();

            _wf = new WorkflowRuntime(); // Çalışma zamanı oluşturulur
            _wf.StartRuntime(); // WF çalışma zamanı başlatılır
                ExternalDataExchangeService excSrv = new ExternalDataExchangeService(); // Bir adet ExternalDataExchangeService servisi oluşturulur ve şu anki WF çalışma zamanına AddService metodu ile eklenir.
            _wf.AddService(excSrv);

            // Yerel Servis(Local Servis) nesnesi örneklenir.
            _arabaServisi = new ArabaYerelServisi();
            // Host üzerinden ele alınacak MesajAlindi olayı yüklenir.
            _arabaServisi.MesajAlindi+=new EventHandler<MesajAlindiEventArgs>(_arabaServisi_MesajAlindi);
            // Yerel servis ExternalDataExchangeService örneğine eklenir
            excSrv.AddService(_arabaServisi);
        }

        // Label içeriğinin güncellenmesi için aşağıdaki gibi Invoker kullanımı gereklidir. Aksi takdirde çalışma zamanında istisna alınır.
        private delegate void GuncellemeTemsilcisi();
        void _arabaServisi_MesajAlindi(object sender, MesajAlindiEventArgs e)
        {
            ornekId = e.InstanceId;
            GuncellemeTemsilcisi dlg = delegate()
            {
                lblGelenMesaj.Content = e.Bilgi.ToString();
            };
            // Normal Windows uygulamalarında this.Invoke ile çağırabilmemiz mümkünken WPF uygulamalarında Dispatcher nesnesinden yararlanılmaktadır
            Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, dlg);
        }

        // Yeni araba aslında Workflow1 tipinden yeni bir State Machine Workflow örneği oluşturulmasını sağlamaktadır.
        private void btnYeniAraba_Click(object sender, RoutedEventArgs e)
        {
            WorkflowInstance wfOrnegi = _wf.CreateWorkflow(typeof(Workflow1), null);
            wfOrnegi.Start(); // State Machine Workflow başlatılır
            ornekId = wfOrnegi.InstanceId; // ExternalDataEventArgs' ta kullanılmak üzere InstanceId değeri alını ve GUID tipinden olan ornekId değişkenine atanır.
        }

        // Olaylar için gerekli argümanların alınması sağlanan metod
        private ExternalDataEventArgs ArgumanAl()
        {
            ExternalDataEventArgs args = new ExternalDataEventArgs(ornekId);
            args.WaitForIdle = true;
            return args;
        }

        private void btnIlerle_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnIlerle(ArgumanAl()); // Servis üzerinden ilgili olay metodu tetiklenir
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnMotoruCalistir_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnArabayiCalistir(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnGeriGit_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnGeriGit(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnMotoruKapat_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnMotoruDurdur(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnDur_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnDur(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnArabadanIn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnArabadanCik(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnSelektorYap_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                _arabaServisi.OnSelektorYap(ArgumanAl());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

Uygulamamızı çalıştırdığımızda aşağıdaki video görüntüsündeki gibi State Machine Workflow başarılı bir şekilde yüklendiği ve çalıştığı görülmektedir. Tabiki geçiş yapılamayan durumlarda söz konusudur. Örneğin araba ileri gidiyorken geri gidiyor durumuna geçilmesi söz konusu değildir. Bu tip hallerde, try...catch blokları devreye girerek üretilen istisna(Exception) mesajları MessageBox içerisinde görülecektir.

Böylece geldik bir uzun makalemizin daha sonuna. Bu makalemizde State Machine Workflow tipinden iş akışlarını kısaca ne olduklarını, nasıl tasarlandıklarını incelemeye çalıştık. Bunu yaparken StateActivity, EventDrivenActivity, HandleExternalEventActivity, SetStateActivity, CallExternalMethodActivity vb aktivite tiplerinden bir kaçına değinme fırsatımızda oldu. Ayrıca durumlar(States) arası geçişleri sağlayan olayların yerel bir servis(Local Service) içerisinde nasıl geliştirilebileceğini gördük. Son olarakta geliştirilen Workflow projesini bir WPF host uygulamasında yürütmeyi inceledik. Örnek senaryo olarak APress yayınlarından olan ve Bruce Bukovics tarafından yazılan Pro WF kitabında yer alan CarService iş akışının daha basit bir versiyonunu adım adım açıklamalı olarak örneklemeye çalıştık. Özel olarak host uygulamayı WPF üzerinde geliştirdik. Böylece Window uygulamalarında kullandığımız method invoker kavramının burada Dispatcher özelliği üzerinden ele alınabileceğini görme fırsatını elde ettik.(Dispatcher kavramına ilerleyen makalelerimizde değinmeye çalışıyor olacağım) State Machine Workflow tipinden iş akışlarının, bu senaryo dışında gerçek .Net nesneleri içerisindeki kullanımını araştırmanızı ve örneklemeye çalışmanızı şiddetle tavsiye ederim. İlerleyen makalalerimizde Windows Workflow Foundation ile ilgili farklı konulara değinmeye devam ediyor olacağız. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

StateMachineOrnegi.rar (90,12 kb)

Tags:  
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

İlk Bakışta Windows Workflow Foundation

Salı, 1 Ocak 2008 16:39 by bsenyurt

Gerçek dünyada pek çok iş probleminin çözümünde iş akışlarından(Workflow) yararlanılır. Temelde bir iş probleminin çözümünde veya amacının gerçekleştirilmesinde izlenen yol birdir. Önce problem yönetilebilir küçük parçalara bölünür. Bu parçalar, gerçekleştirilmesi gereken görevler(Tasks) olarak düşünülebilir. Her bir görevin(Task) içerisinde ona ait gerçekleştirilmesi gereken ne varsa adımlar(Steps) halinde tasarlanır. Bu adımlar dahil oldukları görevin tamamlanmasında rol oynarlar. Adımlar arasındaki geçişler basit olabileceği gibi çeşitli çevresel koşul veya faktörlerede bağımlı olabilir. Bir başka deyişle adımlar arası geçişlerde koşullar(Conditions) söz konusu olabilir. Adımlar düzenli bir sırada olup aralarındaki geçişler önceden tanımlanmış ve belirli olabileceği gibi, çeşitli olaylara göre farklı şekillerde ele alınabilirlerde. Sonuç olarak ortaya iş probleminin çözümü için tasarlanmış bir süreç(Process) ve kontrollü bir akış(Control Flow) çıkar.

İş akışları(Workflow) sayesinde, iş problemlerinin çözümlenmesi, istenirse genişletilebilmesi son derece kolay bir şekilde gerçekleştirilebilmektedir. Bir iş akışına pek çok yerde kolaylıkla rastlayabiliriz. Bunların bir kısmı için hiç bir geliştirme yapılamamakla birlikte çoğu için bilgisayar teknolojisinden yararlanılmaktadır. Bir başka deyişle bazı iş çevrelerinde izlenen süreçler bilinçsiz olarak kendiliğinden bir akışa sahip olabilmektedir. Ancak bilgisayar teknolojisinin hızla yaygınlaşması ve verimliliği arttırması nedeni ile, iş akışları yazılımsal ve donanımsal faktörler üzerinden ele alınmaktadır. Bu anlamda iş akışlarının(Workflow) ilk uygulamaları dökümanların bir noktadan başka bir noktaya taşınması olmuştur. Zaten buda SharePoint Server gibi gelişmiş bir sistemde iş akışlarının neden kullanıldığını açıklamaktadır. Nitekim portal tarzı uygulamalarda döküman yönetimi(Document Management) ve paylaşımı esastır. Bu noktada, dökümanların kullanıcılar(Users) ve sistemler(Systems) arasındaki hareketinde çeşitli onay mekanizmalarının devreye girmesi muhtemeldir. Buda çok doğal olarak bir iş akışı ile ifade edilip tanımlanabilir.

Bir kaç yıl önce çalıştığım özel bir yazılım firmasında şirketlerin iş akışlarının(Workflow) tasarlanabildiği bir yazılım projesinde görev almıştım. İş çevrelerinin çözüm bekleyen çok fazla sayıda problemi vardır. Söz gelimi bir elemanın işe alım yada işten çıkartılma süreçleri, şirketin mali yapısını gösteren raporların onay mekanizmaları, üretim hattına ait süreçler ve daha pek çoğu. Bilgisayar ve yazılımlar sayesinde bu örnek süreç ve benzelerine ait iş akışlarının kullanılması son derece kolaylaşmaktadır. Örneğin işe alım sürecini ele alalım. Elemanın CV' sinin insan kaynakları departmanına verilmesi ve bilgisayar ortamına alınması, departman içerisinde CV' nin ilgili mercilere gönderilerek onaylarının alınması, onay verilmesi halinde işe alınmak istenen personelin görüşmeye çağırılması, görüşme sonrası tüm bilgilerin kayıt altına alınarak sürece dahil olan diğer kişilerede gönderilmesi, gönderme işlemlerinde mail sisteminden yararlanılması gibi işlemler söz konusu olacaktır. Bu işlemlerin her biri arasındaki geçişler karar yapıları ve onay mekanizmaları ile gerçekleşmektedir. Söz gelimi CV' nin ilk değerlendirmesinde en üst mercinin red etmesi halinde iş başvurusunda bulunan kişiye olumsuz cevap verilmesi ve süreç içerisindeki ilgili kişilerede bu durumun mail, sms gibi yollarla aktarılması için bir onay mekanizması ve koşullandırmanın olması gerekmektedir.

Dikkat edilecek olursa iş akışılarının yukarıdaki gibi cümlesel olarak ifadesi anlaşılmasını zorlaştırmaktadır. Hatta bu sürecin kapsadığı alana(Domain) dahil olan kişilerin bilgisayarlarında adımları kolayca izleyebilmesi ve kimin üstüne hangi görev(Task) düşüyorsa bunu yapabilmesi demek aslında bir yazılım sisteminin geliştirilip kurulması anlamına gelmektedir. Buda doğal olarak yukarıdakinden daha uzun ve karmaşık olan iş akışı anlatımlarının önce kağıt üzerinde grafiksel olarak tasarlanması ve sonrasında gerekli yazılımın hazırlanması anlamına gelmektedir. Geliştiriciler(Developers) bu tip iş akışlarını sistemlere uygularken görsel tasarlayıcılarıda(Visual Designer) ele alırlar. Bir başka deyişle iş akışlarının görsel olarak tasarlanabilmeside önemlidir. Bu amaçla geliştirilmiş pek çok yazılım(Software) söz konusudur.

NOT : Karmaşık iş kurallarını içeren akışların görsel olarak ele alınması, akışın içerisinde yer alan adım(Step) ve kuralların(Rule) kolayca tasarlanabilmesi hatta kodlanabilmesi demektir. Bu bir iş akışının sonradan kolayca değiştirilebilmesi bir başka deyişle genişletilebilmesininde kolaylaştırılması anlamına gelmektedir.

Bu yazılımların genel amacı, pek çok iş akışının çoğunlukla birden fazla bilgisayarın olduğu sistemlerde kurulması ve kullanılabilmesidir. Elbette birden fazla bilgisayar olması şart değildir. Bazı durumlarda tek bir bilgisayar üzerindeki programlar için söz konusu olabilecek iş akışlarıda var olabilir. (Aslında Windows Workflow Foundation mimarisinin bu modeldeki iş akışlarına daha yakın olduğunu düşünebiliriz.)

Bu kısa bilgilerden sonra bir iş akışını tanımlamak çok daha kolaylaşmaktadır. Bir iş akışı(Workflow) herhangibir iş probleminin çözümü için gereken adımları(Steps), onay mekanizmalarını ve karar yapılarını(Condition) içeren bir model sunmaktadır. Bir başka deyişle bir iş akışı, belirli kurallar(Rules) üzerine sıralanmış adımlar topluluğu olarakta düşünülebilir. Özellikle bilgisayar teknolojileri üzerinden baktığımızda bir iş akışının aslında farklı bir programlama modeli sunduğuda göz önüne alınabilir. Bu açılardan bakıldığında iş akışı denildiğine akla gelen pek çok kavramda bulunmaktadır. Bu kavramlar aşağıdaki maddelerde belirtildiği gibidir;

  • Görsel Şemalar
  • Kurallar(Rules)
  • Politikalar(Policies)
  • Sisteme giren(Input) ve çıkan(Output) veriler
  • Kişiler(Users)
  • Organizasyonlar(Organizations)
  • Yordamlar(Procedures)
  • Temel Görevle(Tasks)
  • Adımlar(Steps)
  • Aktiviteler(Activities)

Peki Windows Workflow Foundation ile kastedilen nedir? Microsoft bu Foundation ile iş akışlarının tasarlandığı bir programmı üretmiştir? Aslında Windows Workflow Foundation tek bir iş alanı(Single Domain) içerisinde yer alan tek bir uygulamayı(Single Application) hedeflemektedir. Bir başka deyişle Windows Workflow Foundation .Net uygulamalarının kullanabileceği iş akışlarının(Workflow) tasarlanması için gerekli altyapıyı sunan bir Framework 3.0 yaklaşımıdır.

NOT : İş akışları çoğunlukla BizTalk Server' un sundukları ile karşılaştırılır. BizTalk ile özellikle elektronik ticarete uygun olacak şekilde farklı platformlar üzerinde yer alan sistemlere ait iş süreçleri başarılı bir şekilde ele alınabilmektedir. Oysaki Windows Workflow Foundation sadece işletim sistemi seviyesinde(Operating System Level) düşünülmüştür.

Bu anlamda Microsoft otoriteleri BizTalk' un interapplication(Birden fazla uygulama-Mutliple Applications) olarak ele alınması gerektiğini, Windows Workflow Foundation' ın ise intraapplication(Tek bir uygulama-Single Application) şeklinde düşünülmesi gerektiğini vurgulamaktadır. Ancak bu bir kısıt değildir. Nitekim WWF içerisinde Web Servisleri gibi SOA(Service Oriented Architecture) modelleri sayesinde dış platformlara çıkılması ve iş sürecinin bu şekilde genişletilmeside mümkündür.

Windows Workflow Foundation mimarisi sayesinde iş akışları görsel olarak tasarlanıp kodlanabilirler. Ancak tasarlanan bu iş akışlarının işe yarayabilmesi için bir uygulama tarafından ele alınmaları şarttır. Söz konusu uygulamalar host görevini üstlenmekte olup bir veya daha fazla iş akışını barındırıp kullanabilirler. Windows Workflow Foundation mimarisi pek çok fayda sağlamaktadır. Söz konusu faydalar aşağıdaki maddeler ile özetlenebilir.

  • İş akışlarının kolayca ve etkili bir biçimde tasarlanabilmesi için görsel tasarımcı(Visual Designer) sunar.
  • Sistemin insan ile etkileşimde olduğu modellerde aktiviteler(adımlar) arasındaki süre farkları olabilir. Bu nedenle iş akışının güncel durumlarının kaydedilebiliyor ve daha sonra sonra başka bir zaman diliminde tekrar yüklenebiliyor olması gerekir. WWF bunu sağlamaktadır.
  • İş akışları için gerekli çalışma zamanı(Run-Time) ortamı dışında, pek çok tipte farklı aktivite(Activity) için destek, aktivitelerin denetlenmesi(Monitoring), izlenmesi(Tracing) sağlanmaktadır.
  • Sequential iş akışı desteği vardır. Bu akışlar çoğunlukla sistem etkileşimi olan bir başka deyişle insan faktörü fazla bulunmayan durumlarda söz konusudur. Bu tip akışlarda adımların sırası ve düzeneği bellidir.
  • State Machine iş akışı desteği vardır. Bu tip akışlarda insan etkileşimi söz konusudur. Çoğunlukla aktivitiler arasındaki geçişlerin bazı olayların tetiklenmesine bağlı olduğu durumlarda kullanılır.

Windows Workflow Foundation, iş akışlarını esas alan bir programlama modeli sunmaktadır. Söz konusu programlama  modeli deklaratif(declarative) yaklaşımı ele almaktadır. Buna göre iş mantığı ayrık bileşenler içerisinde kapsüllenir(encapsulation). Nitekim bileşenler arasında akışların nasıl yölendirileceğini belirten kurallar deklaratif olarak tanımlanabilirler.

Windows Workflow Foundation mimarisinin nasıl kullanıldığını daha net kavrayabilmek için basit örnekler üzerinden devam etmekte yarar var. İlk örnekte son derece basit bir iş akışını tasarlayıp bunu kullanacak olan bir Console uygulaması geliştiriyor olacağız. Sonrasında ise iş akışını farklı .Net uygulamalarında da kullanabilmek adına bir sınıf kütüphanesi(Class Library) içerisinde tasarlayıp örnek bir Windows uygulaması içerisinden çalıştıracağız. Daha öncedende belirtildiği gibi WWF içerisinde tasarlanmış bir iş akışının(Workflow) işe yarayabilmesi için bir host uygulama tarafından ele alınması gerekmektedir. İlk örneğimizde Host program Console uygulaması olarak tasarlanacaktır. Örneklerimizi Visual Studio 2008 RTM sürümü üzerinde tasarlıyor olacağız. Ancak istenirse gerekli genişletmeler yüklenerek Visual Studio 2005 ilede WWF geliştirmeleri yapılabilir. Elbette .Net Framework 3.0' ın yüklü olması şarttır.

İlk olarak aşağıdaki ekran görüntüsünde olduğu gibi Workflow sekmesinde yer alan proje şablonlarından(Project Templates) birisinin seçilmesi gerekmektedir.

Dikkat edilecek olursa Sequential ve State Machine iş akışlarının(Workflow) geliştirilmesi için gerekli proje şablonları(Project Template) bulunmaktadır. Her iki tip içinde birer sınıf kütüphanesi(Class Library) ve Console uygulaması şablonu yer almaktadır. Çok doğal olarak geliştirilen iş akışlarının farklı tipte .Net uygulamalarında kullanılacağı durumlar söz konusu olduğunda Workflow Library şablonlarını uygulamak daha doğru bir yaklaşım olacaktır. Windows Workflow Foundation ile geliştirilen iş akışlarında aktivitelerin(Activity) büyük önemi vardır. Geliştiriciler isterlerse kendi özel aktivite tiplerinide(Custom Activity Types) yazabilirler. Bunun içinde Workflow Activity Library şablonu kullanılır.

Makalemizdeki ilk örneğimizde sonuçları hemen irdeleyebilmek adına Sequential Workflow Console Application tipinden bir uygulama yazıyor olacağız. Buna ilişkin proje şablonu seçildikten sonra uygulama içerisine aşağıdaki ekran görüntüsünde olduğu gibi Workflow1 isimli bir iş akışı tipinin eklendiği görülür.

İş akışı içerisine örnek aktiviteleri eklemeden önce proje içerisinde oluşturulan tiplerden bahsetmekte yarar vardır. Herşeyden önce bir iş akışı projesi oluşturulduğunda System.Workflow.Activities, System.Workflow.ComponentModel ve System.Workflow.Runtime isimli assembly' ların referans edildiği görülür.

Tahmin edileceği gibi bu assembly' lar içerisinde iş akışlarının geliştirilmesi, çalıştırılması, denetlenmesi ve izlenmesi için gerekli temel tipler yer almaktadır. Bunların dışında console uygulamasına dahil edilmiş farklı assembly referanslarıda bulunmaktadır. Söz gelimi iş akışı içerisinde transaction desteğini sağlamak için System.Transactions, akış içerisinden web servisleri ile iletişime geçebilmek için System.Web, System.Web.Services gibi assembly' ları örnek olarak gösterebiliriz.

Proje içerisine varsayılan olarak atılan Workflow1 isimli sınıfın(Class) hiyerarşik yapısı ise aşağıdaki sınıf diyagramında(Class Diagram) olduğu gibidir.

Örnek Sequential Workflow Console Application olduğundan, içeride kullanılacak varsayılan iş akışıda Sequential Workflow olarak ele alınmaktadır. Bu sebepten dolayı sealed(kendisinden türetme yapılamaz) olarak tanımlanmış Workflow1 isimli sınıf ilk etapta SequentialWorkflowActivity sınıfından türemektedir. Ancak enteresan olan bir nokta vardır. Örnek geliştirilirken kullanılacak olan standart pek çok akitivite bileşeninin Activity isimli sınıfdan türediği görülecektir. Dikkate değer olan, aktiviteleri(adımları-Steps) içeren Workflow tipinin kendisininde aslında bir aktivite nesnesi olmasıdır. (Sınıf diyagramında pek çok tip yer almaktadır. Bu tiplerin detaylarını ilerleyen makalelerimizde inceleme fırsatı bulacağız.)

Örneğimizdeki iş akışının içeriğini geliştirmeden önce Program.cs dosyası içerisindeki kod parçalarına kısaca bakalım. Şu aşamada Workflow1 tipine ait iş akışını(Workflow) veya başkalarını çalıştıracak ve yürütücek olan kısım Main metodunun içeriğidir.

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

namespace MerhabaWWF
{
    class Program
    {
        static void Main(string[] args)
        {
            using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                                                                            {
                                                                                Console.WriteLine(e.Exception.Message);
                                                                                waitHandle.Set();
                                                                            };

                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MerhabaWWF.Workflow1));
                instance.Start();

                waitHandle.WaitOne();
            }
        }
    }
}

Şimdi Main metodu içerisinde neler yapıldığına kısaca bir bakalım. WorkflowRuntime sınıfı iş akışlarının devreye sokulması, bunların çalışması için gerekli ortamın hazırlanması, çalışma zamanı iş akışlarının izlenmesi, denetlenmesi gibi işlemleri üstlenen önemli bir sınıftır. Pek çok önemli olayı(Event) vardır. Bunlardan örnekte hazır olarak sunulan WorkflowCompleted olayı iş akışının tamamlanması sonrasında devreye girmektedir. Bir iş akışı işlemleri tamamlandıktan sonra geriye değer döndürebilir. Bu sebepten dolayı WorkflowCompletedEventArgs tipinden olan olay parametresinin OutputParameters özelliğinden yararlanılarak WorkflowCompleted olay metodu içerisinde sonuç alınması sağlanabilir.Tahmin edileceği gibi WorkflowTerminated olayı, herhangibir hata nedeni ile(çoğunlukla bir istisna-exception) iş akışı tamamlanamadığında devreye girmektedir. Bu olayla ilişkili olan WorkflowTerminatedEventArgs sınıfı üzerinden hareket edilerek oluşan istisna(Exception) referansı çalışma zamanında(runtime) yakalanabilir. Dikkat edilecek olursa Visual Studio tarafından otomatik olarak üretilen bu kod parçasında olaylara ait fonksiyonelliklerin hazırlanmasında isimsiz metodlardan(anonymous methods) yararlanılmaktadır.

Using bloğu içerisinde oluşturulan örneklerden bir diğeride AutoResetEvent sınıfına ait nesnedir. Bu sınıf temel olarak thread senkronizasyonu yönetimi amacıyla tasarlanmıştır. Host uygulamanın kendi içerisinde bir veya daha çok iş akışını tetiklemesi sonrasında askıda kalması istenen bir durum değildir. Nitekim bir iş akışı(Workflow) çalıştırıldığında Workflow çalışma zamanı(Runtime) bunu host uygulamadaki ana thread' in dışında ayrı bir thread içerisine alacaktır. AutoResetEvent sınıfının Set metodu bu tip bir durumda kalındığında bekleyen thread' in serbest bırakılmasını sağlamaktadır. Dikkat edilecek olursa Set metodu hem WorkflowCompleted hemde WorkflowTerminated olay metoduları içerisinde çağırılmakta ve bekleyen thread' in serbest bırakılması sağlanmaktadır.

Main metodu içerisinde örneklenen diğer bir nesne sınıfıda WorkflowInstance' dır. Bu sınıfta sealed olarak tanımlanmıştır. Bir başka deyişle kendisinden türetme yapılamamaktadır. Aynı zamanda yapıcı metodları(Constructor) erişimide yoktur. Kendisi ancak WorkflowRuntime sınıfının static CreateWorkflow metodu ile örneklenebilmektedir. Bu metodun aşırı yüklenmiş(Overload) farklı versiyonları bulunmaktadır. Özellikle iş akışına dışarıdan parametre değerleri aktarılabilmesini sağlayan versiyonu vardır ki bu önemlidir. Nitekim pek çok iş akışının başlangıcında bir takım dış parametre bilgilerine ihtiyaç vardır. Diğer taraftan bir XOML(eXtensible Object Markup Language) dosyasında tanımlı herhangibir iş akışının yüklenmesini sağlayan versiyonuda bulunmaktadır.

Main metodunun sonunda AutoResetEvent sınıfına ait nesne örneği üzerinden WaitOne fonksiyonu çağırılır. Bu çağrı, çalışan ana thread' in bekleyen iş akışları tamamlanıncaya kadar duraksatılması için önemlidir. Nitekim iş akışının içerisindeki adımların gerçekleştirilme sürelerinin belirsiz olması ihtimali vardır. WaitOne metodu ilerlenip ilerlenmeyeceğine, diğer thread' lerden gelen sinyallere göre karar vermektedir. Tahmin edileceği gibi örnekte yer alan söz konusu sinyal Set metodu ile yayınlanmaktadır.

Artık iş akışı içerisine örnek bir adım(Step) ekleyerek devam edebiliriz. Daha önceden de belirtildiği gibi adımlar aslında birer aktivite(Activity) olarak düşünülebilirler. WWF mimarisi çok sayıda hazır aktivite sunmaktadır. Bu aktivite bileşenlerine iş akışına ait tasarım penceresindeylen ToolBox kısmından da erişilebilir. Var olan tüm aktivite bileşenleri System.Workflow.ComponentModel isim alanında(Namespace) yer alan Activity sınıfından(Class) türemektedir. Hazır aktivite bileşenlerine örnek olarak IfElseActivity, CodeActivity, CallExternalMethodActivity, DelayActivity, EventDrivenActivity, ReplicatorActivity, ParallelActivity, TerminateActivity ve daha pek çoğu verilebilir.

Biz örneğimizde basit olması açısından CodeActivity ve IfElseActivity bileşenlerini kullanıyor olacağız. CodeActivity bileşeni sayesinde iş akışının herhangibir adımında çalıştırılması istenen kodlar ele alınabilmektedir. Temel olarak bileşenin ExecuteCode özelliğine atanan değerin işaret ettiği metod, aktiviteye gelindiğinde çalıştırılacak olan kodları içermektedir. IfElseActivity bileşeni yardımıyla bir iş akışının herhangibir noktasına karar yapıları eklenebilmektedir. IfElseActivity bileşeni kendi içerisinde birden fazla IfElseBranchActivity örneği içerebilmektedir. Bu IfElseBranchActivity örneklerinin her biri karar yapısı içerisindeki farklı dallanmaları ifade etmektedir.

Dilerseniz örnek üzerinden devam ederek iş akışını tamamlamaya çalışalım. Örnek senaryoda bir ürünün stoktaki miktarına göre çalışacak bir iş akışı tasarlayacağız. Söz gelimi stok miktarının belirli bir değerin altına inmesi halinde çalışacak bir iş akışı söz konusu olabilir. Hatta farklı aralık değerlerine göre farklı dallanmalar yapılmasıda sağlanabilir. (Bu noktada iş akışının ne kadar anlamlı olduğu çok önemli değildir. Nitekim hedeflenen, temel bir iş akışının WWF altında geliştirilmesi ve kullanılmasıdır.) İlk olarak tasarım zamanında iken Workflow1 üzerine bir IfElseActivity bileşenini ToolBox' tan sürükleyerek bırakalım. Bırakılma işleminden sonra ilk etapta aşağıdaki görüntü ortaya çıkacaktır.

Dikkat edileceği gibi IfElseActivity içerisine varsayılan olarak iki adet IfElseBranchActivity bileşeni daha eklenmiştir. Bu noktada soldaki IfElseBranchActivity bileşeninin belirtilen koşul true olduğunda çalıştırılması, diğerinin ise false durumuna karşılık olarak değerlendirilmesi doğru bir yaklaşımdır. Her bir IfElseBranchActivity kendi içerisinde başka aktivitelerin tetiklenmesinede neden olmaktadır. Drop Activites Here kısmına bu amaçla çeşitli aktivite bileşenleri(Activity Components) bırakılabilir. IfElseBranchActivity' lerin en önemli üyesi aşağıdaki ekran görüntüsündende görülebileceği gibi Condition özelliğidir(Property).

Condition özelliğine Code Condition ve Declarative Rule Condition olmak üzere iki farklı değer verilebilir. Peki bunlar ne anlama gelmektedir? Code Condition değerinin atanması halinde koşulun bir metod ile değerlendirileceği belirtilir. Söz konusu metod bool bir değerlendirme yapmalıdır. Declarative Rule Condition seçeneği sayesinde ise, koşul kodun dışında ayrı bir şekilde ele alınır. Bu çeşit bir kullanım çalışma zamanında(Run-Time) yeniden derleme gerektirmeden koşul değişikliği yapma imkanı sunmaktadır. Örneğimizde ilk olarak Declarative Rule Condition seçeneğini inceliyor olacağız. Bu kriter seçildikten sonra Condition özelliği altına iki alt üyenin daha eklendiği görülecektir.

Bunlardan ConditionName koşulun adını ifade ederken, Expression ise koşula ait ifadeyi içermektedir. ConditionName özelliğine sembolik olarak Miktar50Altinda verdiğimizi düşünelim. Bundan sonra Expression özelliği yanındaki üç nokta düğmesine tıklarsak aşağıdaki arabirim ile karşılaşırız.

Bu ekranda geriye true veya false döndürebilecek şekilde bir koşul tanımlaması yapılmaktadır. Dikkat edilecek olursa metin kutusu içeriğinde intellisense desteği vardır. Ancak bu noktada iş akışının doğurduğu önemli bir ihtiyaçta ortaya çıkmaktadır. Bir şekilde iş akışına, ürüne ait stok miktarı değerinin aktarılması gerekmektedir. Lakin koşulun değerlendirmesi gereken durum stok miktarının belirli bir değerin altında olması halinde yapılacak işlemler ile ilgilidir. Dolayısıyla iş akışına dışarıdan paramete aktarılması gerekmektedir.

NOT : Bir iş akışına aktarılacak olan parametreler herhangibir .Net CLR(Common Language Runtime) tipi olabilir. Bu önemli bir avantajdır, nitekim bir iş akışı(Workflow) önceden tanımlı veya geliştirici tarafından yazılmış herhangibir tip verisi ile başlatılabilir. İş akışlarının herhangibir .Net CLR tipini parametre olarak alabilmesinde object tipi etkin bir rol oynar. Elbetteki bir iş akışına birden fazla parametrede gönderilebilmektedir.

Bunun yapmanın yolu ise son derece basittir. Nitekim Workflow tipi aslında bir sınıftır. Dolayısıyla Workflow tipine özellikler(Property) ekleyerek dış ortamdan parametre aktarımı sağlanabilir. Bu nedenle örnekte yer alan Workflow1 sınıfına aşağıdaki gibi bir özelliğin ilave edilmesi yeterlidir.

public sealed partial class Workflow1: SequentialWorkflowActivity
{
    private int _stokMiktari;

    public int StokMiktari
    {
        get { return _stokMiktari; }
        set { _stokMiktari = value; }
    }

    public Workflow1()
    {
        InitializeComponent();
    }
}

Artık tasarım tarafına dönülerek ilk IfElseBranchActivity için gerekli koşul aşağıdaki ekran görüntüsündeki gibi oluşturulabilir.

Buna göre StokMiktari özelliğinin(Property) değerinin 10 ile 50 arasındaki olması halinde, bu IfElseBranchActitiy bileşenin arkasından gelecek olan aktivite çalıştırılacaktır. Bu noktada ne yapılmak istendiği önemlidir. Söz gelimi bu koşulun sağlanması halinde üreticiye yeni ürün talepleri için mail veya Sms gönderilmesi gibi işlemler yaptırılabilir. Yine çok basit olarak düşünerek hareket edelim ve mail gönderme işleminin yapılacağı kod bloğunu işaret edecek bir CodeActivity bileşenini aşağıdaki ekran görüntüsünde olduğu gibi tasarım ortamına atalım.

CodeActivity bileşeninin en önemli üyesi ExecuteCode özelliğidir. Bu özelliğe atanacak olan isim, bu adımda çalıştırılacak olan metodun adıdır. Bu olay metodunun otomatik olarak oluşuturulması sağlanabilir. Bunun için ExecuteCode özelliğine bir metod adı yazılması ve enter' a basılması yeterlidir. Örnekteki ExecuteCode özelliğine MailGonder ismini yazıp enter tuşuna bastığımızda Workflow1 sınıfına aşağıdaki metodun otomatik olarak eklendiği görülecektir.

private void MailGonder(object sender, EventArgs e)
{
}

Görüldüğü gibi üretilen metod stadart bir olay metodu yapısındadır. Geriye değer döndürmemekte ve iki adet parametre almaktadır. İş mantığında bu adımda işletilmesi gereken kodlar nelerse bu metod içerisine yazılmalıdır. Söz gelimi bu adımda mail gönderme işlemi yaptırılabilir.

IfElseActivity içerisinde sağ tarafta kalan ikinci bir IfElseBranchActivity bileşeni daha vardır. Şu anki senaryoda bu bileşen else olma durumunda ne olacağını belirtmektedir. Diğer taraftan örnek senaryoda başka IfElseBranchActivity bileşenlerinin eklenmeside mümkündür. Örneğin Stok miktarının herhangibir nedenle 0 ve altında olması hali ve Stok Miktarının 50' nin üzerinde olması hali gibi. Bu durumların her biri için birer IfElseBranchActivity kontrolü eklenip gerekli aksiyonların gerçekleştirilmesi sağlanabilir.

NOT : IfElseActivity bileşenleri içerisine IfElseBranchActivity bileşenlerini eklemek için sağ tıklayıp Add Branch demek yeterlidir.

Bu amaçla örneğimize aşağıdaki şekildede görüldüğü gibi başka IfElseBranchActivity bileşenleri daha eklediğimizi düşünelim.

Burada ikinci IfElseBranchActivity içerisinde StokMiktari özelliğinin değerinin 0' ın altında ve eşit olduğu durumda çalıştırılacak bir kod aktivitesi yer almaktadır. 3ncü IfElseBranchActivity içerisinde StokMiktari özelliğinin değerinin 50 ile 500 arasında olduğu durumda çalışacak bir aktivite bulunur. Son IfElseBranchActivity parçasında ise var olan koşulların dışındaki durum ele alınmaktadır. Genellikle birden fazla IfElseBranchActivity içeren durumlarda tüm ihtimallerin dışında kalabilecek bir seçeneğide ele almak için Condition özelliği herhangibir şekilde atanmamış boş bir IfElseBranchActivity parçası kullanmakta yarar vardır. Örnekte bu tarz bir durum için yine kod aktivitesi yürütülmektedir. Elbette her CodeActivity bileşenin ExecuteCode özelliğine ilgili değerlerin atanması ve oluşan olay metodlarının kodlanması gerekmektedir. Şimdilik bu kısım bizim açımızdan önemli değildir. Nitekim örneğe son olarak yapılan eklemelerde kavranması gereken birden fazla IfElseBranchActivity bileşenin bir arada ele alınabilmesidir.

NOT: IfElseBranchActivity işlemlerinde karar mekanizması olarak Declarative Rule Condition kullanılması halinde kuralların rules uzantılı bir XML dosyası içerisine yazıldığı görülür. Aşağıdaki ekran görüntüsünde örnekte kullanılan IfElseBranchActivity' ler için oluşturulan Workflow1.rules dosyasının sadece bir kısmı görülmektedir.

 

Şimdi iş akışı için daha fazla önem arz eden bir konu üzerinde durulmalıdır. İş akışına ilgili parametreler nasıl aktarılacaktır? Bu amaçla Main metodu içerisinde yer alan kod parçalarında bazı değişiklikler yapılması gerekmektedir. Daha öncedende belirtildiği gibi WorkflowRuntime sınıfına ait CreateInstance metodunun ikinci parametresi iş akışlarına değer göndermek için kullanılmaktadır. İş akışları herhangibir .Net CLR tipini parametre olarak aldığından ve dış ortamdan iş akışına gönderilen değerin hangi özelliğe aktarıldığının bilinmesi gerektiğinden generic Dictionary<string,object> koleksiyonu kullanılmaktadır. Böylece ilgili iş akışının hangi özelliğine, hangi değerin aktarılacağı belirtilebilir. Bu aynı zamanda iş akışına birden fazla parametre değeri gönderilebilmesi anlamınada gelmektedir.

Örnekte kullanılan StokMiktari özelliğinin(Property) değeri iş akışına dış ortamdan, örneğin Host uygulama içerisinden gelmektedir. Bu nedenle Main metodu içerisinde aşağıdaki değişikliklerin yapılması gerekir.

Dictionary<string, object> parametreler = new Dictionary<string, object>();
parametreler.Add("StokMiktari", 45);

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MerhabaWWF.Workflow1),parametreler);

İlk olarak parametre veya parametreleri taşıyacak olan Dictionary<string,object> koleksiyonu örneklenir. Bu generic koleksiyonun anahtarları(Keys) string, değerleri(Values) ise object türünden olmalıdır. Daha sonra Add metodu ile parametre ekleme işlemi gerçekleştirilir. Örnekte StokMiktari isimi özellik için 45 değerinin verileceği belirtilmektedir. Son olarak WorkflowInstance örneği oluşturulurken CreateWorkflow metodunun ikinci parametresine, koleksiyona ait nesne örneği atanır. Burada dikkat edilmesi gereken bazı noktalarda vardır. Herşeyden önce Dictionary koleksiyonuna eklenen parametre adının, iş akışı(Workflow) sınıfı içerisinde tanımlanan özellik adı(Property Name) ile bire bir uygun olması gerekmektedir. Söz gelimi Add metodunda StokMiktari yerine stokmiktari yazılırsa aşağıdaki ekran görüntüsünde olduğu gibi çalışma zamanında ArgumentException istisnası(Exception) alınır.

Bu noktadan sonra iş akışı başarılı bir şekilde çalışacaktır. Örnek olarak tasarlanan iş akışının herhangibir amacı ve başarısı yoktur ancak temel kavramların nasıl uygulanacağını göstermektedir.

Makalemize IfElseBranchActivity bileşenlerinde Condition özelliğinde Code Condition seçeneğini nasıl kullanacağımızı inceleyerek devam edelim. Bu amaçla örnek olarak herhangibir IfElseBranchActivity' nin Condition özelliğine Code Condition değerini atmamız yeterli olacaktır.

Burada Condition altında yer alan Condition özelliğinede bir metod adı verilmesi gerekmektedir. Örneğin Stok10ile50Arasindami ismini verip enter tuşuna bastığımızı düşünelim. Bunun sonucu olarak workflow1.cs içerisine aşağıdaki metodun eklendiği görülecektir.

private void Stok10ile50Arasindami(object sender, ConditionalEventArgs e)
{

}

Metodun bool tipinde bir değerlendirme yapması gerekmektedir. Buna karşın dikkat edileceği üzere void tipinde oluşturulmuştur. İşte bu noktada koşulun sonucunu geriye döndürmek için ConditionalEventArgs tipinden olan parametrenin, Result özelliğinden yararlanılır. Result özelliği bool tipindendir ve koşula göre true veya false değerini almalıdır. Buna göre kod aşağıdaki şekilde düzenlenebilir.

private void Stok10ile50Arasindami(object sender, ConditionalEventArgs e)
{
    if (StokMiktari >= 10 && StokMiktari <= 50)
        e.Result = true;
    else
        e.Result = false;
}

Eğer StokMiktari özelliğinin değeri 10 ile 50 arasında ise bu koşul sağlanmış demektir. Bu durumda Result özelliğine true değeri atanmalıdır. Eğer true değeri atanırsa IfElseBranchActivity' den sonra gelen aktivitenin çalıştırılması da sağlanmış olur. Elbetteki buradaki kod parçasında, koşulun sağlanmış olma veya olmama haline göre sıradaki aktiviteye geçilmeden önce farklı işlemler yapıtırılmasıda sağlanabilir.

Makalemizin son bölümünde iş akışını(Workflow) bir sınıf kütüphanesi(Class Library) olarak nasıl tasarlayabileceğimizi ve bunu örneğin bir windows uygulamasında nasıl host edebileceğimizi incelemeye çalışacağız. Bu sefer proje şablonu olarak Sequenatial Workflow Library modelini seçmemiz gerekiyor. Oluşan sınıf kütüphanesi(Class Library) içerisinde yine standart olarak Workflow1 isimli bir iş akışı nesnesi bulunur. Örnek olarak bir metin dosyasının içerisinde parametre olarak verilen bir kelimenin bulunması veya bulunmaması halinde yapılabilecek bazı işlemler olduğunu ve bunun bir iş süreci olarak göz önüne alındığını düşünelim. Söz konusu sürecin birden fazla .Net uygulaması içerisinde ele alınabileceğini düşünürsek iş akışının sınıf kütüphanesi olarak tasarlanması son derece mantıklıdır. İş akışının bu anlamda dışarıdan alması gereken iki adet parametre bulunmaktadır. Bunlardan birisi dosya adresini, diğeri ise aranacak bilgiyi tutmalıdır. Diğer taraftan iş akışından Host eden uygulamayada bilgi gönderilmesi istenebilir. Söz gelimi arama sonuçlarına dair bir string bilgi söz konusu olabilir. Doğal olarak 3ncü bir özelliğe daha gerek vardır. Bu nedenle workflow1 sınıfı içerisine aşağıdaki kod parçasında yer alan özelliklerin(Properties) eklenmesi gerekmektedir.

public sealed partial class Workflow1: SequentialWorkflowActivity
{
    private string _dosyaAdresi;
    private string _arananKelime;
     private string _aramaSonucu;


    public string AramaSonucu
    {
        get { return _aramaSonucu; }
        set { _aramaSonucu = value; }
    }

    public string ArananKelime
    {
        get { return _arananKelime; }
        set { _arananKelime = value; }
    }

    public string DosyaAdresi
    {
        get { return _dosyaAdresi; }
        set { _dosyaAdresi = value; }
    }

    public Workflow1()
    {
        InitializeComponent();
    }
}

İlk örneğimizdeki gibi bir IfElseActivity ve iki adet IfElseBranchActivity ekliyoruz. Burada dosya içerisinde bir bilgi arama işlemi yapılmak istendiğinden koşulun kod yardımıyla kontrol edilmesi, bu nedenle Code Condition seçeneğinin kullanılması daha mantıklıdır. Hatalara çok fazla takılmamak adına sadece txt uzantılı dosyaları ele aldığımızı düşünelim. IfElseBranchActivity1 bileşeninin Condition özelliğinin değerini Code Condition olarak ayarladıktan sonra alt Condition özelliğinede ArananKelimeVarmi bilgisini yazalim. Bu IfElseBranchActivity1 için çalışacak koşul kontrol metodunun adı olacaktır. IfElseBranchActivity2 bileşeni için herhangibir Condition ataması yapılmasına gerek yoktur. Nitekim otomatik olarak else durumunun değerlendirileceği yerdir. Her iki aktivitenin arkasından çalıştırılmak istenen kodların yer aldığı CodeActivity bileşenlerinide ekleyelim. Sonuçta iş akışının tasarım zamanındaki görüntüsü aşağıdaki gibi olacaktır.

CodeActivity1 için ArananBilgiVar isimli bir metod, CodeActivity2 bileşeni içinde ArananBilgiYok isimli bir metod devreye girecektir. Buna göre Workflow1 sınıfının başlangıçtaki içeriği aşağıdaki gibidir.

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;
using System.IO;

namespace StokAkislari
{
    public sealed partial class Workflow1: SequentialWorkflowActivity
    {
        private string _dosyaAdresi;
        private string _arananKelime;
        private string _aramaSonucu;

        public string AramaSonucu
        {
            get { return _aramaSonucu; }
            set { _aramaSonucu = value; }
        }

        public string ArananKelime
        {
            get { return _arananKelime; }
            set { _arananKelime = value; }
        }

        public string DosyaAdresi
        {
            get { return _dosyaAdresi; }
            set { _dosyaAdresi = value; }
        }

        public Workflow1()
        {
            InitializeComponent();
        }

        // IfElseBranchActivity1 bileşenine ait koşul kontrol metodu
        private void ArananKelimeVarmi(object sender, ConditionalEventArgs e)
        {
            if (File.Exists(DosyaAdresi))
            {
                StreamReader reader = new StreamReader(DosyaAdresi);
                e.Result = reader.ReadToEnd().Contains(ArananKelime);
            }
            else
                e.Result = false;
        }

        // CodeActivity1 için çalışacak olay metodu
        private void ArananBilgiVar(object sender, EventArgs e)
        {
            // Aranan bilgi bulunduğunda yapılacak işlemler
            AramaSonucu = ArananKelime + " " + DosyaAdresi + " içinde bulunmuştur";
        }

        // CodeActivity2 için çalışacak olay metodu
        private void ArananBilgiYok(object sender, EventArgs e)
        {
            // Aranan bilgi bulunamadığında yapılacak işlemler
            AramaSonucu = ArananKelime + " " + DosyaAdresi + " içinde bulunamamıştır";
        }
    }
}

Amacımız her zamanki gibi konuyu basitçe kavramak olduğundan CodeActivity bileşenleri ile ilişkili kod parçaları şimdilik göz ardı edilmiştir. Artık host uygulamayı yazarak devam edebiliriz. Bu amaçla basit bir Windows uygulaması tasarlayacağız. Uygulamamız text tabanlı dosyaların seçimine ve aranacak kelimenin girilmesine izin verecek bir arabirime sahip olacaktır. Buradaki asıl hedefimiz ise bir .Net uygulaması içerisinden herhangibir iş akışının(Workflow) nasıl çalıştırılabileceğini görmektir. Windows uygulamasında tahim edileceği gibi bazı Workflow assembly' larının ve iş akışlarını taşıyan kütüphanenin eklenmiş olması gerekmektedir. Sonuç itibariyle windows uygulamasında aşağıdaki şekilde görülen referansların dahil edilmesiyle işe başlanmalıdır.

Bu noktadan sonra Windows uygulamasının aşağıdaki gibi tasarlandığını düşünebiliriz. Kullanıcı Dosya Seç başlıklı düğmeye bastığında açılacak iletişim kutusu ile txt uzantılı dosya seçebilecektir. Aranacak kelime bilgiside girildikten sonra sürecin başlatılması için tek yapılması gereken Süreci Başlat başlıklı düğmeye basmak olacaktır.

Dosya seçme işlemi için OpenFileDialog bileşeni kullanılamktadır. Sadece Text tabanlı dosyalar ele alınmak istendiğinden Filter özelliğine Text Files|*.txt değeri atanmıştır. Uygulamanın kod içeriği ise aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Workflow.Runtime;
using System.Threading;
using StokAkislari;

namespace Istemci
{
    public partial class Form1 : Form
    {
        private WorkflowRuntime _wfRunTime;
        private WorkflowInstance _wfInstance;
        // Workflow thread' lerinin yönetimi için AutoResetEvent nesnesi kullanılır
        private AutoResetEvent _arEvent = new AutoResetEvent(false);

        public Form1()
        {
            InitializeComponent();

            // Workflow çalışma zamanı nesnesi örneklenir
            _wfRunTime = new WorkflowRuntime();
       
            // Workflow tamamlandığında devreye girecek olay metodu yüklenir
            _wfRunTime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
                                                                    {
                                                                        MessageBox.Show(e.OutputParameters["AramaSonucu"].ToString());
                                                                        _arEvent.Set();
                                                                    };

            // Workflow' un çalışması sırasında bir istisna oluştuğunda devreye girecek olay metodu yüklenir.
            _wfRunTime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                                                                    {
                                                                        MessageBox.Show(e.Exception.Message);
                                                                        _arEvent.Set();
                                                                    };
        }

        private void btnDosyaSec_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                txtDosyaAdresi.Text = openFileDialog1.FileName;
            }
        }

        private void btnSureciBaslat_Click(object sender, EventArgs e)
        {
            try
            {
                if (!String.IsNullOrEmpty(txtArananKelime.Text)
                        && !String.IsNullOrEmpty(txtDosyaAdresi.Text))
                {
                    // parametrelerin gönderilmesi için Dictionary koleksiyonu örneklenir
                    Dictionary<string, object> parametreler = new Dictionary<string, object>();

                    // Workflow1 içerisindeki özelliklerin alacağı değerler set edilir.
                    parametreler.Add("DosyaAdresi", txtDosyaAdresi.Text);
                    parametreler.Add("ArananKelime", txtArananKelime.Text);
   
                    // Workflow1 için bir örnek oluşturulur.
                    _wfInstance = _wfRunTime.CreateWorkflow(typeof(Workflow1), parametreler);
   
                    // Workflow1 başlatılır.
                    _wfInstance.Start();
   
                    _arEvent.WaitOne();
                }
                else
                    MessageBox.Show("Verilerde eksik var");
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message);
            }
        }
    }
}

Geliştirilen örnekte form nesnesi üretilirken iş akışı çalışma ortamı(Workflow Run-Time) için gerekli ayarlar yapılmaktadır. Bu amaçla WorkflowRuntime nesnesi örneklenmekte, WorkflowCompleted, WorkflowTerminated gibi olaylar isimsiz metodlar(Anonymous Methods) aracılığıyla yüklenmektedir. Bir önceki örnekten farklı olarak dikkat edilmesi gereken nokta WorkflowCompleted olay metodu içerisinde WorkflowCompletedEventArgs sınıfına ait OutputParameters özelliğinin kullanılışıdır. Bu özellikte Dictionary<string,object> tipinden generic bir koleksiyonu ele almaktadır. Özelliğin amacı, iş akışından uygulama ortamına değer aktarımını sağlamaktır. Bu amaçla indeksleyici(Indexer) operatörü içerisinde, iş akışında tanımlanan AramaSonucu özelliğinin adı verilmektedir.

Doğal olarak sürecin bir şekilde başlatılması gerekmektedir. Bu amaçla WorflowInstance örneği oluşturulduktan sonra, Start metodu çağırılmaktadır. Tüm bu başlatma işlemi ise Form üzerindeki bir Button kontrolüne ait Click olay metodu içerisinde gerçekleştirilmektedir. Örnek test edildiğinde bir döküman içerisinde aranan bilginin var olup olmadığına dair sonuçların alındığı görülecektir. (Tahmin edileceği üzere bu tarz bir ihtiyaç normal bir sınıf kütüphanesi içerisine alınacak bir tip ilede, iş akışlarına gerek olmadan tasarlanabilir. Ancak gerçek iş problemleri göz önüne alındığında tek başına yeterli bir çözüm olmayacaktır.)

Buraya kadar yazdıklarımız ile iş akışı(Workflow) kavramını, Windows Workflow Foundation yaklaşımını incelemeye çalıştık. Giriş niteliğindeki bu makalemizde, bir iş akışı için gerçek hayat senaryoları kullanmamış olsakta, WWF ile nasıl geliştirilebileceklerini, herhangibir .Net uygulamasından nasıl kullanılabileceklerini gördük. Bundan sonraki makalelerimizde WWF mimarisinin başka konularınıda incelemeye çalışıyor olacağız. Makalemize son vermeden önce Windows Workflow Foundation ile ilgili kaynak kitaplar hakkında bilgi vermek isterim. Söz konusu kitaplar ve bunlara ait özet bilgiler aşağıdaki tabloda yer aldığı gibidir.

Kitap Özet Bilgi
Microsoft Windows Workflow Foundation Step by Step

Mart 2007 tarihinde çıkan Microsoft Press' e ait bu kitap içerisindeki bilgiler ile WWF mimarisini adım adım öğrenmek mümkün. Aynen Microsoft Windows Communication Foundation Step by Step kitabında olduğu gibi oldukça iyi bir anlatıma sahip. Kısa sürede tamamlanabilecek bu kitap ile WWF mimarisini orta seviyede öğrenmek mümkün. Toplam 19 bölümden oluşan kitap içerisinde iş akışlarının SOA ile entegrasyonu, transaction desteği, paralel aktivitilerin(Activities) tasarlanması gibi ileri seviye konularda yer almakta.
Pro WF: Windows Workflow in .NET 3.0 (Expert's Voice in .Net)

Şubat 2007 tarihinde APress tarafından yayınlanan bu kitap 744 sayfalık bir içeriği 17 bölüm altında toplamakta. Workflow konusunda yazılmış kitaplar arasında temelden ileri seviyeye doğru giden ve en anlaşılır olanlarından bir tanesi. İleri seviye sayılabilecek bölümlerde, iş akışlarının dinamik güncellenmesi, iş akışlarında serileştirme, iş akışlarının izlenmesi gibi konu başlıklarıda yer almaktadır.

Professional Windows Workflow Foundation

Wrox yayınlarından Mart 2007 tarihinde çıkartılan bu kitap 410 sayfalık mütevazi bir içeriğe sahip. Lakin bu içerik sayesinde çok kısa zamanda Windows Workflow Foundation mimarisini anlamak ve etkin bir şekilde kullanabilmek mümkün. Üstelik kitapta best practices, ileri seviyede aktivite(Activity) tasarlanması, dinamik güncelleştirme, ofis(Office) sistemleri ile entegrasyon gibi enteresan kısımlarda yer almakta.

Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

WWFGiris.rar (83,28 kb)

Tags:  
Categories:   WF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share