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

Workflow Services - Custom Authorization

Pazartesi, 22 Mart 2010 13:30 by bsenyurt

Merhaba Arkadaşlar,

Aşçılık zevkli ama bir o kadarda zor bir zanaattır. Hatta bazen o kadar zor bir zanaat olur ki, aşcının aldığı maaşı pek çok yazılımcı iki yılda kazanamaz. Tabi bu tip aşçılar işin ehli olan kişilerdir. Heleki tek bir mutfak değilde dünya mutfağının seçkin olanlarına ait becerileri bulunanlara paha biçilemez. Türk Mutfağından Japon mutfağına, Meksika yemeklerinden İtalyan spesiyallerine, Fransız tatlılarından Okyanus deniz ürünlerine ve daha nicelerine...Tabi bir aşçı için olmassa olmazlardan biriside yemeği için gerekli olan malzemelerin kalitesidir. Kaliteli zeytinyağı, hamur ve baharat ile yapılan spagettinin, kalitesiz olanlar ile yapılanı arasında dağlar kadar fark olabilir. Spagetti demişken bu günkü yazımızda neler yapacağımıza da bir bakalım dilerseniz. Aşçı olarak bu gün elimizde zor bir tarif var. Malzemelerimiz belli ama pişecek olan yemeğin yapımı biraz zahmetli. Haydi gelin hiç vakit kaybetmeden önlüğümüzü takıp klavyenin başına geçelim.Wink

Bu yazımızda .Net Framework 4.0 tarafında geliştireceğimiz Workflow Service' lerde yetkilendirme işlemini nasıl sağlayabileceğimizi görmeye çalışacağız. Ne yazık ki Authorization işlemini kolaylaştırmak adına hazır bir yapı mevcut değil. Bu nedenle biraz kodlama yapmamız ve çalışma zamanının işleyişine bu şekilde müdahale etmemiz gerekiyor. Hatta yapacağımız özelleştirme öylesine etkili olacak ki, aradan Doğrulamayı(Authentication) bile çıkaracağız farkına varmadan. Surprised Ama önce yemek için gerekli malzemelerimizin neler olduğuna bir bakalım.

  • Bir adet Workflow Service.
  • Bir adet konfigurasyon dosyası(Web.config).
  • System.IdentityModel.dll referansı.
  • ServiceAuthorizationManager türevli bir sınıf.
  • Windows üzerinde tanımlanmış roller ve bu roller içerisinde yer alan kullanıcılar.

Tabiki malzemeleri tedarik etmek yeterli değil. Birde tarifi bilmek lazım Wink Öncelikli olarak yetkilendirme işlemini üstelenen bir sınıf yazmamız gerekiyor. Çok doğal olarak bu sınıfın Workflow Service çalışma zamanı tarafından değerlendirilebilmesi için konfigurasyon dosyası üzerinde de gerekli düzenlemeleri yapmalıyız. Sonrasında ise işi istemci tarafından gelen taleplere bırakıyor olacağız. İşe ilk olarak aşağıdaki gibi bir Workflow Service projemiz olduğunu varsayarak başlayalım.

Calculus.xamlx içeriğimiz ise aşağıdaki gibidir;(Sadece Sequence elementi içeriği verilmiştir)

<p:Sequence DisplayName="Sequential Service" sad:XamlDebuggerXmlReader.FileName="D:\Projects\Workflow Foundation 4.0\UsingCustomAuthorization\UsingCustomAuthorization\CalculusService.xamlx" sap:VirtualizedContainerService.HintSize="277,336">
    <p:Sequence.Variables>
      <p:Variable x:TypeArguments="CorrelationHandle" Name="handle" />
      <p:Variable x:TypeArguments="x:Int32" Name="X" />
      <p:Variable x:TypeArguments="x:Int32" Name="Y" />
      <p:Variable x:TypeArguments="x:Int32" Name="Sum" />
    </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>
    <Receive x:Name="__ReferenceID0" CanCreateInstance="True" DisplayName="ReceiveRequest" sap:VirtualizedContainerService.HintSize="255,86" OperationName="SumOp" ServiceContractName="p1:IService">
      <Receive.CorrelatesOn>
        <MessageQuerySet />
      </Receive.CorrelatesOn>
      <Receive.CorrelationInitializers>
        <RequestReplyCorrelationInitializer CorrelationHandle="[handle]" />
      </Receive.CorrelationInitializers>
      <ReceiveParametersContent>
        <p:OutArgument x:TypeArguments="x:Int32" x:Key="XValue">[X]</p:OutArgument>
        <p:OutArgument x:TypeArguments="x:Int32" x:Key="YValue">[Y]</p:OutArgument>
      </ReceiveParametersContent>
    </Receive>
    <SendReply Request="{x:Reference __ReferenceID0}" DisplayName="SendResponse" sap:VirtualizedContainerService.HintSize="255,86">
      <SendParametersContent>
        <p:InArgument x:TypeArguments="x:Int32" x:Key="SumResult">[X + Y]</p:InArgument>
      </SendParametersContent>
    </SendReply>
  </p:Sequence>

