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

Correlation Nedir? Yenir mi? İçilir mi?

Pazartesi, 1 Şubat 2010 09:25 by bsenyurt

Merhaba Arkadaşlar,

Bazen bir kavramı yada konuyu anlamakta inanılmaz zorlandığınızı hatırlayın. Ne yaparsınız? Kimisi kendisini yemeğe verir. Kimisi hayat küsermişçesine bir köşeye çekilir. Kimisi kendiyle baş başa kalır ve çığlık çığlık haykırır. Kimisi de daha akıllı davranıp bir süre tatile çıkar veya anlayamadığı kavramla ilişkili herhangibir dökümanı bir süreliğine araştırmamaya, okumamaya karar verir. Neredeyse unuturcasına bir zaman koyar araya. Sonrasında ise aynı konuyu tekrar araştırmaya karar verir. İnanın başarılı olma şansı bir önceki denemeye göre çok daha yüksek olacaktır. Önemli olan noktalardan birisi, yılmadan bu iterasyona devam edebilmektir. Okudunuz, hala anlamadınız...Kısa bir ara daha...Sonra tekrar aynı konu ama mümkünse farklı kaynaklarla...Wink

Bende bir süredir Workflow Service' lerde oldukça önemli olan konulardan birisi üzerinde araştırmalarımı tamamen durdurmuştum. Correlation. Çünkü; Matematikte "bağlılaşım/korelasyon", ekonomide "bağlanım", nükleer bilimlerde "bağlantı/eş ilişki", denizbilimde "kaçınım", tıpta "Aferent uyarıların gerekli cevabı oluşturmak üzere beyinin ilgili merkezinde birleşmesi" olarak çevirileri yer alan bu kavramın, Workflow Services içerisinde ne anlama geldiğini anlamak için epey bir süre tepinmem gerekmişti. Geçtiğimiz günlerde aynı konu üzerinde yeniden durmaya ve araştırmaya ve edindiğim bilgileri sizlere paylaşmaya karar verdim. İşte elde ettiğim sonuçlar;

Correlation kavramını mesajları bir arada gruplamanın bir yolu olarak düşünülebilir ilk etapta. Örneğin bir talep(Request) ve bu talebe karşılık gönderilen cevap(Reply) arasındaki ilişki Correlation olarak ifade edilmektedir. Özellikle WCF(Windows Communication Foundation) tarafında Session bazlı haberleşmelerde mesajlar arasında bir Correlation oluştuğu söylenebilir. Ancak Correlation' ın farklı bir yönü daha vardır. Bir servis örneği(Instance) ile bir oturum(Session) arasında da bağıntı kurulabilir. Yani bir SessionId değerine ait olaraktan hareket eden mesajların içeriğinde yer alan bazı veri parçalarının, Session ile alakalı bir servis örneği ile eşleştirilmesi mümkün olabilir. Bir başka deyişle aynı SessionId değeri altındaki mesajların her zaman için aynı servis örneğine ait olduğunun anlaşılmasında, SessionID=Service Instance ID eşitliğinin sağlanması da Correlation olarak ifade edilebilir. Bu ilişki çoğunlukla bilinçsiz(Implicit) olarak sağlanır. Yani, geliştiricinin çoğu zaman bir aksiyonda bulunmasına gerek yoktur. Aslında bu durumu aşağıdaki şekilde olduğu gibi canlandırabiliriz.

Ancak Workflow örneklerinde uzun zaman süren süreçlerin ele alınması da söz konusudur(Long Running Process). İstemcilerin, Workflow Service' ler ile olan haberleşmelerinde aradaki oturumu kapatıp ayrılmaları bu tip süreçlerde son derece yaygındır. Buna göre istemci ile servis arasındaki oturumun her an sonlanabilir olması önemli bir sorunu ortaya çıkarmaktadır; Correlation nasıl sağlanacak? Undecided

Bu amaçla Workflow Service' lerde Correlation' ın sağlanması için kullanılan çeşitli teknikler mevcuttur. Aslında burada da tam bir kavram kargaşası vardır. En güvenilir kaynaklardan birisi olarak ele alacağımız MSDN, Correlation' ı Content-Based ve Protocol-Based olmak üzere iki çeşide ayırmıştır. Protocol-Based Correlation' da kendi içerisinde Context ve Request-Reply isimli iki farklı Correlation tekniğini daha barındırmaktadır.

Content-Based Correlation' da service örneği(Instance) ile ilişkili olan veri(Map edilmiş veri olaraktan da düşünebiliriz) mesajın içeriğinde yer alır. Örneğin mesajın Header veya Body kısımlarında bulunabilir. Dolayısıyla Correlation' ı sağlayan arabirimlerin XML tabanlı olan bu veri içeriğini kontrol edebilmesi gerekmektedir. İşte bu noktada XPath gibi sorgu teknikleri devreye girmektedir. Protocol-Based Correlation ise iletişim mekanizmasını baz alır ve buna göre mesajlar arasında yada mesajlar ile doğru servis örneği arasında gerekli eşleştirmeyi sağlar.

Bu giriş yazımızda özellikle Content-Based Correlation üzerinde durabiliriz. En çok örneklenen model genellikle budur. Workflow Service' lerde servise gelen ve servisten giden mesajların içerdiği veri parçaları ile çalışma zamanındaki servis örnekleri arasında eşleştirme yapmak(yani Correlation' ı sağlamak) son derece kolaydır. Tüm mesajlaşma aktivitelerinin CorrelationInitializers isimli bir koleksiyonu bulunur. Bu koleksiyon içerisinde Key-Query çiftleri yer almaktadır. Tahmin edileceği üzere Key değerleri ile Query' lerin birbirlerinden ayrıştırılması sağlanır. En önemli nokta ise Query' dir. Query içerisinde yer alan XPath sorgusu ile Content içerisindeki veri işaret edilir. Tabi bir mesajlaşma aktivitesi, diğer bir mesajlaşma aktivitesinin başlattığı Correlation' u takip edebilmelidir. Bunun içinde CorrelationWith isimli özellikten yararlanılır. Bu bilgilere göre Correlation' ın bir şekilde başlatılması(Initialize) ve takip edilmesi(Follow) gerektiği anlaşılmaktadır. Başlatılan bir Correlation' ın takip edilmemesi halinde mesajlar ve servis örneği arasında bir eşleştirmenin yapılması söz konusu olmayacaktır. Elbette Workflow Foundation 4.0 içerisinde bir Correlation' ın başlatılmasını kolaylaştıran bir aktivitede gelmektedir. InitializeCorrelation bileşeni.

Sanırım şu ana kadar anlattıklarımız ile kafamızda Correlation' ın ne olduğuna dair bir fikir oluşmuştur. Tabi konuyu kavramak tek başına yeterli değildir. Pratiğe dökmemizde yarar vardır. Ancak şu an için bu konudaki araştırmaya ara verip tatile çıkmak niyetindeyim. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Webiner - Workflow Foundation 4.0 - Introduction [Beta 2]

Çarşamba, 20 Ocak 2010 22:51 by bsenyurt

Merhaba Arkadaşlar,

.Net Framework 3.0 sürümü ile birlikte gelen köklü yeniliklere baktığımızda Windows Presentation Foundation(WPF), Windows Communication Foundation(WCF) ve Workflow Foundation(WF) alt yapı modellerinin ön plana çıktıklarını görmekteyiz. Servis bazlı çözümlere yeni bir yaklaşım getiren WCF, kod akışlarının görsel olarak geliştirilebilmesini sağlayan WF ve windows programlamaya tamamen farklı bir görsellik kazandıran XAML bazlı WPF.

.Net Framework 3.5 versiyonu yayınlandığında ise özellikle, WCF ve WF modellerinin bir birleriyle biraz daha haşır neşir olduklarını, içerisinde insan faktörü bulunan uzun süreli iş akışlarının geliştirilmesinde önemli ilerlemeler kaydedildiğini, iş akışı bazlı servislerin daha kolay yazılabildiğini gördük.

Gün geldi çok doğal olarak .Net Framework 4.0' ın ayak sesleri duyulmaya başlandı.(Hatta 2008 yılının yaz aylarında yapılan Microsoft PDC' de duymaya başladık) Bir kaç aya kadar Release sürümünün de çıkması beklenen .Net Framework 4.0 içerisinde gerek WCF gerek WF tarafında önemli yenilikler olduğunu görmekteyiz.

İşte bu Webinerimizde Workflow Foundation 4.0 modelini incelemeye ve öne çıkan yenilikleri değerlendirmeye çalıştık. Önceki sürümde yer alan zorlukların anlatımı ile başlanan webinerimizde, kısaca Workflow Foundation 4.0 ile gelen önemli yenilikler irdelenip basit bir örnek geliştirilmekte. 

NedirTV?com araclılığıyla gerçekleştirilen ve 20.Ocak.2010 Çarşmaba günü saat 21:00 ile 22:00 arasında başlayacak olan webinere kayıt olmak için http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032439662&EventCategory=4&culture=tr-TR&CountryCode=TR adresini kullanabilirsiniz.

Keyifli seyirler dilerim.

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

Workflow Services 4.0 - Transaction Flow [Beta 2]

Çarşamba, 20 Ocak 2010 10:00 by bsenyurt

Merhaba Arkadaşlar,

Geçen gece ilginç bir rüya gördüm. Bir su birikintisine damlacıklar düşüyordu. Önceleri yavaş yavaş ve uzun aralıklarla düşen damlalar söz konusuydu. Zaman ilerledikçe her bir damlanın suya değdiği noktada bir isim bıraktığını görmeye başladım. int i, for, if derken damlalar hızlanmaya başladı. Daha sık daha çok damla düşüyordu. Bazıları kocaman boyutlardaydı ve düştükleri su birikintisinde neredeyse fırtına koparıyorlardı. .Net, C#, parallel, WPF, Ajax, ASP.Net, WCF, WF derken damlaların artık nerelerden geldiğini takip edemez olmaya başladım. Ama damlalar iz bırakmaya devam ediyordu. 1.1, 2.0, 3.5, 4.0, Beta, RC, RTM...derken terler içerisinde uyanmıştım Wink

Bir kaç ay içerisinde eğer büyük bir aksilik olmassa .Net Framework 4.0 ve Visual Studio 2010 ürünlerinin son sürümleri yayınlanmış olacak. Şu anda gelişmeleri Beta 2 sürümü üzerinden takip etmekteyiz. Ancak yakında RC ve sonrasında RTM sürümlerininde çıkacağını ve önemli iyileştirmeler olacağını biliyoruz. Yinede gelebilecek yenilikleri takip etmek adına araştırmalarıma devam etmekteyim. Bir süredir Workflow Foundation 4.0 üzerine araştırma yapmıyordum. Geçtiğimiz günlerde Transaction yönetimi ile ilişkili olarak önemli bir açığın kapatıldığını öğrendim. Buna göre Workflow Foundation 4.0 öncesinde, Workflow Service' lerde istemci tarafından başlatılan Transaction' ların, servis tarafına akması mümkün olmuyordu. Şimdi bir dakika...Transaction, Flow, İstemciden Sunucuya...Ihmmmm Sealed Biraz kafamız karışmış olabilir. Başlamadan önce bu konu hakkında biraz bilgi vermeye çalışalım dilerseniz.

