Daha önceki yazılarda workflow’ları designer kısmından sürükle bırak yöntemi ile diagramımıza ekliyorduk. Bu workflow’ları kod yardımı ile de diagramımıza uygulayabiliriz. Her workflow kod ile ya da tasarım ile diagramlara eklenebilir, bu tamamen seçim meselesi. Fakat kod ile workflow’ları uygulamak size bu yapıların nasıl çalıştığına dair daha fazla bilgiler verecektir.
Başlangıç için bir Visual C# konsol uygulaması oluşturalım (WF konsol uygulaması değil!!). Daha sonra Solution Explorer kısmından Reference’a sağ tıklayıp Add Reference seçeneğini seçelim. Buradan System.Activities referansını seçip projemize ekleyelim. Bu durum bize workflow aktivitelerini kodumuzun içerisinde kullanma imkanı verir. Daha sonra kod kısmından aşağıdaki namespace’leri de ekleyelim;
using System.Activities.Statements;
using System.Activities.Expressions;
Daha sonra Main() methodumuzun içerisine aşağıdaki kodumuzu yazalım;
WorkflowInvoker.Invoke(WorkflowOlustur());
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
Burada WorkflowOlustur() methodumuzu biz kendimiz yazacağız. Bir önceki yazımda bahsettiğim gibi bir workflow sadece iç içe geçmiş özellikler topluluğudur. Daha doğrusu, iç içe geçmiş sınıflar ve bunları özellikleri topluluğudur. Şimdi WorkflowOlustur() methodumuzu yazmaya başlayalım.
static Activity WorkflowOlustur()
{
Variable<int> NumberOfBells = new Variable<int>()
{
Name = "NumberOfBells",
Default= DateTime.Now.Hour
};
Variable<int> Counter = new Variable<int>()
{
Name = "Counter",
Default = 1
};
return new Sequence()
{
};
}
WorkflowOlustur() methodumuz öncelikle 2 adet int tipinde Variable<T> template sınıfı oluşturduk. (NumberOfBells ve Counter) Bunlar çeşitli aktiviteler tarafından kullanılacak olan değişkenlerdir.
WorkflowInvoker sınıfının beklediği gibi, WorkflowOlustur() methodumuz geriye Activity döndürecek şekilde tanımlandı. Aslında geriye anonim bir Sequence sınıf örneği döndürür. Activity sınıfı, tüm workflow aktivitelerinin türetildiği ana sınıftır, buna Sequence dahil. Şimdi var olan kodumuzu adım adı geliştirmeye başlayalım;
1. Adım: Şimdiye kadar kodumuzda boş bir Sequence aktivitesi oluşturmuştuk. Bu kabaca Sequence’i olup hiçbir aktivitesi olmayan yeni bir workflow yaratmaya benzer. Şimdi yukarıda tanımladığımız Sequence() sınıfımızı yazalım;
return new Sequence()
{
DisplayName = "Main Sequence",
Variables = { NumberOfBells, Counter },
Activities =
{
new WriteLine()
{
DisplayName = "Merhaba",
Text = "Merhaba Dünya"
},
new If()
{
DisplayName = "PM için ayarla"
},
new While()
{
DisplayName = "Zil Sesi"
},
new WriteLine()
{
DisplayName = "Zamanı Göster",
Text = "Şu anki zaman" + DateTime.Now.ToString()
},
new If()
{
DisplayName = "Selam"
},
}
};
Yukarıdaki kodumuz önce aktiviteler için DisplayName ve içerisinde kullanılacak Variable nesnelerini tanımlar. Daha sonra da aşağıdaki aktiviteleri belirtilen DisplayName özelliği ile tanımlar.
Type |
DisplayName |
Writeline |
Merhaba |
If |
PM için ayarla |
While |
Zil Sesi |
Writeline |
Zamanı Göster |
If |
Selam |
2. Adım: Şimdi ilk If aktivitemizin içerisine koşul ifadelerini yazalım.
new If()
{
DisplayName = "PM için ayarla",
Condition = ExpressionServices.Convert<bool>
(env => NumberOfBells.Get(env) > 12),
Then = new Assign<int>()
{
DisplayName = "Zili Ayarla"
}
},
Bu kod if aktivitesinin koşul (condition) ve then kısımlarını tanımlar. Şimdi biraz Condition kısmına biraz daha yakından bakalım.
Deyimler
ExpressionServices sınıfının statik Convert<T>() methodu bir InArgument<T> sınıfı oluşturmak için kullanılır ki bu da tam olarak Condition özelliğinin istediği şeydir. Bu sınıf ve methodlar generic tipinde (<T>) olduklarından, her veri tipi için kullanılabilirler. Bu durumda bool tipini kullanmamız gerekir çünkü if aktivitesinin Condition özelliği sadece true ya da false destekler.
Bu deyim lambda expression kullanarak veriyi workflow’un çevresinden dışarı çıkartır. Burada gösterdiğimiz lambda expression deyiminde => lambda operatörü olarak gösterilir. Bu operatörün solundakiler giriş parametrelerini ifade ettiği gibi, gerçek koşul ifadesi bu operatörün sağında tanımlanır. env değeri runtime zamanında Condition kısmının çalıştırılmasını sağlar.
Aslında workflow herhangi bir veri elemanı depolamaz. Variable sınıfı basitçe veri tanımlar. Get() methodu ile Variable sınıfından veriler alınır. Get(env) ile geri dönen değeri 12’den büyük olup olmadığını karşılaştırır. Şimdi aşağıdaki kodumuzu While aktivitemize ekleyelim;
new While()
{
DisplayName = "Zil Sesi" ,
Condition = ExpressionServices.Convert<bool>
(env => Counter.Get(env) <= NumberOfBells.Get(env)),
Body = new Sequence()
{
DisplayName = "Zil Sesi"
}
},
Burada kullandığımız Condition kısmı If aktivitesi içerisinde kullandığımız ile özdeş bir kullanım. Tabi ki yine ExpressionServices sınıfınının (bool tipi ile) InArgument<T> sınıfı oluşturmak için kullanıyoruz. Bu durumda Counter <= NumberOfBells koşulunu hesaplarız. Bu iki değer için de, gerçek değerlerini hesaplamak için Get(env) methodunu kullanırız.
Şimdi de “Selam” adındaki ikinci If ifademize kodumuzu yazmaya başlayalım;
new If()
{
DisplayName = "Selam",
Condition = ExpressionServices.Convert<bool>
(env => DateTime.Now.Hour >= 18),
Then = new WriteLine() { Text = "İyi Akşamlar"},
Else = new WriteLine() { Text = "İyi Günler"}
},
Condition kısmında env giriş parametresi kullanılmaz fakat yine de deyim içerisinde tanımlanmak zorundadır. Mantıklı olarak saatin 6.00 PM’i geçip geçmediğine göre Then ve Else kısımlarında Writeline aktivitelerinde “İyi Akşamlar” ve “İyi Günler” yazdırılır.
Şimdi ilk If aktivitemiz “PM için ayarla” içerisine Assign aktivitesi (Then kısmı olan) oluşturmuştuk. Bu Assign aktivitemizin içerisine aşağıdaki kodlarımızı ekleyelim;
Then = new Assign<int>()
{
DisplayName = "Zili Ayarla",
To = new OutArgument<int>(NumberOfBells),
Value = new InArgument<int>(env => NumberOfBells.Get(env) - 12)
}
Assign Aktivitesi
Assign sınıfı generic tipindedir, yani herhangi bir veri tipini destekler. Bu durumda, integer tipinde bir değeri atamak için Assign<int> şeklinde kullanmamız gerekir. To ve Value özellikleri template sınıf olarak kullanılır ve aynı tipte (<int>) olmak zorundadırlar. To özelliği bir OutArgument sınıfıdır ve yapıcısının içerisinde bir Variable sınıfı olur. Value özelliği bir InArgument sınıfıdır. Yapıcısı için, Condition kısmında yaptığımız gibi lambda expression kullanımına ihtiyaç duyarız.
Sequence
While aktivitesi içerisinde boş bir Sequence aktivitesi oluşturmuştuk. Bunun anlamı while döngüsü her tekrarlandığında belirli olan aktivite çalıştırılır. Şimdi bu Sequence aktivitesine kodlarımızı yazalım;
Body = new Sequence()
{
DisplayName = "Zil Sesi",
Activities =
{
new WriteLine()
{
Text = new InArgument<string>(env => Counter.Get(env).ToString())
},
new Assign<int>()
{
DisplayName = "Sayacı Arttır",
To = new OutArgument<int>(Counter),
Value = new InArgument<int>(env => Counter.Get(env) + 1)
},
new Delay()
{
Duration = TimeSpan.FromSeconds(1)
}
}
}
Bu kod Sequence içerisine 3 adet aktivite ekler;
- Counter değişkenini göstermek için bir Writeline aktivitesi
- Counter değişkenini arttırmak için bir Assign aktivitesi
- Her öteleme sonrasında küçük bir duraklama için Delay aktivitesi
Burada yazdığımız Writeline aktivitesine bakarsak Text özelliği diğer kullanımlar gibi klasik string değil. Burada değer bir deyim ile gösterilir. Get(env) methodu ile geriye int bir değer döndürür bu deyim. Daha sonra da bu deyimi ToString() methodu ile string haline (Text özelliğinin istediği şekilde) getiririz.
Delay aktivitesinde ise Duration özelliğine TimeSpan sınıfının FromSeconds() statik methodu geçirilerek bu aktiviteyi kullanırız.
Şimdi projemizi F5 ile çalıştıralım. Günün hangi saatinde olduğunuza göre değişebilen çıktımız aşağıdaki gibi olacaktır;
Merhaba Dünya
1
2
3
4
5
6
Şu anki zaman 03.08.2012 18:57:11
İyi Akşamlar
Press ENTER to exit
Program.cs dosyamızın tüm kodu aşağıdaki şekildedir;
using System;
using System.Activities;
using System.Activities.Statements;
using System.Activities.Expressions;
namespace WF_Konsol_Uygulama
{
class Program
{
static void Main(string[] args)
{
WorkflowInvoker.Invoke(WorkflowOlustur());
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
static Activity WorkflowOlustur()
{
Variable<int> NumberOfBells = new Variable<int>()
{
Name = "NumberOfBells",
Default= DateTime.Now.Hour
};
Variable<int> Counter = new Variable<int>()
{
Name = "Counter",
Default = 1
};
return new Sequence()
{
DisplayName = "Main Sequence",
Variables = { NumberOfBells, Counter },
Activities =
{
new WriteLine()
{
DisplayName = "Merhaba",
Text = "Merhaba Dünya"
},
new If()
{
DisplayName = "PM için ayarla",
Condition = ExpressionServices.Convert<bool>
(env => NumberOfBells.Get(env) > 12),
Then = new Assign<int>()
{
DisplayName = "Zili Ayarla",
To = new OutArgument<int>(NumberOfBells),
Value = new InArgument<int>(env => NumberOfBells.Get(env) - 12)
}
},
new While()
{
DisplayName = "Zil Sesi" ,
Condition = ExpressionServices.Convert<bool>
(env => Counter.Get(env) <= NumberOfBells.Get(env)),
Body = new Sequence()
{
DisplayName = "Zil Sesi",
Activities =
{
new WriteLine()
{
Text = new InArgument<string>(env => Counter.Get(env).ToString())
},
new Assign<int>()
{
DisplayName = "Sayacı Arttır",
To = new OutArgument<int>(Counter),
Value = new InArgument<int>(env => Counter.Get(env) + 1)
},
new Delay()
{
Duration = TimeSpan.FromSeconds(1)
}
}
}
},
new WriteLine()
{
DisplayName = "Zamanı Göster",
Text = "Şu anki zaman " + DateTime.Now.ToString()
},
new If()
{
DisplayName = "Selam",
Condition = ExpressionServices.Convert<bool>
(env => DateTime.Now.Hour >= 18),
Then = new WriteLine() { Text = "İyi Akşamlar"},
Else = new WriteLine() { Text = "İyi Günler"}
},
}
};
}
}
}
Bu yazımızda Workflow Foundation yazı serimizin ilk makalelerinde tasarım kısmı ile aktivite oluşturulmasına karşın kod ile nasıl aktivite oluşturulur ve bu aktivitelerin özelliklerinin hangi tür değişkenler ve ifadeler aldıklarını gördük.