Aslında Calculus isimli Workflow Service içerisinde yer alan SumOp isimli operasyonun görevi çok basit ve bellidir. Int32 tipinden iki sayısal değerin toplanması ve sonucunun istemci tarafına geri döndürülmesi. Bizim hedefimiz sadece yetkisi olan kişilerin bu operasyonu çalıştırmasıdır. Bu durumda güvenlik ile ilişkili olarak yetki kontrolünün özel bir sınıf tarafından yapılması gerekmektedir. Söz konusu sınıf System.ServiceModel isim alanı(Namespace) altında yer alan ServiceAuthorizationManager tipinden türetilmelidir. İçeriğini ise çok sade olarak aşağıdaki gibi tasarlayabiliriz.

using System.Collections.Generic;
using System.Security.Principal;
using System.ServiceModel;

namespace UsingCustomAuthorization
{
    public class Authorizer
        : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            var authCtx = operationContext.ServiceSecurityContext.AuthorizationContext;
            var identities=(List<IIdentity>)authCtx.Properties["Identities"];

            foreach (var identity in identities)
            {
                var winIdentity = identity as WindowsIdentity;
                if (winIdentity != null)
                {
                    var winPrincipal = new WindowsPrincipal(winIdentity);
                    return winPrincipal.IsInRole("Administrators");
                }
            }

            return false;
        }
    }
}

Authorizer sınıfı içerisinde CheckAccessCore metodu ezilmiş ve parametre olarak gelen operationContext değişkeninden yararlanarak gerekli yetki kontrolü yapılmıştır. Buna göre Workflow Service' ten talepte bulunan bir Windows kullanıcısı, servisin host edildiği makinede tanımlı ise, Administrators rolünde olup olmadığı kontrol edilmekte ve buna göre geriye true veya false değeri döndürülmektedir. Tahmin edileceği üzere CheckAccessCore metodunun geriye false değer döndürmesi güvenlik hatasına yol açacaktır. Burada unutulmaması gereken bir noktayı da hatırlatmak yarar var. Örneğimizde konuyu son derece basit bir şekilde ele almak istediğimizden, doğrudan Administrator rolünün kontrolünü yapıp işin içinden sıyrılmaktayız. Oysaki yetki kontrolü için harici bir listeden yararlanılabilir. Bu liste web.config dosyasında appSettings kısmında tutulabileceği gibi bir Text dosya içerisinde veya veritabanı üzerindeki bir tabloda konuşlandırılabilir. Yinede varmak istediğimiz noktayı anladığınızı düşünerek devam ediyorum.