Bildiğiniz üzere bir Transaction içerisinde baştan sona başarılı bir şekilde tamamlanması beklenen işlemler bütünü yer alır. Transaction başlatıldıktan sonra içerisinde ceyran eden işlemlerin kalıcı olarak kabul görmesi, ancak tüm adımlardaki işlemlerin başarılı olmasına bağlıdır. Herhangibir adımda bir hata oluştuğunda Transaction' a dahil olan herkesin, Transaction başlamadan önceki durumlarına(State) dönebilmesi gerekir. Üstelik bu geri dönüşlerde(Rollback) verilerin tutarlılığını korumak önemlidir. Hatta Transaction başarılı bir şekilde sonuçlandırıldığında, çıkan verilerin de anlamlı olması beklenir. Kısaca size ACID(Atomicity,Consistensy,Isolation,Durability) kavramını aktarmaya çalıştım. Tabi zaman ilerledikçe bir Transaction' ın sadece tek bir program alanı içerisinde değil, birden fazla program alanı içerisinde ele alınmasının gerektiği durumlar oluşmuştur. Bu noktada Dağıtık Transaction(Distributed Transaction) kavramına geçilmektedir. Buna göre bir program alanı içerisinde başlatılan Transaction, başka bir program alanı içerisinde de ele alınabilir. Elbette bu tip bir senaryoda, program alanlarının farklı makineler üzerinde konuşlandırılmış olması da kuvvetle muhtemeldir. İşte bu gibi durumlarda Transaction' ın koordinasyonu için genellikle 3ncü parti araçların devreye girdiği görülmektedir(Distributed Transaction Coordinator vb...)

Gel gelelim zaman içerisinde söz konusu Transaction' ların servisler üzerinden akması ihtiyacı doğmuştur. Buna göre bir servis tarafından başlatılan bir Transaction' a, diğer bir serviste başlatılan operasyonun da dahil olması gerekebilir. Bu durumda ilgili servis operasyonlarının tamamının aynı Transaction Scope içerisinde ele alınması gerekmektedir. Transaction Scope denilince aklıma gelen ilk şey ise Ado.Net 2.0 ile birlikte gelen TransactionScope tipidir. Bu tip sayesinde, blok içerisine dahil olan farklı bağlantılar(Connection) için aynı Transaction Scope' un oluşturulması ve yönetilmesi son derece kolaylaşmıştır.

Şimdi gelelim bu güne. Artık elimizin altında bir Workflow' un servis bazlı olarak sunulabilmesi imkanı bulunmakta. Uzun süredir. Buna göre istemcilerin, söz konusu Workflow' ları servis bazlı olaraktan talep edebilmesi mümkün. Hal böyle olunca istemci tarafından başlatılacak bir Transaction' ın, çağrıda bulunulan Workflow Service tarafından' da ele alınabiliyor olması istenen bir özelliktir. Dolayısıyla istemcide açılan Transaction' ın Workflow Service tarafına akabiliyor(Flow) olması gerekmektedir.

Workflow Foundation 4.0 Beta 2 sürümünde söz konusu işlevselliği sağlamak için Messaging kontrollerinde yer alan TransactedReceiveScope isimli aktivite bileşeninden yararlanılmaktadır. Bu bileşen içerisinde istemcilerin çağrıda bulunacağı operasyon bildirimi yer alır. Bunun içinde Receive aktivite bileşeninden yararlanılmaktadır. Dilerseniz olayı kavramak için basit bir örnek geliştirelim. Örneğimizde aşağıdaki XAML içeriğine sahip bir Workflow Service oluşturduğumuzu göz önüne alalım.

Xaml içeriğimiz;(Sadece Sequence içeriği belirtilmiştir)

<p:Sequence DisplayName="Sequential Service" sad:XamlDebuggerXmlReader.FileName="G:\Projects\Workflow Foundation\Transactions\TransactionFlow\MathFlowService.xamlx" sap:VirtualizedContainerService.HintSize="325,797">
    <p:Sequence.Variables>
      <p:Variable x:TypeArguments="CorrelationHandle" Name="handle" />
      <p:Variable x:TypeArguments="x:Int32" Name="XValue" />
      <p:Variable x:TypeArguments="x:Int32" Name="YValue" />
      <p:Variable x:TypeArguments="x:Int32" Name="SumResult" />
    </p:Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <TransactedReceiveScope Request="{x:Reference __ReferenceID0}" sap:VirtualizedContainerService.HintSize="303,673">
      <p:Sequence sap:VirtualizedContainerService.HintSize="277,474">
        <sap:WorkflowViewStateService.ViewState>
          <scg3:Dictionary x:TypeArguments="x:String, x:Object">
            <x:Boolean x:Key="IsExpanded">True</x:Boolean>
          </scg3:Dictionary>
        </sap:WorkflowViewStateService.ViewState>
        <p:InvokeMethod sap:VirtualizedContainerService.HintSize="255,127" MethodName="WriteTransactionInfo" TargetType="t:Helper" />
        <p:Assign sap:VirtualizedContainerService.HintSize="255,57">
          <p:Assign.To>
            <p:OutArgument x:TypeArguments="x:Int32">[SumResult]</p:OutArgument>
          </p:Assign.To>
          <p:Assign.Value>
            <p:InArgument x:TypeArguments="x:Int32">[XValue + YValue]</p:InArgument>
          </p:Assign.Value>
        </p:Assign>
        <SendReply DisplayName="SendResponse" sap:VirtualizedContainerService.HintSize="255,86">
          <SendReply.Request>
            <Receive x:Name="__ReferenceID0" CanCreateInstance="True" DisplayName="ReceiveRequest" sap:VirtualizedContainerService.HintSize="277,86" OperationName="Sum" ServiceContractName="p1:IMathFlowService">
              <Receive.CorrelatesOn>
                <MessageQuerySet />
              </Receive.CorrelatesOn>
              <Receive.CorrelationInitializers>
                <RequestReplyCorrelationInitializer CorrelationHandle="[handle]" />
              </Receive.CorrelationInitializers>
              <ReceiveParametersContent>
                <p:OutArgument x:TypeArguments="x:Int32" x:Key="x">[XValue]</p:OutArgument>
                <p:OutArgument x:TypeArguments="x:Int32" x:Key="y">[YValue]</p:OutArgument>
              </ReceiveParametersContent>
            </Receive>
          </SendReply.Request>
          <SendMessageContent DeclaredMessageType="x:Int32">
            <p:InArgument x:TypeArguments="x:Int32">[SumResult]</p:InArgument>
          </SendMessageContent>
        </SendReply>
      </p:Sequence>
    </TransactedReceiveScope>
  </p:Sequence>

Akışımız içerisinde birde yardımcı sınıf bulunmaktadır. Helper isimli sınıf içerisinde InvokeMethod aktivitesi tarafından çalıştırılan ve güncel Transaction ile ilişkili bilgileri dosyaya aktaran basit bir fonksiyonellik yer almaktadır.

using System;
using System.Text;
using System.Transactions;
using System.IO;

namespace TransactionFlow
{
    public class Helper
    {
        public static void WriteTransactionInfo()
        {
            StringBuilder builder = new StringBuilder();

            TransactionInformation currentTrx = Transaction.Current.TransactionInformation;
            builder.AppendLine(String.Format("Oluşturulma zamanı {0}", currentTrx.CreationTime.ToString()));
            builder.AppendLine(String.Format("Local Identifier değeri {0}", currentTrx.LocalIdentifier.ToString()));
            builder.AppendLine(String.Format("Distributed Identifier değeri {0}", currentTrx.DistributedIdentifier.ToString()));

            File.WriteAllText("c:\\TransctionInformations.txt", builder.ToString());
        }
    }
}

Workflow çok basit olarak istemciden gelen iki sayının toplamını geriye döndürmek üzerine tasarlanmıştır. Ancak dikkat edilmesi gereken nokta kullandığı Transaction bilgileridir. Tabi şu noktada unutulmamalıdır. Hem Workflow Service hemde istemci uygulama System.Transactions.dll assembly' ını referans etmelidir. Helper sınıfı içerisinde yer alan WriteTransactionInfo metodu text tabanlı bir dosya içerisine eğer varsa güncel transaction bilgilerini yazdırmaktadır. Bunlardan ilki transaction oluşturulma zamanıdır(CreationTime). Sonrasında yerel transaction değeri(LocalIdentifier) ve dağıtık transaction değerleri(DistributedIdentifier) yazdırılır. Bu değerler GUID tipindendir.

Workflow Service tarafında istemciden gelecek Transaction akışına izin vermek için sadece TransectedReceiveScope bileşeninin kullanılması yeterli değildir. Konfigurasyon içerisinde de transaction akışına izin verileceğinin bildirilmesi gerekir. Bu amaçla Workflow Service uygulamasındaki web.config içeriği aşağıdaki gibi düzenlenebilir.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding transactionFlow="true"/>
      </wsHttpBinding>     
    </bindings>
    <services>
      <service name="MathFlowService">
        <endpoint address="" binding="wsHttpBinding" contract="IMathFlowService"/>                 
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>         
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

Dikkat edileceği üzere wsHttpBinding bağlayıcı tipi(Binding Type) için transactionFlow niteliğine true değeri atanmıştır. Bu zaten Windows Communication Foundation(WCF) tarafından bildiğimiz bir ilkedir. Şimdi gelelim istemci uygulama tarafına. Console projesi şeklinde tasarlanan istemci uygulamaya öncelikli olarak Workflow Service için gerekli proxy içeriği Add Service Reference seçeneği ile eklenmelidir. Tabi buna uygun olarak istemci tarafında üretilen app.config içerisinde de transaction akışı için gerekli bildirimler otomatik olarak üretilecektir. Bunu takiben Main metodunda aşağıdaki örnek kodların geliştirildiğini düşünelim.

using System;
using System.Text;
using System.Transactions;
using ClientApp.MathFlowSpace;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (TransactionScope scope = new TransactionScope())
            {
                MathFlowServiceClient proxy = new MathFlowServiceClient();

                Console.WriteLine("Çağrı öncesi Transaction bilgileri");
                WriteTransactionInfo();

                int? result = proxy.Sum(new Sum { x = 1, y = 4 });

                Console.WriteLine("Çağrı sonrası Transaction bilgileri");
                WriteTransactionInfo();
            }
        }

        public static void WriteTransactionInfo()
        {
            StringBuilder builder = new StringBuilder();

            TransactionInformation currentTrx = Transaction.Current.TransactionInformation;
            builder.AppendLine(String.Format("Oluşturulma zamanı {0}", currentTrx.CreationTime.ToString()));
            builder.AppendLine(String.Format("Local Identifier değeri {0}", currentTrx.LocalIdentifier.ToString()));
            builder.AppendLine(String.Format("Distributed Identifier değeri {0}", currentTrx.DistributedIdentifier.ToString()));

            Console.WriteLine(builder.ToString());
        }
    }
}

İstemci uygulama tarafında en önemli nokta TransactionScope kullanımıdır. Ayrıca, Workflow Service operasyonunun çağırılmasından hemen önce ve sonra ortamdaki güncel Transaction bilgilerinin yazdırılması sağlanmıştır. Buna göre örnek bir çalışma zamanı çıktısı aşağıdaki gibi olacaktır.

Dikkat edileceği üzere istemci tarafında servis çağrısının yapılmasından sonra oluşan ve servis tarafında dosyaya yazılan Transaction bilgilerindeki DistributedIdentifier değerleri aynıdır. Bir başka deyişle istemci ve Workflow Service tarafı aynı Transaction alanı içerisinde çalıştırılmıştır. Hemen tersi durumu ispat etmeye çalışalım. Bunun için her iki taraftaki config dosyalarında yer alan transactionFlow niteliklerine false değer verdiğimizi düşünelim. Örneği tekrardan çalıştıralım. İşte sonuç;

Görüldüğü gibi operasyon çağrısı sonucu istemcide üretilen ve servis tarafında dosyaya yazılan DistributedIdentifier değerleri 0' dır. 0 olması zaten bir dağıtık transaction oluşturulmadığı/oluşturulamadığı anlamına gelmektedir. İşte bu kadar.Wink Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Transactions.rar (54,45 kb)

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

Workflow Foundation 4.0 - Declarative Validation [Beta 2]

Salı, 19 Ocak 2010 15:00 by bsenyurt

Merhaba Arkadaşlar,

Sakin bir Cuma gününde bilgisayarın başında kahvemi yudumlarken ve M&M drajelerinden avuç avuç yerken araştırmalarıma devam ediyordum. Bir süredir Workflow Foundation 4.0 ile birlikte gelen yenilikleri incelediğimden takip ettiğim bloglar ve MSDN üzerinde bu konu ile ilişkili yazıları okumaktaydım. Özelliklede son iki yazımda üzerinde durmaya çalıştığım özel aktivite bileşenlerinin doğrulanması konusunu irdelemekteydim. Bu yazımızda doğrulama(Validation) ile ilişkili araştırmalarımı sizlerle paylaşmaya devam ediyor olacağım.

Doğrulama işlemlerinin çeşitlerine baktığımızda Declarative Constraint isimli bir yaklaşımın daha olduğu görülmektedir. Bu yaklaşıma göre bir aktivite ile ilişkili doğrulama mantığının kısıt olaraktan(Constraint) ayrı bir tip ve metod içerisinde konuşlandırılması mümkündür. (Hatta kod dışında XAML bazlı olaraktan kısıtların konulmasıda söz konusudur) Workflow Foundation alt yapısında bu tip dekleratif doğrulamalar için Constraint sınıfından yararlanılmaktadır. Constraint  tipi aslında NativeActivity türevidir. Bir başka deyişle bir aktivitedir.

Yukarıdaki şekildende görülebileceği gibi Constraint abstract bir sınıftır(dolayısıyla kendisinden türeyen tiplerin mutlaka uyması gereken kuralları bildiren, örneklenemeyen ama kendisinden türeyen tip örneklerini taşıyabilen bir sınıftır) ve NaticeActivity tipinden türemektedir. Bu türetme nedeniyle aslında Workflow çalışma zamanının çeşitli materyallerine erişebildiğini(Scheduling, Bookmarks vb...) söyleyebiliriz. Peki pratikte kendi geliştireceğimiz aktivite bileşenleri için gerekli kısıtları nasıl koyabiliriz? MSDN üzerinde bu konu ile ilişkili olarak geliştirilen basit örnekte bir aktivite bileşeninin DisplayName özelliğinin 2 karakterden fazla olması gerekliliğinin örneklendiği görülmektedir. Bizde buna benzer bir kısıtlama geliştiriyor olacağız. Ancak örneğin farklı olması açısından, hayali yazılım şirketinin kod standartlarına göre DisplayName özelliğinin Chinook ön eki ile başlaması için bir kısıt getireceğiz. İşte Activity Library içerisinde tuttuğumuz örnek sınıf kodlarımız.

using System.Activities;
using System.Activities.Validation;

namespace CustomActivities
{
    public static class ActivityConstraints
    {
        // Constraint oluşturup geriye döndürecek basit bir static metod
        public static Constraint ValidateActivityDisplayNameForCompanyCodeStandards()
        {
            DelegateInArgument<Activity> element = new DelegateInArgument<Activity>();

            // Herhangibir Activity(T generic tipi olarak Activity kullanıldığından) bileşeninin doğrulanmasında kullanılacak olan Constraint tipi örneklenir.
            // Constraint tipi aslında NativeActivity türevli bir Activity bileşenidir.
            Constraint<Activity> constraint = new Constraint<Activity>
            {
                // Body kısmı doğrulama mantığını içermektedir ve ActivityAction tipindendir               
                Body = new ActivityAction<Activity, ValidationContext>
                {
                    Argument1 = element,                   
                    Handler = new AssertValidation
                    {
                        // Warning bilgisi gösterilmeyecektir. Yani Error mesajı verilecektir. IsWarning özelliğinin varsayılan değeri false' dur.
                        IsWarning=false,
                        // e, ActivityContext tipinden bir referanstır. Dolayısıyla Constraint' in uygulanacağı aktivite bileşeninin güncel içeriğine erişilebilmesi mümkündür.
                        // Örnek doğrulamaya göre Actitiy örneğinin DisplayName özelliğinin Chinnok kelimesi ile başlaması beklenmektedir.
                        Assertion=new InArgument<bool>(
                            e=>                               
                                element.Get(e).DisplayName.StartsWith("Chinook")
                            ),
                            // Eğer Chinook ismi ile başlanılmıyorsa bir hata mesajı verilir.
                        Message=new InArgument<string>("Şirketin kod standartları gereği, özel Activity adlarının Chinook ile başlaması gerekmektedir."),
                        DisplayName="DisplayNameValidationActivity"                        
                    }
                }
            };
            return constraint;
        }
    }
}

ActivityConstraints isimli static sınıf içerisinde yer alan ValidateActivityDisplayNameForCompanyCodeStandards isimli metod geriye Constraint tipinden bir referans döndürmektedir. Constraint tipinin üretimi sırasında dikkat edileceği üzere Handler özelliğine AssertValidation tipinden bir referans atanmaktadır. İşte bu sınıfın örneklenmesi sırasında kullanılan Assertion özelliği ilede bir Expression tanımlaması yapılmakta ve bu kısıtın uygulandığı Activity bileşeninin DisplayName özelliğinin Chinook ile başlayıp başlamadığı kontrol edilmektedir. Bu ifadeden dönecek değer göre Message özelliğine atanan bilginin derleme zamanında gösterilmesi sağlanmaktadir. IsWarning özelliği varsayılan olarak false değere sahiptir ve buna göre mesajın bir Error olarak gösterilmesi sağlanmaktadır. Ancak bu özelliğe true değerini atayaraktan Warning olarak gösterilmesi de sağlanabilir.  Peki bu kısıt bir aktivite bileşenine nasıl uygulanabilir? Aslında bunun için geliştiriken aktiviteye bir bildirimde bulunulması yeterlidir. Aşağıdaki kod parçasında yer alan aktivite bileşeninde bu durum ele alınmaktadır.

using System.Activities;
using System.Activities.Validation;

namespace CustomActivities
{
    public enum LogSource
    {
        File,
        Database,
        WebService,
        System
    }

    // Loglama yapan örnek bir aktivitedir.
    public sealed class LogActivity
        : CodeActivity<bool>
    {
        public InArgument<LogSource> LogSourceType { get; set; }

        public LogActivity()
        {
            // Constraint' lerin bir aktivite ile ilişkilendirilebilmesi için base referans üzerinden ilgili koleksiyona eklenmesi gerekmektedir.
            // Constraints özelliği bir koleksiyonu referans ettiği için bir aktiviteye birden fazla Constraint yüklenmesi mümkündür.
            base.Constraints.Add(ActivityConstraints.ValidateActivityDisplayNameForCompanyCodeStandards());
        }

        protected override bool Execute(CodeActivityContext context)
        {
            //TODO@Burak: Gerçektende loglama işlemi yapılması için gerekli kodlar yazılmalı
            switch (LogSourceType.Get(context))
            {
                case LogSource.File:
                    break;
                case LogSource.Database:
                    break;
                case LogSource.WebService:
                    break;
                case LogSource.System:
                    break;
                default:
                    break;
            }

            return true;
        }
    }
}

Burada odaklanılması gereken tek bir yer vardır...Yapıcı metodun(Constructor) içerisinde yer alan kod satırı. Dikkat edileceği üzere Constraints özelliği üzerinden ValidateActivityDisplayNameForCompanyCodeStandards  metodunun dönüş referansının ilgili koleksiyona eklenmesi sağlanmaktadır. Buna göre, LogActivity bileşeninin Visual Studio ortamında kullanıldığı durumlarda DisplayName özelliğinin ilgili kısıta göre kontrol edileceği bildirilmiş olmaktadır. Dilerseniz birde bileşeni test edelim. İşte bileşeni örnek bir Workflow üzerine ilk kez sürükleyip bıraktığımızdaki durum;

Görüldüğü üzere DisplayName özelliği için bir hata mesajı alınmıştır. Buna göre Chinook ön ekin kullandığımızda sorunun ortadan kalktığı görülebilir.

Constraint kullanımı dikkat edileceği üzere herhangibir aktivite bileşeni için kısıt koyabilmeyi olanaklı hale getirmektedir. Constraint kullanımında farklı durumlarda söz konusudur. Örneğin bir aktivitenin içerisinde yer alan tüm alt aktiviteler için geçerli olacak kısıtların konulmasıda mümkün olabilir. Bu son derece doğaldır nitekim Constraint sınıfı örneklenirken, ilişkilendirildiği aktivite içeriğine erişebilmektedir. Burada DelegateInArgument<Activity> temsilci tipinin büyük rolü vardır. Öyleki Expression tanımlamasının yapıldığı ve doğrulama mantığının gerçekleştirildiği yerde, güncel aktivite referansına ulaşmak için element.Get(e) söz dizimi kullanılmaktadır. Tabiki bu yazımızda Declarative Constraint kullanımını çok basit seviyede ele almaya çalıştık. En güncel ve detaylı bilgiyi elbetteki MSDN üzerinde bulabilirsiniz. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

DeclerativeConstraints.rar (50,29 kb)

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

Workflow Foundation 4.0 - Kodlama Zamanında Doğrulama(Validation)

Cuma, 15 Ocak 2010 10:08 by bsenyurt

Merhaba Arkadaşlar,