Sırada çalışma zamanı için Authorizer sınıfının yetki kontrolü amacıyla kullanılacağını bildirmemiz gerekiyor. Bunun için web.config dosyasını aşağıdaki şekilde düzenlememiz yeterli olacaktır.(Bu arada projemize System.IdentityModel.dll assembly' ını referans etmeyi unutmamalıyız. Aksi takdirde authCtx üzerinden hiç bir özelliğe erişemeyiz)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceAuthorization serviceAuthorizationManagerType="UsingCustomAuthorization.Authorizer, UsingCustomAuthorization"/>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add scheme="http" binding="wsHttpBinding"/>
    </protocolMapping>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

Dikkat edileceği üzere serviceAuthorization elementi ile yetkilendirme davranışını ele alacak Authorizer tipi belirlenmiştir. Ayrıca iletişimin güvenli olmasını sağlamak adına protocolMapping sekmesinde wsHttpBinding bağlayıcı tipinin kullanılacağı bildirilmiştir. İşte bu kadar. Wink Artık yemeğimizi orta ateşte 40 dakika kadar pişirip servis edebiliriz. Tabi servis etmeden önce tadına bakmak gerekmektedir. Nasıl mı?

Öncelikli olarak Administrator rolünde olan ve olmayan iki test kullanıcımız olduğunu düşünelim. Ben, örneği geliştirmekte olduğum makinede bu amaçla bsenyurt ve runi isimli iki kullanıcı oluşturdum. Bu kullanıcılardan bsenyurt Administrator rolünde iken runi User rolü içerisinde yer almakta. Dolayısıyla test sonuçlarımıza göre runi isimli kullanıcı talebi karşılığında Access Denied hata mesajını almalı. Bakalım gerçektende böylemi oldu?

WcfTestClient uygulamamızı Run As.. komutu yardımıyla önce bsenyurt kullanıcısı ile çalıştıralım. Eğer Asp.Net Developement Server' ı Visual Studio ortamına Attach' larsak CheckAccessCore metodu içeriğini de debug edebiliriz. Buna göre debug modunda aşağıdaki sonuçlar ile karşılaşırız.

Görüldüğü üzere bsenyurt kullanıcısı doğrulanmıştır. IsAuthenticated değerinin true olduğuna dikkat edelim. Şimdi WcfTestClient uygulamasının sonuç ekranına bakarsak toplama işleminin başarılı bir şekilde gerçekleştirildiğini görebiliriz.

Şimdi de WcfTestClient aracını Runi isimli kullanıcı ile çalıştıralım. Debug modda aşağıdaki sonuçlar ile karşılaşırız.

Tahmin edileceği üzere Runi isimli kullanıcı da doğrulanmıştır. Nitekim servisin çalıştırıldığı makine de tanımlı bir Windows kullanıcısıdır. Ancak WcfTestClient uygulaması üzerinden bir toplama işlemi talebinde bulunulduğunda hata mesajı ile karşılaşılacaktır.

Volaaaaa!!! Bu çok doğaldır. Nitekim Runi isimli kullanıcı Administrators grubuna dahil değildir ve bu yüzden yetki kontrolünden geçememiştir.

Yapmış olduğumuz bu çalışmaya göre bir Workflow Service' ini host ettiğimiz sunucu üzerindeki Windows kullanıcılarından ve dahil oldukları grup bilgilerinden yararlanarak az bir kodlama ile doğrulama ve yetkilendirme işlemlerini gerçekleştirebiliriz. Afiyet olsun Wink

UsingCustomAuthorization_RC.rar (17,67 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Workflow Foundation 4.0 - Paralel Olmak ya da Olmamak

Salı, 16 Mart 2010 11:15 by bsenyurt

Merhaba Arkadaşlar,

Pek çoğumuz ünlü İngiliz şairi Shakespeare' in adını ve eserlerini bir şekilde duymuş, okumuş veya seyretmişizdir. Yaşadığı 1564-1616 yılları arasında yazdığı sayısız Komedi, Trajedi ve Romanesk bulunmaktadır. Bunlar yüz yıllar boyu Tiyatrolarda sergilenmiş ve edebi değeri yüksek eserlerdir. Shakespeare dendiğinde insanın aklına hemen Romeo ve Juliet, Hamlet, Othello, Machbeth gibi eserleri gelmektedir. Aslında Edebiyat' tan çok fazla anlamam. Büyük bir ihtimalle Matematikçi olduğum içindir. Dolayısıyla Shakespeare gibi ünlü şarilerin eserlerini hayatım boyunca çok fazla dikkate almamışımdır. Tabi son zamanlarda bu tip kült klasiklerin NTV Yayınlarından çıkan çizgi serileri yer almakta. En azından çocuklarımızın okuması için bir hamlede bulunabiliriz. Her ne kadar Shakespeare' i çok fazla okumasam da, Mel Gibson' ın oyunculuğuyla parladığı Hamlet filmini seyretmişimdir. Her ne kadar Shakespeare' in eserlerindeki anlamı, derinliği tam olarak bilemesem de, şiirindeki şu meşhur mısrayı hiç unutmam; "Olmak ya da olmamak; işte bütün mesele bu" . Bu konuya nereden mi geldik? Kısaca hikayeyi anlatayım.

Geçtiğimiz günlerde Workflow Foundation 4.0 içerisinde NativeActivity türevli bileşenlerde hata yönetiminin nasıl yapılabileceğini incelerken, ne olduysa kendimi ParallelForEach<T> aktivitesini çalıştırmaya uğraşırken buldum. Bir türlü istediğim gibi ayrı Thread parçaları oluşturulmuyor dolayısıyla aktivite içerisine aldığım işler paralel olarak yürütülmüyordu. O sırada şöyle mırıldandığımı çok net hatırlıyorum; "Paralel olmak ya da olmamak. Sanırım tüm mesele bu..." Sealed İşte bu yazımızda ParallelForEach<T> aktivitesinin örnek senaryoya göre neden çalıştırılamadığını ve buna karşın basit olan çözümün ne olduğu görmeye çalışacağız. Öncelikli olarak sorunumuzu örnek bir senaryo üzerinden masaya yatıralım. Bu amaçla aşağıdaki sınıf diagramında görülen CodeActivity türevli bir bileşenimiz olduğunu düşünelim.

Kod içeriği;

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

namespace ErrorHandlingForNativeActivities
{
    public sealed class LetterCalculaterActivity
        : CodeActivity
    {
        public InArgument<char> Letter { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            Thread.Sleep(1000);
            Console.WriteLine(
                "{0} ASCII = {1} | Current Thread Id : {2}"
                , Letter.Get(context)
                ,((byte)Letter.Get(context)).ToString()
                , Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }
}

CodeActivity türevli olan bu bileşenimiz InArgument<char> tipinden olan Letter isimli özelliğin çalışma zamanı değerini almakta ve ASCII kodu karşılığı ile o anki yönetimli(Managed) Thread Id değerlerini ekrana yazdırmaktadır. Execute metodunda dikkat edileceği üzere şakacıktan ana Thread' in 1 saniye süreyle duraksatılması söz konusudur. Peki bu Activite bileşeni ne işimize yarayacak? Bu amaçla tasarım görünümü aşağıdaki gibi olan bir Workflow geliştirdiğimizi düşünelim.

Xaml içeriği;

<Activity........>
  <x:Members>
    <x:Property Name="vSentence" Type="InArgument(x:String)" />
  </x:Members>
  <sap:VirtualizedContainerService.HintSize>349,370</sap:VirtualizedContainerService.HintSize>
  <mva:VisualBasic.Settings>Assembly references and imported namespaces for internal implementation</mva:VisualBasic.Settings>
  <Sequence sad:XamlDebuggerXmlReader.FileName="D:\Vs 2010\RC\Workflow Foundation\ErrorHandlingForNativeActivities\ErrorHandlingForNativeActivities\ParallelFlow.xaml" sap:VirtualizedContainerService.HintSize="309,330">
    <sap:WorkflowViewStateService.ViewState>
      <scg:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <ParallelForEach x:TypeArguments="x:Char" DisplayName="ParallelForEach&lt;Char&gt;" sap:VirtualizedContainerService.HintSize="287,206" Values="[vSentence]">
      <ActivityAction x:TypeArguments="x:Char">
        <ActivityAction.Argument>
          <DelegateInArgument x:TypeArguments="x:Char" Name="ltr" />
        </ActivityAction.Argument>
        <local:LetterCalculaterActivity sap:VirtualizedContainerService.HintSize="257,100" Letter="[ltr]" />
      </ActivityAction>
    </ParallelForEach>
  </Sequence>
</Activity>

Tasarım zamanından da görüleceği üzere, ParallelFlow.xaml içerisinde ParallelForEach<T> aktivite bileşeni yer almaktadır. ParallelForEach<T>, char tipi ile çalışacak şekilde ayarlanmıştır ve Workflow için vSentence isimli argümanın değerini alıp, söz konusu cümledeki her bir harfi, içerdiği LetterCalculaterActivity bileşenine göndermektedir. Bu akıştan çalışma zamanındaki beklentimiz, söz konusu cümledeki harflerin paralel thread' lerin ele alacağı şekilde yorumlaması ve kullanılmasıdır. Bu arada unutmadan, Main metodunun içeriğinin aşağıdaki gibi olduğunu düşünelim.

using System;
using System.Activities;

namespace ErrorHandlingForNativeActivities
{

    class Program
    {
        static void Main(string[] args)
        {
            ParallelFlow pFlow = new ParallelFlow();
            WorkflowInvoker.Invoke(pFlow);
            Console.WriteLine("İşlemler tamamlandı");
            Console.ReadLine();
        }
    }
}

ve buna göre çalışma zamanı sonuçlarına kısaca bir bakalım.

Uppsss!!! Sealed Enteresan bir durum söz konusu. "Bu gün çok güzel bir gündü" cümlesi ters sırada işlenmiştir. Dahası tüm işlemler 1 numaralı ThreadId' ye bağlı olarak gerçekleştirilmektedir. Bir başka deyişle ParallelForEach<T> aktivitesi istediğimiz/beklediğimiz şekilde çalışmamıştır. Sorun ne olabilir acaba?Undecided Aslında sorundan ziyade yanlış bir çözüm yolu izlediğimizi ifade edebiliriz. Esasında ParallelForEach<T> bileşeninin bu senaryoda işe yarayabilmesi için içerisinde yer alan CodeActivity türevli bileşenin de paralel çalışmaya destek vermesi bir başka deyişle asenkron olarak yürütülebiliyor olması gerekmektedir. Ahaaa!! Wink İşte şimdi çözümü bulduk. Buna göre LetterCalculaterActivity bileşeninin CodeActivity yerine AsyncCodeActivity tipinden türetilmesi ve kodlanması yeterlidir. O halde söz konusu bileşenimizi aşağıdaki şekilde değiştirelim.

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

namespace ErrorHandlingForNativeActivities
{
    public sealed class LetterCalculaterActivity
            : AsyncCodeActivity
    {
        public InArgument<char> Letter { get; set; }

        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            Func<char, bool> dlg = c =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("{0} için hesaplamalar| Current Thread Id : {1}", c, Thread.CurrentThread.ManagedThreadId.ToString());
                return true;
            };

            context.UserState = dlg;
            return dlg.BeginInvoke(Letter.Get(context), callback, state);
        }

        protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            bool r = ((Func<char, bool>)context.UserState).EndInvoke(result);
            Console.WriteLine("\t{0}", r);
        }
    }
}

Kod parçasına göre, temsilcilerin(Delegates) BeginInvoke ve EndInvoke metodlarından yararlanılarak aktivite içerisinde asenkron bir işin yürütülmesinin sağlandığını özetleyebiliriz.

Not: AsyncCodeActivity türevli bileşenlerin nasıl geliştirileceğini daha önceden Workflow Foundation 4.0 - Custom Async Activity Geliştirmek [Beta 2] isimli yazımızda değerlendirmiştik.

Buna göre program kodumuzu yeniden test edersek, çalışma zamanında aşağıdakine benzer sonuçlar ile karşılaştığımızı görebiliriz.

Çalışma sırası tam olarak şöyledir ; "Bu gnü ç kogz eülbir günüd" . Sakın bu cümleyi okumaya çalışmayın.Laughing

Görüldüğü üzere farklı yönetimli Thread Id değerleri üretilmiş, üstelik "Bu gün çok güzel bir gündü" cümlesi aynı harf sırasına göre ele alınmamıştır. Bir başka deyişle paralel çalışma sağlanmıştır. Tabi çalışma zamanı ve çevresel donanım şartlarına göre bu sıralama her seferinde farklı sonuçlanabilir veya aynı sonuçlar tekrar tekrar elde edilebilir. Aslında bütün mesele de budur zaten. "Paralel olmak ya da olmamak". Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ToBeOrNotToBe_RC.rar (48,75 kb) [Örnek uygulama Visual Studio 2010 Ultimate RC Sürümü Üzerinde Geliştirilmiştir ve Test Edilmiştir]

Screencast - Workflow Foundation 4.0 Switch Aktivite Bileşeni [RC]

Pazartesi, 1 Mart 2010 09:35 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere bir süre önce .Net Framework 4.0 RC sürümü yayınlandı. Bu da RTM ve Release sürüme çok yaklaştığımızı göstermekte. RC(Release Candidate) sürümünün önceki Beta sürümlerine göre daha tutarlı olduğunu söyleyebiliriz. Ancak yine de bu görsel dersimizde anlatacaklarımız ile ilişkili olarak Bug Fix' ler veya farklı güncelleştirmeler söz konusu olacaktır. Peki ya biz bu görsel dersimizde neyi ele almaktayız? Wink

Workflow Foundation 4.0 içerisinde yer alan Built-In Activity bileşenleri arasında bir programlama dilinin temel özelliklerinin de çoğu yer almakta aslında. Söz gelimi ForEach<T>, While, If, DoWhile, Assign, InvokeMethod, WriteLine ve benzerleri gibi aktiviteler göz önüne alındığında, değişken ataması, metod çağırılması, döngü kurulması veya koşullu kontrollerin yapılması son derece kolay. Bu anlamda, WF 4.0 içerisinde yer alan aktivite bileşenlerinden bir tanesi de Switch. C# dilinden aşina olduğumuz bu koşul ifadesinin mantığını, Workflow tiplerinde de kullanabilmekteyiz. NedirTv? katkılarıyla gerçekleştirdiğimiz bu görsel dersimizde Switch aktivite bileşeninin özel .Net tipleri için nasıl değerlendirilebileceğini incelemeye çalışmaktayız. Hepinize keyifli seyirler dilerim.

[Görsel derste yer alan örneğimiz Visual Studio 2010 Ultimate Beta 2 sürümünde geliştirilmiş ve test edilmiştir.]

Dosya Boyutu : 24.5 Mb

Süre : 13:43

İzlemek veya Download etmek için

UsingSwitch.rar (57,95 kb)

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

WF 4.0 - Bookmarks [RC]

Cuma, 19 Şubat 2010 14:04 by bsenyurt

Merhaba Arkadaşlar,

Çalışmakta olduğum yazılım şirketinin çok yakınında kocaman bir alışveriş merkezi bulunmakta. Bazen öğle yemekleri için alışveriş merkezinin tahsis ettiği servisler ile oraya gidiyoruz. Alışveriş merkezi olduğu için tehlikeli bir yer olduğunu da söyleyebiliriz. Sealed Nitekim çok büyük bir yer ve A' dan Z'ye herşey bulunabilmekte. Arkadaşlarım ile sık uğradığım mekanlardan birisi de D&R kitap evi. Çoğunlukla aylık dergilerimi almak için uğramaktayım (Aslında Türkiye' de Amazon gibi bir kitap dağıtım evi olmadığı için çok şanslı olduğumu düşünüyorum. Her halde öyle bir yer açılsa kazancımın çok büyük bir kısmı meslek kitaplarına gider) Geçen gün yine Bilim Teknik, NTV Bilim ve NG dergilerimi almak üzere oradaydım. Sırada beklerken kasada ücretsiz olarak verilen kitap ayraçlarını farkettim. Hep görürdüm ama bu gün biraz daha anlamlı geliyorlardı. Üzelerinde çeşitli reklamlar veya faydalı bilgiler bulunan bu ayraçlar yardımıyla(ki Bookmark diyebilir miyiz acaba? Wink), okuduğumuz kitabın neresinde kaldığımızı kolayca hatırlayabildiğimizi düşünmeye başladım. Derken evde uzun süredir el değdirip kaldığım yerden devam edemediğim kitaplarım aklıma geldi. Hüzünlendim...Tongue out Tesadüfe bakın ki bu kitapları okumak baya uzun sürmüştü. Zamanın neresinde okumaya başladığımı pek hatırlamamakla birlikte, neresinde kaldığımı da hatırlamadığım bir kaç kitap...Tesadüfe bakın ki bu uzun sürecin benzeri Workflow Uygulamalarında da söz konusu olabilmekteydi. Wink

Aslında .Net Framework 3.5 sürümünde Uzun Süreli İşemlerin(Long Running Process) için ExternalDataExchangeService veya WorkflowQueue tiplerinden yararlanılmaktadır. Ne varki Workflow Foundation 4.0 sürümünde, geliştiricilerin kulağına daha hoş gelen Bookmark kavramı ile karşılaşmaktayız. Peki Bookmark nedir? Ne işe yaramaktadır? Nasıl kullanılmaktadır? Konuyu anlamanın belki de en kolay yolu her zaman ki gibi basit bir örnek üzerinden ilerlemekle olacaktır. Bu nedenle Bookmark kavramının tanımlamasını yazımızın sonunda yapmaya çalışacağız.

Bookmark kullanımında önemli olan noktalardan birisi, geçici olarak duraksatılabilecek(Pause) Activity bileşeninin Workflow' un çalışma zamanı içeriğine ulaşabiliyor olmasıdır. Bu nedenle en uygun aktivite bileşenleri, NativeActivity(veya NativeActivity<T>) türevli olanlardır. Bunu ilk gereksinimimiz olarak düşünebiliriz. Aşağıdaki diagramda .Net Framework 4.0 RC sürümü içerisinde yer alan NativeActivity tipleri ve üyeleri görülmektedir.

Şimdi bu türetmeyi kullanarak aşağıdaki kod parçasında görülen aktivite bileşenini geliştirdiğimizi düşünelim. İlgili örneğin bir Workflow Console Application üzerinden geliştirebiliriz.

using System;
using System.Activities;

namespace HelloBookmarks
{

    public sealed class ResizeImageActivity
        : NativeActivity
    {
        protected override bool CanInduceIdle
        {
            get
            {
                return true;
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            Console.WriteLine("Resize Image bir takım işlemler yapıyor");
            // Bazı işlemler
            // İkinci parametre BookmarkCallback temsilcisi tarafından işaret edilen bir fonksiyondur.
            context.CreateBookmark("ResizeImageBookmark",               
                (nac, b, obj) =>
                {
                    Console.WriteLine("Resume edilen bookmark adı {0}",b.Name);                   
                }
            );
        }
    }
}

Bookmark işlemleri Idle konuma düşebilen Workflow aktivitelerinde işe yarayacak bir teknik olarak düşünülmelidir. Nitekim bir aktivite içerisinde herhangibir zamanda Pause etme ve sonraki bir anda Resume etme söz operasyonları konusudur. Bu sebepten CanIncludeIdle özelliğinin override edilmesi ve geriye true değer döndürmesi söz konusudur. Aksi durumda çalışma zamanında aşağıda görülen InvalidOperationException hata mesajı alınacaktır.

CreateBookmark metodunun ikinci parametresi BookmarkCallback tipinden bir temsilcidir(Delegate). Bu temsilcinin yapısı ise aşağıdaki gibidir.

public delegate void BookmarkCallback(System.Activities.NativeActivityContext context, System.Activities.Bookmark bookmark, object value)

Buna göre örneğimizde yer alan isimsiz metodun(Anonymous Method) ilk parametresi ile Activity' nin çalışma zamanındaki çevresel içeriğine, ikinci parametre ilede Bookmark örneğine erişilebilir. Bu temsilci aslında bir geri bildirim metodunu(Callback Method) işaret etmektedir. Bir başka deyişle, Idle konumda kalan Activity örneğinin tekrar Resume edilmesi halinde devreye girecek olan metod olarak düşünülebilir. Dolayısıyla geri bildirim metodu içerisinde CreateBookmark tarafında saklanan bazı varlıkların tekrardan yüklenmesi, hazırlanması gibi operasyonlar ele alınabilir. CreateBookmark metodunun aslında 8 aşırı yüklenmiş(Overload) versiyonu bulunmaktadır. Diğer versiyonlar göz önüne alındığında dikkat çeken parametrelerden birisi BookmarkOptions Enum sabitidir. Bu enum sabiti MultipleResume, NonBlocking ve None değerlerinden birisini almaktadır. Varsayılan değer None' dır. MultipleResume olması halinde bir den fazla Resume işlemi yapılabileceği belirtilir. NonBlocking değerine göre ilgili aktivite bileşeni Resume edilmemiş olsa dahi WF' in çalışacağını belirtilir. Nitekim normal şartlar altında bir aktivite içerisinde oluşturulan Bookmark' ların tamamı Resume edilmediği sürece WF' in tamamlanması söz konusu değildir. Dilerseniz MultipleResume ve NonBlocking değerlerini bir arada kullanabilirsiniz. Gelelim aktivitenin nasıl kullanılacağına. Örneğimizdeki amacımız sadece Bookmark kullanımını görmek olduğundan aşağıdaki şekilde görülen Workflow Activity içeriğini değerlendirebiliriz.

Bookmark kullanımı yazımızın başında da belirttiğimiz üzere uzun süreli işlemler(Long Running Process) için anlamlıdır. Bu sebepten WorkflowApplication tipinin kullanılması gerekmektedir. Workflow Console Application tipinden olan uygulamamızda, çalışma zamanındaki Idle durumları irdelememiz aslında son derece kolaydır. Console.ReadLine metodu burada çok işe yarayacaktır. Wink İşte kod içeriğimiz;

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

namespace HelloBookmarks
{

    class Program
    {
        static void Main(string[] args)
        {
            AutoResetEvent rE = new AutoResetEvent(false);
           
            // Workflow örneği oluşturlur
            Workflow1 wf1 = new Workflow1();
            // Workflow Application örneği oluşturulur
            WorkflowApplication wfApp = new WorkflowApplication(wf1);
            // Workflow' un tamamlanması sonrası devreye girecek Completed olay metodu
            wfApp.Completed = (e) => { rE.Set(); }; // işlemlerin bittiğine dair bilgilendirme için AutoResetEvent örneğinin Set metodu çağırılır.
            // Workflow çalışma zamanı başlatılır dolayısıyla Workflow1 örneği yürütülür
            wfApp.Run();           
            Console.WriteLine("Bir süre bekleyin...");
            Console.ReadLine(); // Bu noktada Workflow1 örneğinin Idle konuma geçmesi söz konusudur.
           
            /* Kullanıcı devam etmek istediğinde Bookmark' lanan Workflow1 örneğine tekrardan hayata geçirilir. ResumeBookmark metodunun ilk parametresi dikkat edileceği üzere ResizeImageActivity içerisinde kullanılan Bookmark adıdır. Bu adın aslında aktivite bileşeni içerisinden çalışma zamanı ortamına verilmesi(örneğin bir OutArgument) ile faydalı olabilir. İkinci parametre ise hangi Workflow örneğinin Resume edileceğidir. Buna göre Workflow1 içerisinde ResizeImageBookmark isimli Bookmark' ın yer aldığı aktivite bileşeninin Resume edilmesi söz konusudur. */
            BookmarkResumptionResult result = wfApp.ResumeBookmark("ResizeImageBookmark", wf1);
            // ResumeBookmark metodunun sonucu olan Enum sabitinin değerine göre bir işlem yapılabilir
            switch (result)
            {
                case BookmarkResumptionResult.NotFound:
                    Console.WriteLine("Not Found");
                    break;
                case BookmarkResumptionResult.NotReady:
                    Console.WriteLine("Not Ready");
                    break;
                case BookmarkResumptionResult.Success:
                    Console.WriteLine("Success");
                    break;
                default:
                    break;
            }
           
            // Eğer Workflow Application' ın beklediği çalışan örnekler var ise bunların tamamlanması beklenir
            rE.WaitOne();
        }
    }
}

Aslında örnek kodumuz Workflow1 tipinden bir nesne örneğini çalıştırmakta ve yaşamı içerisinde kullanıcından belirli süreliğine tuşa basmasını beklemektedir. Tuşa basmayı beklediği sırada ise Idle olabilen bileşenlerin bu konuma geçmesi söz konusudur. ResumeBookmark çağrısından sonra ise Bookmark ile Pause konumda duran aktivitenin ilgili geri bildirim metodunun çağırılması ve dolayısıyla yürütülmeye devam edilmesi sağlanır. İşte örnek program kodumuzun çalışma zamanı çıktısı.

Bu arada çalışma zamanında aktif olan Bookmark listesini de WorkflowApplication nesne örneğine ait GetBookmarks metodu üzerinden alabileceğinizi belirtmek isterim. Aşağıdaki kod parçasında bu durum örneklenmektedir.

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

namespace HelloBookmarks
{
    class Program
    {
        static void Main(string[] args)
        {
            AutoResetEvent rE = new AutoResetEvent(false);                       
            Workflow1 wf1 = new Workflow1();
            WorkflowApplication wfApp = new WorkflowApplication(wf1);
            wfApp.Completed = (e) => { rE.Set(); }; // işlemlerin bittiğine dair bilgilendirme için AutoResetEvent örneğinin Set metodu çağırılır.
            wfApp.Run();           
            Console.WriteLine("Bir süre bekleyin...");
            Console.ReadLine();
            foreach (BookmarkInfo bm in wfApp.GetBookmarks())
            {
                Console.WriteLine(bm.BookmarkName);
            }           
...

Şimdi örneğimizi biraz daha ilginç bir hale getirelim. Öncelikli olarak aşağıdaki kod içeriğine sahip Custom Activity sınıfını oluşturarak projemize ilave edelim.

using System;
using System.Activities;

namespace HelloBookmarks
{

    public sealed class SendImageActivity
        : NativeActivity
    {       
        protected override bool CanInduceIdle
        {
            get
            {
                return true;
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            Console.WriteLine("Send Image bir takım işlemler yapıyor");
            context.CreateBookmark("SendImageBookmark",               
                (nac, b, obj) =>
                {
                    Console.WriteLine("Resume edilen bookmark adı {0}",b.Name);                   
                }
            );
        }
    }
}

ResizeImageActivity tipinin tıpkısının aynısı olan bu bileşeni de ele alarak Workflow1 içeriğini aşağıdaki şekilde görüldüğü gibi değiştirelim.

Bu sefer aynı anda birden fazla aktivitenin çalıştırılmasına izin veren Parallel bileşeninden yararlanmaktayız. İncelemek istediğimiz nokta ise şu; her iki aktivite de Execute metodları içerisinde birer Bookmark oluşturmaktadır. Buna göre program kodumuzda birden fazla Bookmark' ın nasıl ele alınacağına bakmak istiyoruz. Söz konusu vakayı analiz etmek için, Main metoduna ait kod içeriğini aşağıdaki gibi değiştirmemiz yeterli olacaktır.

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

namespace HelloBookmarks
{
    class Program
    {
        static void Main(string[] args)
        {
            AutoResetEvent rE = new AutoResetEvent(false);           
            Workflow1 wf1 = new Workflow1();
            WorkflowApplication wfApp = new WorkflowApplication(wf1);
            wfApp.Completed = (e) => { rE.Set(); };
            wfApp.Run();           
            Console.WriteLine("Bir süre bekleyin...");
            Console.ReadLine();
            Console.WriteLine("***Etkin Bookmark Listesi***");
            foreach (BookmarkInfo bm in wfApp.GetBookmarks())
            {
                Console.WriteLine("Bookmark Name:{0}, Owner:{1}",bm.BookmarkName,bm.OwnerDisplayName.ToString());
            }
            Console.WriteLine();
           
            BookmarkResumptionResult result1 = wfApp.ResumeBookmark("ResizeImageBookmark", wf1);
            BookmarkResumptionResult result2 = wfApp.ResumeBookmark("SendImageBookmark", wf1);

            Console.WriteLine("ResizeImageBookmark için Resume Result : {0}",result1.ToString());
            Console.WriteLine("SendImageBookmark için Resume Result : {0}", result2.ToString());

            rE.WaitOne();
            Console.ReadLine();
        }
    }
}

Ve işte çalışma zamanı sonuçları;

Çıktımıza göre her iki akitivite bileşeni, eş zamanlı olarak Execute metodlarını icra etmiş ve birer Bookmark oluşturmuştur. Sonrasında kullanıcının tuşa basması ile devam eden süreçte ilk olarak yüklü olan Bookmark' lar listelenmiş ve ardından ResumeBookmark çağrıları nedeniyle Pause edilmiş olan aktivitelerin geri bildirim operasyonları devreye girmiştir. Her iki Bookmark' ında Resume edilmesinin sonucu olarak Workflow1 örneğinin işlemlerini tamamladığı anlaşılmaktadır. Program kodu da buna göre sonlanır.

Görüldüğü gibi bir aktivitenin Bookmark' lanması aslında isimlendirilmiş duraksama noktalarına(Named Pause Points) sahip olması anlamına gelmektedir. Öyleki, ResumeBookmark metodu sayesinde Pause edilen noktadan tekrar yürütülmeleri sağlanabilir. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HelloBookmarks.rar (54,52 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]