Bazen nerede duracağımızı bilmemiz gerekir ve bazende, mümkün olduğunca erken durup bazı şeyleri değiştirerek ilerlememiz...Bu teori yazılım geliştirmeninde pek çok noktasında karşımıza çıkmaktadır. Durmamız gereken noktalardan birisi, uygulamaların ürettiği ve önceden fark edebileceğimiz hatalardır(Genellikle Exception' ları düşünebiliriz). Ancak bazı olası hataların uygulamaların çalışması sırasında değil, çalıştırılmaya başlamadan önce bilinmesinde hem zaman hemde maliyet kazancı açısından yarar vardır. Şimdi elimizdeki materyalleri bir düşünelim. Ürün geliştirmek için kullandığımız Visual Studio gibi gelişmiş bir araç, .Net Framework platformu vb...O halde bazı hataların çalışma zamanı yerine daha geliştirme aşamasındayken IDE üzerinde fark edilmesinin önemli olduğunu söyleyebiliriz. Peki geliştirme safhasında, örneğin bir Workflow aktivitesini kullanırken...Hımmmm...Sanırım nereye varmak istediğimi anladınız.Wink Bu yazımızda, özel aktivite bileşenlerinin kodlama zamanındayken olası hataları nasıl bildirebileceğini ve böylece doğrulamanın(Validation) nasıl sağlanabileceğini incelemeye çalışacağız.

Bundan önceki yazılarımızda özel aktivite bileşenlerimizi nasıl geliştirebileceğimizi incelemeye çalışmıştık. Bu amaçla yazdığımız örneklermizde CodeActivity ve AsyncCodeActivity türevli bileşenler geliştirmiştik. Özel aktivite bileşenleri geliştirilmesi sırasında dikkat edilmesi gereken önemli konulardan biriside kodlama zamanında gerçekleştirilmesini istediğimiz doğrulama(Validation) işlemleridir. Doğrulama için Workflow Foundation 4.0 tarafında kullanılabilen birden fazla teknik bulunmaktadır. Nitelik(Attribute) bazlı kullanım dışında Imperative ve Declerative olaraktan da doğrulama işlemlerini gerçekleştirebiliriz. Aslında konuyu anlamanın en güzel yolu öncelikli olarak sorunu ortaya koymaktan geçmektedir. Bu sebepten aşağıdaki gibi bir CodeActivity bileşeni tasarladığımızı düşünelim.

using System.Activities;
using System.IO;
using System;
using System.Activities.Expressions;

namespace CustomActivities
{
    public sealed class FileCopy
        : CodeActivity
    {
        public InArgument<string> Source { get; set; }
        public InArgument<string> Destination { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            File.Copy(Source.Get(context), Destination.Get(context));
        }
    }
}

FileCopy isimli aktivite bileşenimizin görevi Source özelliğinde belirtilen dosyayı, Destination özelliğinde belirtilen yere kopyalamaktır. Bu işlem için CodeActivity tarafından gelip ezilen(override) Execute metodu içerisinde, File sınıfının static Copy metodundan yararlanılmaktadır. Ne varki bu aktivite bileşeninin özellikle çalışma zamanında üreteceği bazı sorunlar vardır. Aslında bunları şimdiden tahmin etmemiz son derece kolaydır.

Herşeyden önce, InArgument<string> tipinden olan Source ve Destination özelliklerinin boş geçilmemesi yani veri ile doldurulması şarttır. Nitekim null verilerin Copy metodu içerisinde kullanılması söz konusu olamaz. Diğer yandan Source özelliğinde belirtilen dosyanın, sistemde gerçekten var olması gerekmektedir. Dolayısıyla Source özelliğinde bir değer olsa bile bunun geçerli bir dosya olup olmadığına bakılmalıdır. Üçüncü olaraktan Destination özelliğinde belirtilen dosya adının geçerli olması ve belkide Source ile belirtilen dosya uzantısında olması gerekmektedir. Hatta hedef dosya zaten var ise üzerine yazılması durumu söz konusudur. Bu vakaların herhangibirinin çalışma zamanında gerçeklenmesi sonrası istisnalar(Exceptions) ile karşılaşılması kaçınılmazdır. Söz gelimi aktivitemizi bu haliyle örnek bir Workflow içerisinde icra ettirdiğimizde çalışma zamanında aşağıdaki istisna mesajını alırız.

Source özelliğinde null değer olduğundan Copy operasyonunun icrası sırasında ArgumentNullException alınmıştır. Oysaki bu hatanın çalışma zamanında değil kodlama zamanında, yani Visual Studio ortamında daha tasarımı gerçekleştirirken farketmemiz çok önemlidir. Bu bize zaman ve maliyet kazancı olarak geri dönecektir. İşte doğrulamanın sağlanması gereken yerlerden birisi burasıdır. Peki bunu nasıl gerçekleştirebiliriz? Öncelikli olarak Source ve Destination isimli InArgument<string> tipinden olan özelliklerin boş bırakılmamasını sağlamalıyız. Bunun için ilgili özellikleri RequiredArgument niteliği ile aşağıdaki kod parçasında görüldüğü gibi işaretlememiz yeterli olacaktır.

[RequiredArgument]
public InArgument<string> Source { get; set; }
[RequiredArgument]
public InArgument<string> Destination { get; set; }

Bu durumda tasarım zamanında aşağıdaki görüntü ile karşılaşırız.

Dikkat edileceği üzere Source ve Destination değelerinin doldurulması zorunlu hale getirilmiştir. Üstelik bu durum derleme işleminden sonra açık bir şekilde adeta geliştiricinin gözüne sokulmaktadır. Laughing RequiredArgument niteliği ile ilişkili olarak dikkat edilmesi gereken noktalardan biriside Argument tiplerine uygulandığında işe yarıyor olmasıdır. Yani Soruce ve Destination özelliklerinin string tipinden olmaları gibi bir durumda bu niteliğin bir etkisi olmayacaktır.

İlk vakayı çözümledik. Artık dosya adlarını girdiğimizi düşünebiliriz. Sıradaki sorun Source özelliğine girilen dosyanın sistemde olmaması halinde ortaya çıkacaktır. Buna göre yine kopyalama işlemi sırasında bir çalışma zamanı hatası alınacaktır. Durumu irdeleyebilmek için Source ve Destination özelliklerini aşağıdaki şekilde görüldüğü gibi ayarladığımızı düşünelim.

Bu durumda çalışma zamanında aşağıdaki hata mesajını alırız.

Tabi burada c:\ klasörü içerisinde Source.txt isimli bir dosyanın gerçektende var olmadığını düşünüyoruz. Buradaki istisnaya göre kaynak dosyanın önceden kontrol edilmesi ve derleme işleminden sonra geliştiriciye bir uyarı veya hata mesajı ile durumun bildirilmesi istenebilir. Ancak burada basit bir nitelik yardımıyla aşabileceğimizin ötesinde bir durum vardır. Nitekim doğrulama için özel bir iş mantığı(Bussines Logic) bulunmaktadır. İşte bu tip doğrulamaları gerçekleştirebilmek için CodeActivity ve NativeActivity türevlerinde CacheMetadata isimli metodun ezilmesi ve Visual Studio ortamına doğrulama ile ilişkili bilgilendirmenin yapılması gerekmektedir. Sözün özü FileCopy aktivite bileşenimizi aşağıdaki hale getirmemiz yeterli olacaktır Wink

using System.Activities;
using System.IO;
using System;
using System.Activities.Expressions;

namespace CustomActivities
{
    public sealed class FileCopy
        : CodeActivity
    {
        [RequiredArgument]
        public InArgument<string> Source { get; set; }
        [RequiredArgument]
        public InArgument<string> Destination { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            File.Copy(Source.Get(context), Destination.Get(context));
        }

        protected override void CacheMetadata(CodeActivityMetadata metadata)
        {
            base.CacheMetadata(metadata);
            string source = ((Literal<string>)Source.Expression).Value;

            if (!File.Exists(source))
                metadata.AddValidationError(new System.Activities.Validation.ValidationError("Kaynak dosya bulunamadı.", true));
        }
    }
}

Burada Source özelliğinin değerine bakılmakta ve belirtilen dosyanın sistemde gerçekten var olup olmadığı tespit edilmektedir. Eğer söz konusu dosya sistemde yok ise "Kaynak dosya bulunamadı." içeriğine sahip bir uyarı mesajı(Warning) üretilir. Uyarı mesajı diyoruz çünkü sonda yer alan true değeri bunu sağlamaktadır. Yine derleme işleminden sonra oluşacak durum aşağıdaki gibidir.

Tabi eğer true yerine false değerini kullanırsak aşağıda görülen nur topu gibi error' un sahibi oluruz. Smile

Bu adımdan sonra geçerli bir kaynak dosyayı Source özelliğine atayarak devam edersek Destination özelliğine atanan değer ile ilişkili kontrolleri yapmamız gerektiği sonucuna varabiliriz. Buna göre hedef dosyanın kaynak dosya ile uzantı bakımından uyumlu olması sağlanabilir. Tabiki arka arkaya yapılan çalıştırmalar sonrasında hedef dosya zaten var ise overwrite ile ilişkili hataların oluşması da söz konusudur. Bu iki doğrulama işlemini siz değerli okurlarıma bir antrenman olması için bırakıyorum Wink Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

CustomActivityValidation.rar (50,30 kb)

Workflow Foundation 4.0 - Custom Async Activity Geliştirmek [Beta 2]

Pazartesi, 11 Ocak 2010 00:45 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız üzere bir önceki blog yazımızda Workflow Foundation 4.0 üzerinde özel aktiviteleri nasıl geliştirebileceğimizi incelemeye başlamış ve bu anlamda ilk olarak CodeActivity<T> türevli bir bileşen üretmiştik. Workflow Foundation 4.0 ile gelen önemli yeniliklerden biriside asenkron aktivite bileşenlerini içeriyor olmasıdır. Özellikle .Net Framework 4.0 tarafında üzerinde ağırlıklı olarak durulmaya başlanan paralel programlamanın da bir sonucu olan bu durum karşısında, geliştiricilerin asenkron olarak çalışabilen aktivite bileşenleri yazması pek tabidir.

Asenkron aktivitelerde iki temel operasyon bulunmaktadır. Bu operasyonlardan birisi asenkron olarak çalışacak işlerin başlatıcısı iken, diğerde tamamlandığında devreye girecek olanıdır. Bunlara ilaveten opsiyonel olarak asenkron işlemin iptal edilebilmesi için gerekli operasyonda eklenebilir.

Bu operasyonları içeren asenkron aktiviteleri geliştirmek için AsyncCodeActivity<T> tipinden türetme(Inherit) yapmak yeterlidir.

Dilerseniz hiç vakit kaybetmeden basit bir örnek üzerinden devam edelim. Bu amaçla bir önceki projemizde yer alan aktivite kütüphanemize BulkMailActivity isimli yeni bir Code Activity öğesi ekleyelim. Bu bileşen toplu olarak mail gönderme işlemlerini üstlenecektir. Gerçektende sürecin içerisinde bir noktada yer alabilen toplu mail gönderme adımının, sürecin kalan kısmını duraksatmaması istenebilir. Böyle bir vakada asenkron olarak mail gönderme işlemini üstelenecek bir aktivite çok yararlı olacaktır. BulkMailActivity isimli bileşenimizin sınıf diagramı görüntüsü ve kod içeriği ise aşağıdaki gibidir.

using System;
using System.Activities;
using System.Collections.Generic;
using System.Net.Mail;

namespace ActivityLibrary2
{
    public sealed class BulkMailActivity
        : AsyncCodeActivity<bool>
    {
        public InArgument<string[]> MailList{ get; set; }
        public InArgument<string> MailBody { get; set; }
        public InArgument<string> MailSubject { get; set; }

        private SmtpClient smtp;

        public BulkMailActivity()
        {
            smtp = new SmtpClient("localhost");
        }

        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            MailMessage message = new MailMessage
            {                
                  Body=MailBody.Get(context),
                   Subject=MailSubject.Get(context),
                   From=new MailAddress("admin@wf4.com")
            };
            message.To.Add(String.Join(",", MailList.Get(context)));

            Func<MailMessage, bool> SendDelegate = new Func<MailMessage, bool>(Send);
            context.UserState = SendDelegate;
            return SendDelegate.BeginInvoke(message, callback, state);
        }

        protected override bool EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            Func<MailMessage, bool> SendDelegate = (Func<MailMessage, bool>)context.UserState;
            return SendDelegate.EndInvoke(result);
        }

        bool Send(MailMessage message)
        {
            smtp.Send(message);
            return true;
        }
    }
}

Dikkat edileceği üzere AsyncCodeActivity<T> türetmesinden dolayı BeginExecute ve EndExecute isimli iki metodun ezilmesi(override) gerekmektedir. BeginExecute metodu içerisinde ise Func<T,TResult> tipinden bir temsilci(delegate) kullanılarak asenkron olarak yürütülecek metodun işaret edilmesi sağlanmaktadır. Çok doğal olarak asenkron kodlamada ana fikir, temsilcilerin(delegate) üzerinden çağırılan BeginInvoke ve EndInvoke metodlarıdır. Func temsilcisi sayesinde parametre ve geri dönüş tipi belli olan metodun işaret edilerek kullanılabilmesi sağlanmaktadır. Func temsilcisinin bu örnekte kullanılan versiyonuna göre ilk parametre in tipindendir ve Send metoduna gönderilebilecek olan parametreyi belirtmektedir. İkinci generic parametre ise out tipinden olup, Send metodundan döndürülecek olan tipi belirtmektedir. Dikkat edilmesi gereken noktalardan bir diğeride, BeginExecute ve EndExecute metodları arasında veri paylaşımının nasıl yapıldığıdır.

Yukarıdaki şekil sanıyorum ki bu konuda bir fikir vermektedir. Her iki metodda AsyncCodeActivityContext tipinden bir parametre almaktadır. BeginExecute metodunda bu parametrenin UserState özelliğine SendDelegate isimli Func<MailMessage,bool> temsilci örneği atanmıştır. Buna göre EndExecute metodu içerisinde SendDelegate referansının yakalanması ve çok doğal olarak EndInvoke metodunun çağırılabilmesi mümkündür. Aktivitemiz dışarıdan mail listesini, mail gövdesini ve konu kısımlarını almaktadır ki çok daha fazla parametre alabilir.(Size önerim MailMessage tipinin alabileceği tüm özellikleri içerecek bir mail gönderme aktivite bileşenini geliştirmeye çalışmanızdır) Gönderme işlemi sırasında makine üzerindeki varsayılan SMTP tipi sunucusu kullanılır. Bu nedenle örneğin kendi bilgisayarınızda çalışması sırasında mail gönderme işleminin gerçekleşmemesi mümkündür. Sealed 

Gelelim test kısmına. Bu amaçla TestScene.xaml içeriğini aşağıdaki gibi değiştirelim.

Görüldüğü üzere bileşenimizin ilgili özelliklerine bazı test verileri aktarılmıştır. MailList özelliği aslında bir String dizisi olduğundan değer ataması yapılırken süslü parantezli bir yazım stili kullanılmalıdır. Bunun dışında mail gönderme bileşeni sonuç olarak bool bir değer üretmektedir. Bu değer SendResult isimli aktivite bazındaki argümana atanmaktadır.

Xaml kısmı(Sadece bir kısmı verilmiştir);

 <x:Members>
    <x:Property Name="SendResult" Type="InArgument(x:Boolean)" />
  </x:Members>
  <mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
  <Sequence sad:XamlDebuggerXmlReader.FileName="C:\Documents and Settings\bsenyurt\my documents\visual studio 10\Projects\ActivityLibrary2\ActivityLibrary2\TestScene.xaml" sap:VirtualizedContainerService.HintSize="222,200">
    <Sequence.Variables>
      <Variable x:TypeArguments="sd1:DataTable" Name="QueryResult" />
    </Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <local:BulkMailActivity sap:VirtualizedContainerService.HintSize="200,22" MailBody="Deneme mailidir" MailList="[{&quot;birmailadresi@bimail.com&quot;, &quot;ikincimailadresi@bimail.com&quot;}]" MailSubject="Konu Yok" Result="[SendResult]" />
  </Sequence>

Tabiki bu örnekte sadece async tipinden bir code activity bileşeninin nasıl geliştirilebileceği ele alınmıştır. Aslında ilave edilmesi gereken ek özelliklerde yok değildir. Söz gelimi mail gönderme işlemi sırasında oluşabilecek istisnalar(Exception) sonrasında nasıl davranılacaktır. Bunun için bir Exception Handling mekanizmasının kullanılması, bir başka deyişle try...catch bloklarına başvurulması gerekebilir. Diğer yandan mailleri virgül ile ayırıp toplu şekilde göndermek yerine tek tek gönderilmesi yolu da tercih edilebilir. Nitekim şu durumda, herhangibir maile yapılan gönderi sırasında oluşacak istisna sonrası kalan gönderme işlemleride icra edilmeyecektir. Sözün özü gerçek hayat senaryolarında bu tip bir bileşenin çok daha titiz yazılması şarttır. Bizim bu yazı için odaklanmamız gereken noktalar ise şunlardır;

  • Asenkron aktivite bileşenleri geliştirmek için AsyncCodeActivity<T> tipinden türetme yapılır.
  • Türetme sonrası BeginExecute ve EndExecute metodları ezilir.
  • İstenirse iptal işlemlerinin ele alınabilmesi amacıyla Cancel metoduda ezilebilir.
  • Asenkron olarak çalışacak işin doğasında BeginInvoke veya EndInvoke kabiliyetleri yoksa Func gibi temsilcilerden yararlanılabilir.
  • BeginExecute ve EndExecute metodları arasında veri taşınması gerektiğinde, ortak parametre olan AsyncCodeActivityContext tipinden yararlanılır. Örneğimizdeki gibi illede temsilcinin taşınmasına gerek yoktur. Söz konusu parametre ortak olarak kullanılacak bir referansın taşınması istendiğinde değerlendirilmelidir.
  • Çalışma zamanı, Workflow içerisinde bir asenkron bileşen ile karşılaştığında bunu yürütmeye başlar ve hemen sonraki adımdan işleyişine devam eder.

Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ActivityLibrary2Async.rar (54,25 kb)

Workflow Foundation 4.0 - Custom Activity Geliştirmek [Beta 2]

Cuma, 1 Ocak 2010 16:00 by bsenyurt

Merhaba Arkadaşlar,

Programlamaya profesyonel olarak adım attığım yıllarda henüz .Net mimarisi geliştirilmeden önce Delphi programlama dili ile ürünler yazmaya çalışırdım. Aslında .Net çıktığından beri uğraşmadığım Delphi programlama dilini düşündüğümde aklıma ilk gelen hızlı geliştirme(Rapid Development) için sunduğu zengin Component sekmeleridir. Sayısız bileşen sayesinde müşteri ihtiyaçlarına çok hızlı cevap verebilecek ürünler geliştirebildiğimi de gayet net hatırlayabiliyorum. Tabi .Net dünyasına geçiş yapıp Visual Studio.Net(2005,2008,2010) ortamı ile karşılaştırıldığında çok fazla bileşen olduğu göze çarpmaktaydı. Ancak .Net tarafında da kodlama yeteneklerinin daha ön plana çıktığı bir gerçekti.

Ancak ister Delphi olsun ister .Net ister Java platformu, görsel program geliştirme ortamlarında var olan bileşenlerin yeterli gelmediği durumlar ile karşılaşılabilir. Bu durumda, geliştiriciler kendi bileşenlerini yazmaya çalışırlar(Yada ihtiyaçlarını karşılayan bileşenleri satın alma yoluna giderlerWink ). Bunun pek çok nedeni olabilir. Bu nedenler basit Web tabanlı bir TextBox kontrolünün güvenlik açığı, çok sık kullanılan IPV4 girişleri için bir kontrolün bulunmayışı veya birden fazla kontrolün birleşiminin pek çok yerde kullanılması gerekliliği vb olabilir...

Özel bileşenlerin geliştirilmesi genellikle bellidir. Bunlardan birisi bileşenin sıfırdan yazılmasıdır. Bu zaman zaman zahmetli bir yoldur. Söz gelimi Windows veya Web tarafında ekrana basılacak olan çıktının üretilmesi için gerekli kodlamaların yapılması gerekir. Bu Windows tarafında Paint olayının ezilerek GDI/DirectX gibi tekniklerin konuşturulması veya Web tarafında HTML ve Javascript destekli kodlamaların yapılmasını gerektirebilir. İkinci bir yol var olan bileşenlerin tasarım ortamında bir arada değerlendirilerek composite üretimlerin gerçekleştirilmesidir. Yine Windows ve Web tarafındaki User Control' leri bu anlamda düşünebiliriz. Diğer bir teknik ise, var olan bir bileşenden türetme yoluna gidilerek geliştirmenin yapılmasıdır ki bu çok sıkça tercih edilmektedir. Peki Workflow Foundation alt yapısında kendi bileşenlerimizi nasıl yazabiliriz? Aslında Workflow Foundation tarafında kullanılan Activity' lerin, Windows veya Web tarafındaki görsel olan/olmayan kontrollerden pekte farkı yoktur. Aktivite' lerde Workflow Foundation tarafında geliştirici tarafından değerlendirilen özel bileşenlerdir.

İşte bu yazımızda Workflow Foundation 4.0 alt yapısında, kendi aktivite bileşenlerimizi nasıl geliştirebileceğimizi incelemeye çalışıyor olacağız. An itibariyle Workflow Foundation 4.0 Beta 2 sürümü üzerinden ilerleyeceğiz. Aslında Custom Activity geliştirmek için pek çok neden sıralanabilir. Genel olarak var olan built-in aktivite bileşenlerinin yeterli gelmediği veya özel business logic' lerin içeren aktivitelerin pek çok akışta kullanılması gerektiği durumlarda özel aktivite bileşenlerinden yararlanılabilir. Özel aktiviteleri geliştirmeye başlamadan önce .Net Framework 4.0 Beta 2 bünyesindeki aktivite hiyerarşisini iyi bir şekilde incelemek gerekmektedir.

Genel Aktivite hiyerarşisi;

Burada önemli olan nokta, her aktivitenin bir Activity tipi olması gerekmediğidir. Evet nesne yönelimli teoriye göre böyledir ancak mantıksal çerçevede böyle değildir. Özellikle generic ve normal tip yönelimlerine bakıldığında amaca göre uygun olan aktivite bileşeninden türetilme yoluna gidilmesinin tercih edillmesi gerektiği ortadadır. Bu nedenle doğrudan Activity bileşeni yerine CodeActivity, AsyncCodeActivity, NativeActivity veya bunların generic olan versiyonlarından türetme işlemleri yapılmaktadır. Aslında doğrudan Activity/Activity<T> tipinden türetme yolu da tercih edilebilir. Aslında biraz kafamızın karıştığı ortadadır. Undecided Hangi türetmeyi seçeğimize karar vermek için şu noktalara dikkat etmemiz yeterli olacaktır;

  • Diğer aktivitelerin bir arada kullanılmasından ve doğrudan XAML tanımlamalarından oluşturalacak özel aktivite bileşenlerinin(bunları Asp.Net veya Windows uygulamalarındaki User Control' lere benzetebiliriz) Activity/Activity<T> tipinden türetilmesi,
  • Özel aktivitenin çalışma zamanında sonlandırılması sırasında bazı kodlamalara ihtiyaç duyuluyorsa CodeActivity,
  • Aktivite bileşeninin içerdiği işlemleri asenkron olarak yürütebilmesi isteniyorsa AsyncCodeActivity,
  • Eğer özel aktivite bileşeni çalışma zamanı verilerine erişecekse(örneğin diğer aktivitelere ulaşılıp kataloglanması, takvimlendirilmesi-scheduling vb...) NativeActivity,

türevli olması tercih edilmektedir.

Görüldüğü üzere aslında özel aktivite bileşeni geliştirmek için birden fazla yol vardır. Dilerseniz konuyu basit bir örnek ile süsleyerek ilerlemeye çalışalım. Bizim için en basit olanı seçeceğiz. Bu amaçla CodeActivity<T> türevli bir özel bileşen geliştirmeye çalışacağız. Söz konusu bileşeni Workflow Activity Library tipinden olan proje içerisine yeni bir Code Activity öğesi ekleyerek örnekleyebiliriz. AdapterActivity ismiyle geliştireceğimiz bileşenimizin temel amacı Select sorgusu ile bir veritabanı tablosunun içeriğini DataTable tipinden bir referans olarak geriye döndürmektir. AdapterActivity isimli örnek bileşenimizin kod içeriği aşağıdaki gibidir.

using System;
using System.Activities;
using System.Data;
using System.Data.SqlClient;

namespace ActivityLibrary2
{
    public sealed class AdapterActivity
        : CodeActivity<DataTable>
    {
        // Aktivite içerisinde kullanılan bazı özellikler
        public InArgument<string> Server { get; set; }
        public InArgument<string> Database { get; set; }
        public InArgument<string> SelectQuery { get; set; }

        // Execute metodu ezilir. AdapterActivity tipi, CodeActivity<DataTable> sınıfından türediğinden Execute metodunun DataTable türünden bir değer döndürmesi sağlanmalıdır
        // Execute metodunun dönüş değeri, Designer tarafında Result isimli özellik ile yakalanabilir
        protected override DataTable Execute(CodeActivityContext context)
        {
            // InArgument<T> tipinden olan değişken değerlerinin çalışma zamanında alınması için Get metodlarından yararlanılır. Metod parametre olarak o anki Aktivite içeriğinin referansını kullanır.
            string conStr = String.Format("data source={0};database={1};integrated security=SSPI", Server.Get(context), Database.Get(context));
            DataTable table=null;
            using (SqlConnection conn = new SqlConnection(conStr))
            {
                SqlDataAdapter adapter = new SqlDataAdapter(SelectQuery.Get(context), conn);
                table = new DataTable();
                adapter.Fill(table);
            }
            return table;           
        }
    }
}

CodeActivity<T> türevli olan AdapterActivity bileşeni, bir Select sorgusunu çalıştırıp sonucu DataTable içerisine aktarmak üzere tasarlanmıştır. Çok tabi olarak örneği basit tuttuğumuzu ifade etmek isterim. UserId,Password gibi ConnectionString için önemli olan alanlar yer almamaktadır. Bunun yerine varsayılan olarak Windows doğrulama modunda bağlanılabildiği düşünülmektedir. Üstelik yazılan SQL sorgusunun geçerli olup olmadığına dair bir kontrolde bulunmamaktadır ki olmasında yarar vardır. Nitekim hatalı veya SQL Injection saldırılarına açık tipte bir SQL sorgusunun yazılabiliyor olması arzu edilmeyecektir. Aslında tüm bu iki handikap sadece tablo adının alınıp Select sorgusunun kod içerisinde dinamik olarak oluşturulmasıyla da çözümlenebilir. Ancak odaklanmamız gereken daha önemli bir mevzu vardır. Override edilimiş olan Execute metodu. Bu metod söz konusu kod aktivitesi çalıştığında işletilecek olan kodları içermektedir. Dikkat edileceği üzere o an üzerinde çalıştığı aktivitenin çalışma zamanı içeriğini kullanabilmesi için CodeActivityContext tipinden bir parametre ile süslenmiştir. Bu parametre kod içerisinde Server, Database ve SelectQuery gibi özelliklerin çalışma zamanı değerlerini almak için çağırılan Get metodlarında parametre olaraktan da kullanılmaktadır. Dikkat çekici bir diğer nokta Execute metodunun dönüş tipidir. Varsayılan olarak void olan Execute metodu, AdapterActivity' nin CodeActivity<DataTable> türevli olması nedeniyle geriye DataTable döndürmektedir. Nitekim generic olarak yapılan türetmeye göre T tipi, aynı zamanda Execute metodunun geri dönüşü tipidir.

Bu basit bileşeni test etmek için aşağıdaki XAML ve tasarım zamanı içeriğine sahip TestScene isimli bir aktivite oluşturduğumuzu düşünebiliriz. (Uzun olan Namespace tanımlamaları göz ardı edilerek sadece Sequence içeriği verilmiştir)

<Sequence sad:XamlDebuggerXmlReader.FileName="C:\Documents and Settings\bsenyurt\my documents\visual studio 10\Projects\ActivityLibrary2\ActivityLibrary2\TestScene.xaml" sap:VirtualizedContainerService.HintSize="232,245">
    <Sequence.Variables>
      <Variable x:TypeArguments="sd1:DataTable" Name="QueryResult" />
    </Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <local:AdapterActivity Database="AdventureWorks" sap:VirtualizedContainerService.HintSize="210,22" Result="[QueryResult]" SelectQuery="Select * From Production.Product Order By Name desc" Server="." />
    <WriteLine sap:VirtualizedContainerService.HintSize="210,59" Text="[QueryResult.Rows.Count.ToString()]" />
  </Sequence>

Tasarım Zamanı(Design Time);

Tasarım zamanında dikkat edileceği üzere kod tarafında eklediğimiz Database, Server ve SelectQuery gibi özelliklere ulaşılabilmektedir. TestScene aktivitesi QueryResult isimli bir Variable değerine sahiptir. Bu değişkenin içeriği ise AdapterActivity isimli bileşenin Result özelliğidir. Bir başka deyişle CodeActivity<T> türevli özel aktivitenin Execute metoduna ait dönüş değerinin, Result isimli özelliğe aktarıldığını söyleyebiliriz. Gelelim TestScene.xaml aktivitesinin çalıştırılacağı kodlara;

using System.Activities;

namespace ActivityLibrary2
{
    public class Program
    {
        static void Main(string[] args)
        {
            var result=WorkflowInvoker.Invoke(new TestScene());
        }
    }
}

Görüldüğü gibi tek yaptığımız WorkflowInvoker tipinin Invoke metodu ile yeni bir TestScene aktivite örneğini çalıştırmaktır. Test için AdventureWorks veritabanında yer alan Products tablosunu kullandığımızda, standart olarak 504 olan satır sayısı değerinin elde edilmesi gerekmektedir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.

Custom Activity geliştirmek ile ilişkili diğer teknikleri de ilerleyen yazılarımızda ele almaya çalışıyor olacağız. Söz gelimi aktivitilerin çoğu için tasarım zamanı desteği bulunmaktadır. Kendi geliştireceğimiz aktivite bileşenleri için bu tip destekleri sunabilir miyiz? Sunabilirsek nasıl? 

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

ActivityLibrary2.rar (47,00 kb)

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

Workflow Foundation 4.0 - Persistence [Beta 2]

Salı, 1 Aralık 2009 08:29 by bsenyurt

Merhaba Arkadaşlar,

Bundan bir kaç yıl önce, eşimle birlike İtalya' daki Amca' sını ziyarete gitmiştik. Amcamız, Milano şehrinde uzun yıllar restoran işiyle uğraşmış oldukça yetenekli bir aşçı ve işletmeciydi. Bir gün İtalya' da bizi davet ettiği bir restoranda yemek yerken güzel bir tavsiyede bulunduğunu hatırlıyorum; "Yemekte bitiremediğiniz salatalar mı var? Yemekten sonra onları tavada ısıtın ve buzdolabına koyun. Ertesi gün yine soslayarak yiyebilirsiniz. Tabiki ağır soslu salatalar değilde hafif olanlardan bahsediyorum". Bir anlamda salatayı herhangibir anda o anki haliyle saklayıp(ki ben buna Persist etmek demek istiyorum) sonra t zamanında yeniden yemek. Her ne kadar bu senaryoda salatayı yıllarca saklamak zor olsada(ki bu durumdada bazı sonuçlarına katlanmak gerekebilir Sealed ) yazılım dünyasında uzun süreli bir iş akışının yıllarca saklanabilmesi mümkündür. İşte bu günkü konumuz; Workflow Foundation 4.0 ile Persistence işlemleri nasıl gerçekleştirilebilir...

Workflow Foundation modeli ile geliştirilen uzun süreli işlemlerde(Long Running Process) en önemli konulardan biriside, Workflow örneğinin herhangibir t anında kalıcı olarak saklanabilmesi(Persist) ve istenildiğinde saklandığı yerdeki içeriği ile birlikte tekrardan ayağa kaldırılabilmesidir. Persist edilecek verilerin nerede saklanacağına ilişkin olarak çalışma zamanının varsayılan tutumu belleği kullanmaktır. Ancak çok sayıda Workflow örneğinin Long Running Process olarak değerlendirildiği gerçek hayat vakalarında hem yönetim(Administration) hemde kalıcılığın daha kuvvetli olması adına SQL veritabanı ortamının değerlendirilmesi çok daha doğru bir davranıştır.

İlk versiyonlarından bu yana, Workflow tarafında persistence alanı olarak SQL veritabanının kullanılması bazı SQL Script' lerinin çalıştırılıp gerekli veritabanının oluşturulması ile başlamaktadır. Bu veritabanının Workflow çalışma zamanı tarafından kullanılacağının belirtilmesi sırasında bağlantı bilgisi verilmesi yeterlidir. Buda önemli bir noktadır ki veritabanını, Workflow uygulamalarını host edip çalıştıran sunucunun dışında bir yerde tutma şansına sahip olabiliriz. Yazımızın bundan sonraki kısımlarında, Workflow Foundation 4.0 Beta 2 üzerinden Persistence sistemini nasıl kullanabileceğimize çok basit bir örnek üzerinden bakmaya çalışacağız. İşe ilk olarak WFPersistenceStore(ki siz istediğiniz bir veritabanı adını verebilirsiniz) isimli bir veritabanını oluşturarak başlayabiliriz. Bu işlemin ardından varsayılan olarak C:\WINDOWS\Microsoft.NET\Framework\v4.0.21006\SQL\en\ adresinde duran SqlWorkflowInstanceStoreSchema, SqlWorkflowInstanceStoreLogic sql script' lerini sırasıyla oluşturulan veritabanı üzerinde çalıştırmamız gerekmektedir. Söz konusu işlem sonrasında Workflow örneklerinin tutulması ile ilişkili olaraktan gerekli veritabanı nesnelerinin üretildiği görülebilir(Tables, Stored Procedures, Views vb...).

Workflow tarafında Persistence işlemleri için birden fazla yol kullanılabilir. Bunların içerisinde belkide en basiti Persist isimli aktivite bileşeninin Workflow içerisinde bir noktada değerlendirilmesidir. Bu yol dışında istenirse WorkflowApplication nesne örneğinin PersistableIdle özelliğide değerlendirilebilir. Ancak unutulmaması gereken noktalardan birisi de Workflow örneklerinin saklanma durumlarının aslında uzun süreli süreçler için geçerli oluşudur. Bu nedenle ilgili özellik ve konfigurasyon işlemleri esas itibariyle WorkflowApplication tipi üzerinden yapılmaktadır.

Dilerseniz daha fazla vakit kaybetmeden örneğimizi geliştirmeye başlayalım. İlk olarak basit bir Workflow Console Application projesi oluşturarak yolumuza devam edebiliriz. Workflow uygulamamızda SQL tabanlı bir Persistence sistemi kullanacağımızdan gerekli assembly' larında projeye referans edilmesi gerekmektedir. Bu amaçla uygulamamıza System.Activities.DurableInstancing.dll' ini eklememiz gerekmektedir. Ayrıca System.Runtime.dll assembly' ının 4.0 versiyonun da eklenmesi gerekmektedir. Söz konusu referans eklemeleri sonrasında tasarım zamanındaki görüntüsü aşağıdaki gibi olan çok basit bir Workflow geliştirerek devam edebiliriz.

Workflow örneğine ait XAML içeriğinin sadece Sequence bölümünden oluşan parçası aşağıdaki gibidir.

 <Sequence sad:XamlDebuggerXmlReader.FileName="c:\documents and settings\bsenyurt\my documents\visual studio 10\Projects\WFPersistence\WFPersistence\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="486,614">
    <Sequence.Variables>
      <Variable x:TypeArguments="x:Int32" Name="CurrentNumber" />
    </Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <WriteLine sap:VirtualizedContainerService.HintSize="464,59" Text="[String.Format(&quot;Workflow başlangıcı {0}&quot;, DateTime.Now.ToLongTimeString())]" />
    <While sap:VirtualizedContainerService.HintSize="464,391" Condition="True">
      <Sequence sap:VirtualizedContainerService.HintSize="438,280">
        <sap:WorkflowViewStateService.ViewState>
          <scg3:Dictionary x:TypeArguments="x:String, x:Object">
            <x:Boolean x:Key="IsExpanded">True</x:Boolean>
          </scg3:Dictionary>
        </sap:WorkflowViewStateService.ViewState>
        <Assign sap:VirtualizedContainerService.HintSize="242,57">
          <Assign.To>
            <OutArgument x:TypeArguments="x:Int32">[CurrentNumber]</OutArgument>
          </Assign.To>
          <Assign.Value>
            <InArgument x:TypeArguments="x:Int32">[CurrentNumber + 1]</InArgument>
          </Assign.Value>
        </Assign>
        <WriteLine sap:VirtualizedContainerService.HintSize="242,59" Text="[String.Format(&quot;CurrentNumber : {0} &quot;, CurrentNumber.ToString())]" />
      </Sequence>
    </While>
  </Sequence>

Tasarım ekranından görebileceğiniz üzere Workflow örneğimiz bir WriteLine ile başlamakta ve sonrasında sonsuz bir döngüye girmektedir. Bu sonsuz döngü için While bileşeninden yararlanılmaktadır. Sonsuz döngü içerisinde yer alan Assign bileşeni sayesinde, CurrentNumber isimli Sequence seviyesindeki Variable' ın değeri sürekli olarak arttırılmaktadır. Buna göre yazacağımız program kodunun önemi vardır. Workflow örneğini Host eden Console uygulamasını öyle yazmalıyızki, uygulama kapandığında Workflow örneğinin o anki hali Persistence tablolarına yazılabilsin. Bu nedenle Program.cs içeriğini aşağıdaki gibi geliştirebiliriz.

using System;
using System.Activities;
using System.Activities.DurableInstancing;

namespace WFPersistence
{

    class Program
    {
        static void Main(string[] args)
        {
            string conStr = "data source=.;database=WFPersistenceStore;integrated security=SSPI";

            // Persistence işleminin yapılacağı veritabanını işaret edecek ve yönetecek nesne örneklenir
            SqlWorkflowInstanceStore instanceStore = new SqlWorkflowInstanceStore(conStr);

            // Workflow nesnesi örneklenir
            Workflow1 wf1 = new Workflow1();

            // WorkflowApplication nesnesi örnekleni ve wf1' i çalıştıracağı belirtilir
            WorkflowApplication wfApp = new WorkflowApplication(wf1);
            // WorkflowApplication nesne örneğinin persistence store olarak instanceStore isimli SqlWorkflowInstanceStore nesne örneğini kullanacağı ve dolayısıyla saklama işlemlerinin WFPersistenceStore veritabanında gerçekleştirileceği belirtilir
            wfApp.InstanceStore = instanceStore;

            // WorkflowApplication başlatılır
            wfApp.Run();

            Console.WriteLine("Uygulamadan çıkmak için bir tuşa basın");
            Console.ReadLine();

            // Unload metodunun çalıştırılması nedeniyle Workflow örneğinin o anda bulunduğu içerik itibariyle Persist edilmeside sağlanmaktadır.
            wfApp.Unload();

            Console.WriteLine("Uygulamadan çıkış zamanı {0}",DateTime.Now.ToLongTimeString());
        }
    }
}

Örneğimizi çalıştırmadan önce SQL Server Profiler aracınında açık olduğundan emin olalım. Böylece arka planda gerçekleşen insert işlemlerini görme şansımız olacaktır. Ben örneğimizi bu haliyle test ederken aşağıdaki sonuçlar ile karşılaştım. (Bilinçli olarak belirli süre geçtikten sonra uygulamayı kapattım)

Dikkat edileceği üzer While döngüsünde CurrentNumber değeri 4158 iken çıkılmıştır. Tabiki sizin testlerinizde bu değer çok daha farklı olabilir. Uygulama Unload metodu çağrısını gerçekleştirdikten sonra SQL Server Profiler aracından yakalan sorgu aşağıdaki gibidir(SQL İçeriği çok uzun olduğundan son kısmı kırpılmıştır)

exec sp_executesql N'begin transaction
declare @result int
exec @result = [System.Activities.DurableInstancing].[SaveInstance] @instanceId, @surrogateLockOwnerId, @handleInstanceVersion, @handleIsBoundToLock,
@primitiveDataProperties, @complexDataProperties, @writeOnlyPrimitiveDataProperties, @writeOnlyComplexDataProperties, @metadataProperties,
@metadataIsConsistent, @encodingOption, @pendingTimer, @suspensionStateChange, @suspensionReason, @keysToAssociate, @keysToComplete,
@keysToFree, @concatenatedKeyProperties, @unlockInstance, @isReadyToRun, @isCompleted,
@lastMachineRunOn, @executionStatus, @blockingBookmarks, @operationTimeout ;
if (@result = 0)
begin
commit transaction
end
else
rollback transaction
',N'@instanceId uniqueidentifier,@surrogateLockOwnerId bigint,@handleInstanceVersion bigint, @handleIsBoundToLock bit, @pendingTimer datetime,@unlockInstance bit,@suspensionStateChange tinyint,@suspensionReason nvarchar(450),@isCompleted bit,@isReadyToRun bit, @operationTimeout int,@keysToAssociate xml,@keysToComplete xml, @keysToFree xml,@concatenatedKeyProperties varbinary(8000),@primitiveDataProperties varbinary(8000), @complexDataProperties varbinary(1664), @writeOnlyPrimitiveDataProperties varbinary(470) ,@writeOnlyComplexDataProperties varbinary(860),@metadataProperties varbinary(8000), @metadataIsConsistent bit,@encodingOption tinyint, @lastMachineRunOn varchar(900),@executionStatus varchar(900),@blockingBookmarks varchar(900)',

@instanceId='074A5D57-C268-43E6-B516-DFE49894D7A7',

@surrogateLockOwnerId=26,@handleInstanceVersion=-1, @handleIsBoundToLock=0,@pendingTimer=NULL,@unlockInstance=1, @suspensionStateChange=0,@suspensionReason=NULL,@isCompleted=0,@isReadyToRun=1, @operationTimeout=29922, @keysToAssociate=NULL,@keysToComplete=NULL ,@keysToFree=NULL, @concatenatedKeyProperties=NULL,@primitiveDataProperties=NULL, @complexDataProperties=...

Görüldüğü üzere Transaction içerisine alınmış olan ve SaveInstance isimli Stored Procedure için yapılan bir çağrısı söz konusudur. Yapılan çağrıda şu an için dikkat edilmesi gereken nokta parametre olarak gelen instanceId değeridir. Bu işlemin ardından Instances isimli View içeriğine bakılırsa söz konusu InstanceId değerini içeren bir satırın üretildiği görülmektedir.

Peki ya şimdi ne olacak?

T zaman sonra(Gün, Ay, Yıl bile olabilir. Yeterki SQL tarafındaki bilgilere zarar gelmesin), bu Workflow örneğinin kaldığı yerden ayağa kaldırılarak devam etmesi istenebilir. İşte bu durumda Persist edilmiş olan örneğin, kaydedildiği haliyle tekrardan yüklenmesi gerekecektir. Geliştirdiğimiz örnek senaryoya göre, CurrentNumber değerinin kaldığı yerden başlayarak devam etmesi gerekmektedir. Persist edilmiş olan bir Workflow örneğinin tekrardan ayağa kaldırılması için GUID tipinden olan InstanceId değerinin Load metoduna parametre olarak geçirilmesi yeterlidir. İşte Persist edilmiş olan Workflow örneğini ayağa kaldırmak için kullanacağımız kodlarımız.

Tam Load metodunun olduğu yerde breakpoint koyarak ilerlemenizi öneririm. Persist edilen Workflow örneğinin canlandırılması esnasında SQL tarafında LoadInstance isimli bir Stored Procedure' ün çalıştırılması sağlanmaktadır. İşte çalıştırılan SQL sorgusu.

exec [System.Activities.DurableInstancing].[LoadInstance] @surrogateLockOwnerId=27,@operationType=3, @keyToLoadBy='00000000-0000-0000-0000-000000000000',

@instanceId='074A5D57-C268-43E6-B516-DFE49894D7A7',

@handleInstanceVersion=-1,@handleIsBoundToLock=0,@keysToAssociate=NULL, @encodingOption=1,@concatenatedKeyProperties=NULL, @operationTimeout=29812

Hatta bu noktada iken IsLocked alanın söz konusu Workflow örneği için 1 olarak set edildiği gözlemlenebilir. Bir başka deyişle Workflow örneği bu işlem sırasında diğer bir talebe kapatılmış durumdadır.

Peki ya uygulamanın durumu? Uygulama yeniden çalıştırıldığında CurrentNumber değerinin kaldığı yerden devam ettiği gözlemlenecektir. Bunu görme zevkini siz değerli okurlarıma bırakıyorum Wink Persistence işlemi bu örneğimizde WorkflowApplication nesnesi tarafından ele alınmıştır. Ancak Workflow Service isimli önemli bir gerçekde vardır. Bir Workflow Service' in uzun süreli olarak saklanması da, gerçek hayat senaryolarında yaşanan vakalardan birisidir. Bu durumu bir sonraki yazımızda ele almaya çalışacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kişisel Not : Kalıcı olarak saklama işlemini yazımızın başında belirttiğimiz üzere, Persist aktivitesi ilede kolayca gerçekleştirebiliriz. Bu konu ile ilişkili bir görsel dersi ilerleyen tarihlerde yayınlamaya çalışıyor olacağım.

WFPersistence.rar (37,63 kb)

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

WF 4.0 : WorkflowInvoker ile Single Thread, WorkflowApplication ile Multi-Thread [Beta 2]

Pazartesi, 2 Kasım 2009 11:35 by bsenyurt

Merhaba Arkadaşlar,

Hiç müzik dinlerken bir yandan da kod yazmayı denediniz mi? Üstelik çevre ile olan etkileşiminiz devam ederken Wink Söz gelimi hareketli bir parçayı tempo tutarak dinleyip ondan tamamen bağımsız bir şekilde geliştirmeye devam ederken yan masadaki arkadaşınızdan gelen "Dün akşamki maçı seyrettin mi?...Ronaldo ne gol attı öyle..." sorusuna da rakip takımın orta sahasını kattığınız bir yorumda bulunup diğer taraftanda kahve içtiğinizi düşünebilirsiniz. Tabiki insan beyninin büyülü dünyası ve eş zamanlı olarak çalışma yetenekleri zaman zaman geliştirdiğimiz uygulamalara da yansımaktadır. Böyle bir giriş yaptığımıza göre multi-thread bir takım işlemleri anlatacağımı düşünmüş olmalısınız. İşte bu gün konumuz Workflow Foundation 4.0 üzerindeki Single-Thread ve Multi-Thread çalıştırma modelleri.

WF 4.0 öncesinde bir Workflow örneğini çalıştırmak için WorkflowRuntime sınıfından yararlanılmaktadır. Aşağıdaki kod parçasında Visual Studio 2008 üzerinde geliştirilen basit bir WF örneğinin çalıştırılması için otomatik olarak üretilen kod görülmektedir.

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(WorkflowConsoleApplication1.Workflow1));
                instance.Start();

                waitHandle.WaitOne();
            }

Ancak Workflow Foundation 4.0 içerisinde bir Workflow örneğini çalıştırmak için iki farklı yol sunulmaktadır. İlk yol daha önceki yazı ve görsel derslerimizde de sıklıkla bahsettiğimiz WorkflowInvoker sınıfına ait static Invoke metodunun kullanılmasıdır. Bu tekniğin en önemli özelliği Workflow örneğinin çalıştığı uygulamaya ait Thread içerisinde senkron olaran yürütülmesini sağlamasıdır. Dilerseniz ne demek istediğimize basit bir örnek yardımıyla bakmaya çalışalım. Visual Studio 2010 Ultimate Beta 2 sürümü üzerinden oluşturduğumuz Workflow Console Application içerisinde aşağıdaki Workflow1 içeriği göz önüne alınmaktadır.

Akışın xaml içeriği ise aşağıdaki gibidir.

<Activity mc:Ignorable="sap" x:Class="WorkflowConsoleApplication5.Workflow1" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" xmlns:mv1="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns:s1="clr-namespace:System;assembly=mscorlib" xmlns:s2="clr-namespace:System;assembly=System" xmlns:s3="clr-namespace:System;assembly=System.Xml" xmlns:s4="clr-namespace:System;assembly=System.Core" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sd1="clr-namespace:System.Data;assembly=System.Data.DataSetExtensions" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Threading;assembly=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns:st1="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Sequence sad:XamlDebuggerXmlReader.FileName="c:\documents and settings\bsenyurt\my documents\visual studio 10\Projects\WorkflowConsoleApplication5\WorkflowConsoleApplication5\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="232,344">
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <WriteLine sap:VirtualizedContainerService.HintSize="210,59" Text="[String.Format(&quot;WF Başlangıç {0} Thread ID:{1}&quot;, TimeString, Thread.CurrentThread.ManagedThreadId.ToString())]" />
    <Delay Duration="00:00:05" sap:VirtualizedContainerService.HintSize="210,22" />
    <WriteLine sap:VirtualizedContainerService.HintSize="210,59" Text="[String.Format(&quot;WF Bitiş {0}&quot;, TimeString)]" />
  </Sequence>
</Activity>

Workflow1 içerisinde dikkat çekici noktalardan birisi Delay aktivitesi ile sanal bir geciktirmenin uygulanmış olmasıdır. Bunun yanında ilk WriteLine bileşeni içerisinde güncel ManagedThreadId değerinin yazdırılması sağlanmaktadır. Şimdi bu Workflow örneğini yürütmek için aşağıdaki kod parçasını geliştirdiğimizi düşünelim.

using System;
using System.Activities;
using System.Threading;

namespace WorkflowConsoleApplication5
{

    class Program
    {
        static void Main(string[] args)
        {
            #region Senkron çalışma(Aynı Thread içerisinde)

            WorkflowInvoker.Invoke(new Workflow1());
            Console.WriteLine("Invoke çağrısının hemen sonrası {0} Thread ID :{1}",DateTime.Now.ToLongTimeString(),Thread.CurrentThread.ManagedThreadId.ToString());

            #endregion
        }
    }
}

İlk olarak WorkflowInvoker sınıfının static Invoke metodu ile Workflow1 nesne örneğinin çalıştırılması sağlanmaktadır. Devam eden kod satırının devreye girmesi için Delay süresi kadar beklenmesi gerekecektir ki bu örnekte önemli olan noktada budur. Diğer taraftan hem Workflow1 için hemde Program sınıfı için aynı ManagedThreadId değerlerinin üretildiği gözlemlenecektir. İşte çalışma zamanı sonuçlarımız.

Görüldüğü gibi Invoke metodu çağrısı sonrasındaki kod satırına geçmek için Workflow1' in tamamlanması beklenmiştir. Bu senkron çalışmanın bir sonucu ve ispatıdır. Ayrıca bir Workflow örneğini çalıştırmanın en basit yolu olarak düşünülebilir. Ancak bazı zamanlarda özellikle Long Running Process' lerde söz konusu olduğunda, sunucu tarafındaki Workflow örneğinin farklı bir Thread içerisinde yürütülmesi ve buna bağlı olarakta Host uygulamanın paralel olarak çalışmaya devam etmesi istenebilir. Bir başka deyişle Workflow örneklerini host eden uygulamanın söz konusu akışları Multi-Thread olarak yürütmesi istenebilir. İşte bu durumda WorkflowApplication tipinden yararlanılmaktadır. Bu tip ve üyeleri sayesinde Workflow örneğinin farklı bir Thread içerisine açılması ve ana uygulama ile paralel olarak yürütülmesi sağlanabilir. Üstelik çeşitli olayları sayesinde Workflow örneğinin tamamlanması, ele alınmamış bir istisna(Unhandled Exception) nedeniyle sonlanması gibi durumlar kontrol altına alınabilir. Aslında bir önceki versiyonda yer alan WorkflowRuntime tipinin üstlendiği misyonun aynısıdır diyebiliriz. Şimdi bu çalışmayı farklı bir Workflow örneği üzerinden değerlendirmeye çalışalım. Bu sefer parametre kullanımı ve geri dönüş tiplerinide söz konusu sınıf olayları içerisinde değerlendirmeye çalışacağız. İşte Workflow2 tasarım görüntüsü.

Workflow1'deki aktivitemizde olan bileşenleri kullanmakla beraber bu kez X ve Y isimli iki In ve Sum isimli bir Out argümanımız olduğunu belirtelim. Main metodu içeriğimizi ise aşağıdaki gibi geliştirdiğimizi düşünelim.

using System;
using System.Activities;
using System.Threading;

namespace WorkflowConsoleApplication5
{

    class Program
    {
        static void Main(string[] args)
        {
            #region Asenkron çalışma

            AutoResetEvent aResetEvent = new AutoResetEvent(false);

            // Workflow2 nesnesi örneklenir ve In argümanlarına ilk değerleri atanır
            Workflow2 wf2 = new Workflow2
            {
                X = 10,
                Y = 12
            };

            // WorkflowApplcation nesnesi örneklenirken parametre olarak çalıştırılmak istenen Workflow nesne örneği verilir.
            WorkflowApplication wfApp = new WorkflowApplication(wf2);

            // Completed olay metodu, WorkflowApplicationCompletedEventArgs tipinden olay parametresi alır.
            // Workflow çalışmasını tamamladığında devreye girecek olay metodudur
            wfApp.Completed = (e) =>
                {
                    // e parametresinden yararlanarak Outputs özelliğine ve dolayısıyla çalıştırılmakta olan Workflow' un Out argümanlarına ilgili Dictionary koleksiyonu üzerinden erişilebilir.
                    Console.WriteLine("Toplam Sonucu {0}, Thread ID : {1}", e.Outputs["Sum"].ToString(), Thread.CurrentThread.ManagedThreadId.ToString());
                    aResetEvent.Set(); // Workflow' un tamamlandığı bilgisi Thread' e sinyal olarak gönderilir.
                };

            // Aborted olayı WorkflowApplicationAbortedEventArgs tipinden parametre almaktadır.
            // Workflow bir sebepten iptal olduğunda devreye giren olay metodudur.(Örneğin, Abort metodu ile yapılan çağrı veya bir Exception nedeniyle)           
            wfApp.Aborted = (e) =>
                {
                    // Akışın bir istisna(Exception) nedeniyle iptal olması halinde Exception tipinden olan Reason özelliği değerlendirilir.
                    if(e.Reason!=null)
                        Console.WriteLine("Workflow2 {0} sebebiyle iptal oldu", e.Reason.Message);                   
                    aResetEvent.Set(); // Workflow' un tamamlandığı bilgisi Thread' e sinyal olarak gönderilir.
                };

            wfApp.Run(); // İşlemler başlatılır

            Console.WriteLine("WorkflowApplication.Run çağrısının hemen sonrası {0} Thread ID :{1}", DateTime.Now.ToLongTimeString(), Thread.CurrentThread.ManagedThreadId.ToString());

            // Ana Thread içerisindeki işlemlerin paralel yürüdüğünü göstermek için kullanılan hile döngüsü
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Workflow çalışıyor...");
                Thread.Sleep(1000);
            }

            aResetEvent.WaitOne(); // Eğer Workflow halen daha tamamlanmamışsa bekle.

            #endregion
        }
    }
}

Örneği bu haliyle çalıştırdığımızda aşağıdaki ekran görüntüsüne benzer çalışma zamanı sonuçlarını alırız.

Görüldüğü üzere Workflow2 örneği çalıştırıldıktan sonra ana uygulama akmaya devam etmiştir. Üstelik ana uygulama akmaya devam ederken tamamlanan Workflow ile ilişkili Completed olay metodu da devreye girmiştir. Dikkat edilmesi gereken noktalardan biriside ana uygulamaya ait ManagedThreadId ile WorkflowApplication tarafından açılan ManagedThreadId değerlerinin farklı olmasıdır ki istediğimiz sonuçlardan bir diğeride budur. Nitekim Multi-Thread çalışmanın ispatıdır. Aslında WF4.0 öncesindeki WorkflowRuntime kullanımına baktığımızda neredeyse aynı işleyiş modelinin kullanıldığını görebiliriz. Önceki modelde de olay bazlı olarak Workflow örneklerinin tamamlanması ve sonlanması durumları ele alınabilmektedir. Ancak WorkflowApplication tipinin bu şekilde kalıp kalmayacağıda henüz belli değildir. Bunu ancak son sürümde net olarak öğrenebileceğimizi ifade edebilirim. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kişisel Not : Tabiki halen Beta 2 sürümünde olduğumuzu hatırlatmak isterim. Yani bu özelliklerde değişiklikler olabilir, bazıları kaldırılabilir veya tekrardan geriye dönüşler yapılabilir.

WorkflowConsoleApplication5.rar (43,99 kb)

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

Screencast - Workflow Foundation 4.0 : Flowchart

Cumartesi, 31 Ekim 2009 01:45 by bsenyurt

Merhaba Arkadaşlar,

Bundan yaklaşık 1 sene kadar önce Microsoft PDC 2008 sunumlarında gösterilen ve demoları yapılan Workflow Foundation 4.0 ile ilişkili yenilikler arasında dikkat çekici olanlarından bir tanesi de, Flowchart Workflow modeliydi. Aslında bu yeni özellik, çoğu yazılımcının yaşam döngüsü içerisinde sıklıkla kullandığı akış diyagramlarının, Workflow Foundation modeli içerisinde ele alınmasından başka bir şey değildi ki Workflow Foundation 4.0 öncesinde aradığımız ama bulamadığımız bir yenilikti. Visual Studio 2010 Beta 2 sürümünün yayınlandığı şu günlerde, etkili WPF tasarım ortamınında katkısıyla, akış diagramı modeline uygun Workflow aktivitelerinin tasarlanması hem çok kolay hemde çok zevkli hale geldi. Bakalım görsel dersimizde bizleri neler bekliyor... Wink

   

Yeni görsel derslerde görüşmek dileğiyle hepinize mutlu günler dilerim.

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