https://buraksenyurt.com/Burak Selim Şenyurt - WF2018-04-27T06:32:38+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/WF-Rule-Engine-How-ToWF Rule Engine’ i Dışarıdan Kullanmak2012-09-02T19:45:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/mfln2687l.jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="mfln2687l" src="/pics/mfln2687l_thumb.jpg" alt="mfln2687l" width="280" height="233" align="right" /></a>Merhaba Arkadaşlar,</p>
<p>Kurallar, kurallar, kurallar!Hayatın hemen her noktasında karşımıza tonlarca kural çıkar. Tabi mevzumuz kuralların zorlayıcılığı ve saire değil, kuralların ihlal edilmesi veya uyulması halinde gerçekleşen aksiyonların neler olduğu ile ilgilidir. Ki bu düşünce tarzı aslına bakarsanız iş dünyasının çekirdek süreçlerinden tutun, en ince ve hatta uç noktalarına kadar yayılır.</p>
<p>Hangi sektörde olunursa olunsun, işler ister kağıt üstünde, ister elektronik ortamda yürüsün, iş süreçleri kendi içerisinde tanımlı bir çok kural kümesi içerir. İş bu kural kümeleri, gerekli durumlarda doğal yollarla sistemin bir parçası olarak ya da yürütme usulü ile manuel olarak devreye girerek, sürecin şekillenmesi ve bir takım aksiyonların alınması noktasında önemli rol üstlenirler.</p>
<p>Biz geliştiricilerde, iş akışı mantığına dayalı sistemleri tasarladığımız durumlarda bu kural kümelerinin esnekliklerine sahip olmak isteriz. Bu Biztalk, Sharepoint, TIBCO vb iş akışı modellerini içeren gelişmiş ürünlerde çoğu zaman karşımıza çıkmaktadır.</p>
<p>Aslına bakarsınız uzun bir süre önce <a href="https://buraksenyurt.com/post/Business-Rule-Engine-ile-Programlama(Biztalk-Server-2006)">Business Rule Engine ile Programlama(Biztalk Server 2006)</a> başlıklı bir makale yazmıştım. Bu makalede ana konu <strong>Biz Talk’</strong> un gelişmiş<strong> Business Rule Engine</strong> ara birimini <strong>BizTalk</strong> ortamı dışında nasıl kullanabileceğimizi görmekti. O zamanlar bir <strong>POC(Proof of Concept)</strong> çalışması için yapmış olduğum araştırmanın sonuçlarının kayıt altına alınmış hali olan bu yazı pek çoğu gibi atıl oldu tabi...</p>
<p>Ancak nasıl ki <strong>Biztalk</strong> tarafında dışarıdan kullanabileceğimiz bir<strong> Rule Engine</strong> bulunmaktadır, benzer şekilde <strong>Workflow Foundation</strong> tarafında da kural setleri tanımlayıp, uygulatabileceğimiz bir çalışma zamanı motoru <em>(Runtime Rule Engine) </em>mevcuttur. İşte bu yazımızda söz konusu <strong>WF Rule Engine</strong> arabirimini herhangibir .Net uygulamasında basit anlamda nasıl kullanabileceğimizi bir kaç temel adımla görmeye/anlamaya çalışıyor olacağız.</p>
<p>Örnek senaryomuzda <strong>Product</strong> isimli bir <strong>POCO(Plain Old CLR Object)</strong> tip üzerinden <em>kural tanımlanması, kuralların kayıt edilmesi, yeniden yüklenmesi</em> ve istenildiği zaman canlı bir <strong>Product</strong> örneği üzerinde <em>işletilmesi</em> gibi fonksiyonellikleri sağlıyor olacağız. İşe ilk olarak aşağıdaki basit görünüme sahip olan bir <strong>Windows Forms</strong> uygulaması geliştirerek başlayabiliriz.</p>
<p><a href="https://buraksenyurt.com/pics/wfrule_Form.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_Form" src="/pics/wfrule_Form_thumb.png" alt="wfrule_Form" width="443" height="284" /></a></p>
<p>Test amaçlı olarak kullanacağımız bu <strong>Windows Form</strong> uygulamasında <strong>Product</strong> tipine ait basit kuralları tanımlayabileceğimiz, kayıt altına alabileceğimiz, tekrardan yükleyebileceğimiz ve çalıştırıp sonuçlarını görebileceğimiz işlevsellikler söz konusudur.</p>
<blockquote>
<p>Pek tabi Workflow Foundation Rule Set Engine kullanılmak istendiğinden, projeye aşağıdaki .Net Assembly’ larının da referans edilmesi gerekmektedir.<a href="https://buraksenyurt.com/pics/wfrule_References.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_References" src="/pics/wfrule_References_thumb.png" alt="wfrule_References" width="278" height="472" /></a></p>
</blockquote>
<p>Şimdi arka planda gerekli olan kod üretimlerini gerçekleştirerek işlemlerimize devam edelim. Kural motoru için kullanacağımız <strong>Product</strong> tipi ve bir kuralın <strong>XAML(eXtensible Application Markup Language)</strong> olarak <em>serileştirilmesi(Serialization)</em> ile tekrardan <em>geri yüklenmesi(DeSerialization)</em> için gerekli fonksiyonellikleri içeren <strong>Utility</strong> sınıfının kod içerikleri aşağıdaki gibidir.</p>
<p><strong>Sınıf diyagramı;</strong></p>
<p><a href="https://buraksenyurt.com/pics/wfrule_Model.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_Model" src="/pics/wfrule_Model_thumb.png" alt="wfrule_Model" width="288" height="486" /></a></p>
<p><strong>Product.cs;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
namespace WFRuleSetHowTo
{
public class Product
{
public Guid ProductId { get; set; }
public string Name { get; set; }
public decimal ListPrice { get; set; }
public int StockLevel { get; set; }
public string ErrorInformation { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2} {3} [{4}]"
, ProductId
, Name
, ListPrice
, StockLevel
,ErrorInformation
);
}
}
}</pre>
<p><strong>Utility.cs;</strong></p>
<p> </p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Workflow.Activities.Rules;
using System.Workflow.ComponentModel.Serialization;
using System.Xml;
namespace WFRuleSetHowTo
{
public static class Utility
{
// Workflow RuleSet' lerin XAML bazlı serileştirilmesi/ters serileştirilmesi için gerekli nesne örneklenir
private static WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
public static RuleSet Load(string ruleSetFileName)
{
RuleSet ruleSet = null;
try
{
// RuleSet dosyası okunmak üzere reader' a yüklenir
XmlTextReader reader = new XmlTextReader(ruleSetFileName);
// Ters serileştirme işlemi uygulanarak RuleSet içeriği nesnelleştirilir
ruleSet = serializer.Deserialize(reader) as RuleSet;
reader.Close();
}
catch (Exception excp)
{
//TODO@Burak Do Something
}
return ruleSet;
}
public static bool Save(string ruleSetFileName, RuleSet ruleset)
{
bool result = false;
try
{
// RuleSet' i kaydetmek için bir writer oluşturulur
XmlTextWriter writer = new XmlTextWriter(ruleSetFileName, null);
// RuleSet ilgili dosya içerisine serileştirilir
serializer.Serialize(writer, ruleset);
result = true;
writer.Flush();
writer.Close();
}
catch (Exception excp)
{
//TODO@Burak Do Something
}
return result;
}
}
}</pre>
<p><strong>Utility</strong> sınıfı temel olarak bir <strong>RuleSet</strong> örneğinin fiziki dosyaya <strong>XAML</strong> formatında serileştirilmesi veya tam tersi olarak <strong>XAML</strong> formatından geriye yüklenerek çalışma zamanında kullanılabilmesi işlevlerini barındırmaktadır. <strong>Product</strong> sınıfı ise örneğimizde kullanacağımız ve kural seti içerisinde ele alacağımız nesne şablonu olarak düşünülmelidir. Gelelim <strong>Form</strong> üzerindeki Button arkası kod parçalarına.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.IO;
using System.Windows.Forms;
using System.Workflow.Activities.Rules;
using System.Workflow.Activities.Rules.Design;
namespace WFRuleSetHowTo
{
public partial class Form1 : Form
{
Product sampleProduct = null;
RuleSet sampleRuleSet = null;
string ruleSetFileName = Path.Combine(Environment.CurrentDirectory, "Product.rules");
public Form1()
{
InitializeComponent();
// Kullanılacak olan RuleSet örneklenir
sampleRuleSet = new RuleSet();
}
private void btnCreateProduct_Click(object sender, EventArgs e)
{
decimal price;
int stockLevel;
// RuleSet testi için örnek bir Product instance' ı oluşturulur
sampleProduct = new Product
{
ProductId=Guid.NewGuid(),
Name=!String.IsNullOrEmpty(txtProductName.Text)?txtProductName.Text:"Ornektir",
ListPrice=decimal.TryParse(txtProductListPrice.Text,out price)?price:1M,
StockLevel=int.TryParse(txtStockLevel.Text,out stockLevel)?stockLevel:100
};
MessageBox.Show(string.Format("{0} örnek kullanım için üretildi",sampleProduct.ToString()));
}
private void btnCreateRule_Click(object sender, EventArgs e)
{
// RuleSet' in oluşturulacağı Dialog nesnesi örneklenir
// ilk parametre kuralın uygulanacağı nesne tipidir
RuleSetDialog rsDialog = new RuleSetDialog(typeof(Product), null, sampleRuleSet);
if (rsDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
if (Utility.Save(ruleSetFileName,rsDialog.RuleSet))
MessageBox.Show("Rule Set başarılı bir şekilde kayıt edildi");
else
MessageBox.Show("İşlemlerinizi gözden geçiriniz. RuleSet kayıt edilemedi");
}
}
private void btnLoadRule_Click(object sender, EventArgs e)
{
sampleRuleSet = Utility.Load(ruleSetFileName);
if (sampleRuleSet != null)
MessageBox.Show("RuleSet başarılı bir şekilde yüklendi");
else
MessageBox.Show("RuleSet yüklenemedi!");
}
private void btnRunRule_Click(object sender, EventArgs e)
{
// Elimizde bir RuleSet' imiz var ise
if (sampleRuleSet != null)
{
// Kuralı işletmek için gerekli doğrulama nesnesi örnek Product tipi için üretilir
RuleValidation validation = new RuleValidation(sampleProduct.GetType(), null);
// Kuralı işletecek olan motor örneklenir. İlk parametre doğrulama kriterlerini ikinci parametre ise doğrulamaya tabi olacak canlı nesne örneğini içerir
RuleExecution engine = new RuleExecution(validation, sampleProduct);
// Kural işletilir.
sampleRuleSet.Execute(engine);
MessageBox.Show(sampleProduct.ToString());
}
}
}
}</pre>
<p>Geliştirici bu arabirim üzerinden bir <strong>Product</strong> tipi için yeni <strong>RuleSet</strong> tanımlayabilir, kayıt altına alabilir, kayıtlı olanı yükleyebilir ve işletebilir. İşin temelinde <strong>RuleSetDialog, RuleValidation, RuleExecution</strong> ve <strong>RuleSet</strong> tipleri yer almaktadır.</p>
<p><strong>RuleSet</strong> nesne örneğine ait <strong>Execute</strong> metodu parametre olarak gelen <strong>RuleExecution</strong> <strong>instance’</strong> ını baz alarak bir kural kümesi işletimini gerçekleştirmektedir. <strong>RuleExecution</strong>, hangi nesne örneği için ilgili kural kümesinin çalıştırılacağını, ikinci parametresi sayesinde bilmekte olup ilk parametre ile de bir doğrulama işlemini sürece dahil etmektedir. Bu doğrulama, <strong>RuleValidation</strong> örneğine göre bir<strong> .Net</strong> tipi için<em>(örneğimizde Product sınıfıdır)</em> icra edilmektedir.</p>
<p><strong>RuleSetDialog</strong> tipi ile aşağıdakine benzer bir iletişim kutucuğunun<em>(Rule Set Editor)</em> çalışma zamanında açılması ve yine resimde görüldüğü gibi örnek bir kuralın tanımlanması mümkündür.</p>
<p><a href="https://buraksenyurt.com/pics/wfrule_dialog.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_dialog" src="/pics/wfrule_dialog_thumb.png" alt="wfrule_dialog" width="640" height="533" /></a></p>
<p>Örnekte tanımlanan <strong>StockLevelRule</strong> ile, herhangibir <strong>Product</strong> nesne örneğinin <strong>StockLevel</strong> <strong>değerinin 250’ nin altında olması</strong> hali ele alınmış ve durumun <strong>true</strong> veya <strong>false</strong> olmasına göre yine o anki canlı <strong>Product</strong> nesne örneğinin <strong>ErrorInformation</strong> özelliğine bazı bilgilendirme mesajları atanmıştır. <em>(Çok doğal olarak burada başka atamaların yapılması da söz konusu olabilir) </em>Tanımlanan bu kural seti serileştirilerek kayıt altına alındığında ise aşağıdaki <strong>XAML</strong> içeriğinin üretildiği görülür.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><RuleSet Description="{p1:Null}" Name="{p1:Null}" ChainingBehavior="Full" xmlns:p1="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<RuleSet.Rules>
<Rule Priority="0" ReevaluationBehavior="Always" Description="{p1:Null}" Active="True" Name="StockLevelRule">
<Rule.Condition>
<RuleExpressionCondition Name="{p1:Null}">
<RuleExpressionCondition.Expression>
<ns0:CodeBinaryOperatorExpression Operator="LessThan" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ns0:CodeBinaryOperatorExpression.Right>
<ns0:CodePrimitiveExpression>
<ns0:CodePrimitiveExpression.Value>
<ns1:Int32 xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">250</ns1:Int32>
</ns0:CodePrimitiveExpression.Value>
</ns0:CodePrimitiveExpression>
</ns0:CodeBinaryOperatorExpression.Right>
<ns0:CodeBinaryOperatorExpression.Left>
<ns0:CodePropertyReferenceExpression PropertyName="StockLevel">
<ns0:CodePropertyReferenceExpression.TargetObject>
<ns0:CodeThisReferenceExpression />
</ns0:CodePropertyReferenceExpression.TargetObject>
</ns0:CodePropertyReferenceExpression>
</ns0:CodeBinaryOperatorExpression.Left>
</ns0:CodeBinaryOperatorExpression>
</RuleExpressionCondition.Expression>
</RuleExpressionCondition>
</Rule.Condition>
<Rule.ThenActions>
<RuleStatementAction>
<RuleStatementAction.CodeDomStatement>
<ns0:CodeAssignStatement LinePragma="{p1:Null}" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ns0:CodeAssignStatement.Left>
<ns0:CodePropertyReferenceExpression PropertyName="ErrorInformation">
<ns0:CodePropertyReferenceExpression.TargetObject>
<ns0:CodeThisReferenceExpression />
</ns0:CodePropertyReferenceExpression.TargetObject>
</ns0:CodePropertyReferenceExpression>
</ns0:CodeAssignStatement.Left>
<ns0:CodeAssignStatement.Right>
<ns0:CodePrimitiveExpression>
<ns0:CodePrimitiveExpression.Value>
<ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Stok seviyesi kritik</ns1:String>
</ns0:CodePrimitiveExpression.Value>
</ns0:CodePrimitiveExpression>
</ns0:CodeAssignStatement.Right>
</ns0:CodeAssignStatement>
</RuleStatementAction.CodeDomStatement>
</RuleStatementAction>
</Rule.ThenActions>
<Rule.ElseActions>
<RuleStatementAction>
<RuleStatementAction.CodeDomStatement>
<ns0:CodeAssignStatement LinePragma="{p1:Null}" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ns0:CodeAssignStatement.Left>
<ns0:CodePropertyReferenceExpression PropertyName="ErrorInformation">
<ns0:CodePropertyReferenceExpression.TargetObject>
<ns0:CodeThisReferenceExpression />
</ns0:CodePropertyReferenceExpression.TargetObject>
</ns0:CodePropertyReferenceExpression>
</ns0:CodeAssignStatement.Left>
<ns0:CodeAssignStatement.Right>
<ns0:CodePrimitiveExpression>
<ns0:CodePrimitiveExpression.Value>
<ns1:String xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">Stok seviyesi normal</ns1:String>
</ns0:CodePrimitiveExpression.Value>
</ns0:CodePrimitiveExpression>
</ns0:CodeAssignStatement.Right>
</ns0:CodeAssignStatement>
</RuleStatementAction.CodeDomStatement>
</RuleStatementAction>
</Rule.ElseActions>
</Rule>
</RuleSet.Rules>
</RuleSet></pre>
<p>Her ne kadar bu çıktıyı gözle takip etmek zor olsa da şu noktaya dikkat edilmelidir.</p>
<blockquote>
<p>XAML olarak üretilen içerik Notepad gibi basit bir metin editörü ile açılıp düzenlenebilir. Bir başka deyişle kuralların dekleratif olarak tanımlanabilmesi, güncellenmesi ve devreye alınması söz konusudur.</p>
</blockquote>
<p>Pek tabi kayıt altına serileştirerek almış olduğumuz bu <strong>XAML</strong> içeriğini uygulamayı kapatsak bile tekrardan aynı veya farklı uygulamalara yükleyebilir ve işletebiliriz. Uygulamamızda örnek bir <strong>Product</strong> için kural çalıştırıldığında aşağıdaki sonucun alındığı gözlemlenir.</p>
<p>Stok seviyesinin kuralda tanımlanan <strong>250 birimin altında</strong> olması halinde,</p>
<p><a href="https://buraksenyurt.com/pics/wfrule_Run1.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_Run1" src="/pics/wfrule_Run1_thumb.png" alt="wfrule_Run1" width="502" height="404" /></a></p>
<p>Stok seviyesinin kuralda tanımlanan <strong>250 birimin üstünde</strong> olması halinde,</p>
<p><a href="https://buraksenyurt.com/pics/wfrule_Run2.png"><img style="margin: 4px 0px; display: inline;" title="wfrule_Run2" src="/pics/wfrule_Run2_thumb.png" alt="wfrule_Run2" width="465" height="420" /></a></p>
<p>Görüldüğü gibi <strong>Workflow Foundation</strong> ile birlikte gelen kural motorunun herhangibir<strong> .Net</strong> uygulaması üzerinden kullanılabilmesi son derece kolaydır. Hatta bu tip bir arabirim yardımıyla, iş analistlerinin çeşitli kurallar tanımlayıp kayıt altına alabilecekleri ve aslında süreç yönetim araçlarında önemli yere sahip olan bir takım depolama programlarının geliştirilmesi de kolaylaşmaktadır. Çok doğal olarak bu kurallar bir servis arkasında işletilebilirler de.</p>
<blockquote>
<p><strong>WF Rule Set Editor</strong> arayüzü, kullanıcısına daha esnek bir şekilde kural tanımlayabilme ve bunları kayıt altına alarak saklayabilme imkanı sunmaktadır.</p>
</blockquote>
<blockquote>
<p>XAML formatlı olarak kayıt altına alınabilen kural kümeleri(RuleSet) istenildiği zaman çalışma zamanına yüklenebilir ve tanımın ait olduğu nesne örneği/örnekleri için işletilebilir.</p>
</blockquote>
<blockquote>
<p>Rule Set Editör içerisinde birden fazla kural tanımlanabilir ve bunlar çalışma zamanında yürütülebilir.</p>
</blockquote>
<p>Bu yazımızda çok basit olarak <strong>Workfow Rule Engine</strong> alt yapısına bir Merhaba demeye çalıştık. Kapıyı aralamak benden içeri girip yürümek ise sizden. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><span style="color: #ff0000;">[Örnek her nedense halen RC sürümünde olan Visual Studio 2012 ile geliştirilmiştir. Ancak Visual Studio 2010 ile de çalışmaktadır]</span></p>
<p><a href="https://buraksenyurt.com/pics/2012%2f7%2fWFRuleSetHowTo.zip">WFRuleSetHowTo.zip (60,57 kb)</a></p>2012-09-02T19:45:00+00:00rule engineworkflow foundationworkflow foundation 4.0wf rule enginerule setrulesetwindows formsbsenyurtHangi sektörde olunursa olunsun, işler ister kağıt üstünde, ister elektronik ortamda yürüsün, iş süreçleri kendi içerisinde tanımlı bir çok kural kümesi içerir. İş bu kural kümeleri, gerekli durumlarda doğal yollarla sistemin bir parçası olarak ya da yürütme usulü ile manuel olarak devreye girerek, sürecin şekillenmesi ve bir takım aksiyonların alınması noktasında önemli rol üstlenirler. Biz geliştiricilerde, iş akışı mantığına dayalı sistemleri tasarladığımız durumlarda bu kural kümelerinin esnekliklerine sahip olmak isteriz. Bu Biztalk, Sharepoint, TIBCO vb iş akışı modellerini içeren gelişmiş ürünlerde çoğu zaman karşımıza çıkmaktadır.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=1f62e593-fe0a-4cc8-94ed-0df571c063461https://buraksenyurt.com/trackback.axd?id=1f62e593-fe0a-4cc8-94ed-0df571c06346https://buraksenyurt.com/post/WF-Rule-Engine-How-To#commenthttps://buraksenyurt.com/syndication.axd?post=1f62e593-fe0a-4cc8-94ed-0df571c06346https://buraksenyurt.com/post/Custom-Activity-Designer-GelistirmekCustom Activity Designer Geliştirmek2012-06-12T07:00:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2012%2f6%2fChallenge.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>İnsanın kendisini en çok geliştireceği yer gerçek çalışma sahaları/ortamlarıdır. Ortaya konan ihtiyaçlar ne zaman ki sizin kullanmakta olduğunuz araçların(Tools) sınırlarını zorlamaya başlar, bu noktadan itibaren içerisine gireceğiniz her çeşit mücadele size inanılmaz derece tecrübe ve bilgi katacaktır. Tabi bu know-how bilgisini saklayabilir, kendiniz için dökümante edebilir veya kuralları çerçevesinde paylaşabilirsiniz <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> </p>
<p>Geçtiğimiz günlerde <strong>Workflow Foundation</strong> tarafında bir <strong>Component Set'</strong> in geliştirilmesi üzerine açılan <strong>POC(Proof of Concept)</strong> projesinde görev aldım. Bu anlamda yoğun bir şekilde<strong> Custom Activity Designer</strong> konusu ile yakın ilişki içerisinde yer almam gerekti. <strong>Workflow Foundation'</strong> ın bileşen seti her ne kadar geniş bir yelpazeye sahip olsa da, özellikle uygulama geliştiricilerin hızlı bir şekilde Workflow<em>(Flow Chart, Sequential vb)</em> tasarlaması gerektiği durumlarda, işleri kolaylaştıracak <strong>Component</strong> setlerinin üretilmesi son derece önemlidir. Ne varki <strong>XAML</strong> tabanlı çalışan <strong>Activity Designer</strong> örnekleri, <strong>Visual Studio IDE'</strong> si ile pek kardeşçe yaşamamaktadır<em>(Bu durumun Visual Studio 2012' de devam etmediğini umuyorum)</em>. Dikkat edilmesi gereken pek çok nokta ve ip ucu bulunmakta. Dilerseniz ne demek istediğimi örnek bir senaryo üzerinden görmeye çalışalım. </p>
<p>Senaryomuzda metod adlarını ve bu fonksiyonlara bağlı parametre listelerini gösteren basit bir <strong>Workflow Activity</strong> bileşenini tasarlamaya çalışıyor olacağız. Bileşenimiz standart bir<strong> Code/Native Activity’</strong> den farklı olarak görsel arayüze sahip olacak ve <strong>Visual Studio IDE’</strong> si içerisinden de kullanılabilecek. Bir başka deyişle <strong>ToolBox</strong> sekmesinden <strong>designer</strong> ortamına sürükleyip bıraktığımızda, <strong>IDE</strong> kullanıcısı ile etkileşim içerisinde olacak. Dolayısıyla <strong>Activity</strong> <strong>Designer</strong> tipini ele alacağımız bir örnek üzerinde çalışıyor olacağız. İlk olarak projelerimizi oluşturarak işe başlayalım. Bu anlamda <strong>Solution</strong> içeriğini aşağıdaki şekilde görüldüğü gibi tasarlayabiliriz.</p>
<p><img src="/pics/2012%2f6%2fwda_1.png" alt="" /></p>
<p><strong>Solution</strong> yapısı oldukça önemlidir. <strong>Activity</strong> projesi <strong>NativeActivity</strong> türevli tipleri barındırıyor iken, <strong>Design</strong> kütüphanesinde sadece görsel tasarımlar yer alacaktır. <strong>Azon.Workflow.Activity</strong> projesi <strong>Activity Library</strong> tipinden iken <strong>Azon.Workflow.Activity.Design</strong>, <strong>Activity Designer Library</strong> tipindendir. Burada <strong>Visual Studio 2010</strong> <strong>IDE’</strong> sinin beklediği bir isimlendirme standartı bulunmaktadır. Buna göre, <strong>Component’</strong> in görsel arayüzünün tasarlanacağı kütüphane adının mutlaka <strong>Design</strong> kelimesi ile bitmesi gerekmektedir<em>(Bu bilgiyi bulmak oldukça fazla vakit kaybına neden oldu. Ben en başından söylemek istiyorum <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> )</em></p>
<p>Yolumuza <strong>Native</strong> <strong>Activity</strong> bileşenimizi geliştirerek devam edelim. <strong>Azon.Workflow.Activity</strong> kütüphanesi içerisinde aşağıdaki sınıf diagramında görülen tipleri üretiyor olacağız. Senaryomuza göre bileşenimiz, kaynak bir listede yer alan metod adlarını ve bunlara ait parametreleri gösteriyor olacak. İlk hedefimiz bu.</p>
<p><img src="/pics/2012%2f6%2fwda_2.png" alt="" /></p>
<p>Şimdi tiplerimiz içeriklerini biraz değerlendirelim.</p>
<p><strong>InstanceMethodActivity.cs</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Activities;
namespace Azon.Workflow.Activity
{
public sealed class InstanceMethodActivity
:NativeActivity<object>
{
public InArgument<string> Description { get; set; }
public string MethodName { get; set; }
protected override void Execute(NativeActivityContext context)
{
//TODO@Burak burada bir takım kodlar işletilir
}
}
}</pre>
<p><strong>InstanceMethodActivity</strong>, <strong>türetilemeyen(Sealed)</strong> ve <strong>NativeActivity<object></strong> türevli bir tiptir. <strong>CodeActivity</strong> türevli tiplere benzer olarak, çalışma zamanındaki işlerini <strong>Execute</strong> metodu içerisinde icra etmektedir. Örneğimizde söz konusu <strong>Activity</strong> bileşeni için herhangibir <strong>Runtime</strong> işlemi uygulatmıyor olacağız. Asıl hedefimiz <strong>Visual Studio 2010 IDE'</strong> sinde <strong>Design</strong> <strong>Time</strong> <strong>Support'</strong> unu sağlayabilmektir. Tipimizin içerisinde <strong>InArgument<string></strong> tipinden <strong>Description</strong> ve <strong>string </strong>türünden <strong>MethodName</strong> isimli iki <strong>özellik(Property)</strong> yer almaktadır. <strong>Designer</strong> tarafında işimize yarayacak olan sınıflar ise <strong>InstanceMethod, InstanceMethodParameter, ParameterType(Enum sabiti)</strong> ve <strong>InstanceMethodList'</strong> tir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Azon.Workflow.Activity
{
using System.Collections.Generic;
public class InstanceMethod
{
public string Name { get; set; }
public List<InstanceMethodParameter> Parameters{ get; set; }
}
}</pre>
<p><strong>InstanceMethod</strong>, aslında bir metodun adını ve parametrik yapısını taşımak üzere tasarlanmış bir <strong>POCO(Plain Old Clr Object)</strong> tipidir. <strong>Parameters</strong> özelliği <strong>InstanceMethodParameter</strong> tipinden <strong>generic</strong> bir <strong>List</strong> koleksiyonudur ve ilgili sınıfın içeriği de aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Azon.Workflow.Activity
{
public class InstanceMethodParameter
{
public string Name { get; set; }
public string DotNetType { get; set; }
public ParameterType ParameterType{ get; set; }
}
}</pre>
<p>Bu tip içerisinde ise sembolik olarak metod parametrelerine ait çeşitli bilgiler yer almaktadır. Örneğin parametrenin adı, <strong>.Net Framework Common Type System</strong> deki karşılığı gibi. <strong>ParameterType</strong> <strong>enum</strong> sabiti ile de ilgili parametrenin ne çeşitte olduğu belirtilmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Azon.Workflow.Activity
{
public enum ParameterType
{
Ref,
Out,
Standart,
Return,
Params
}
}</pre>
<p>Bu kütüphane içerisindeki en önemli tip ise <strong>ObservableCollection<InstanceMethod></strong> türevli olan <strong>InstanceMethodList'</strong> dir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Azon.Workflow.Activity
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class InstanceMethodList
:ObservableCollection<InstanceMethod>
{
public InstanceMethodList()
{
Add(new InstanceMethod
{
Name = "Sum",
Parameters = new List<InstanceMethodParameter>{
new InstanceMethodParameter{ Name="X", DotNetType="System.Int32", ParameterType= ParameterType.Standart},
new InstanceMethodParameter{ Name="Y", DotNetType="System.Int32", ParameterType= ParameterType.Standart},
new InstanceMethodParameter{ Name="Result", DotNetType="System.Int32", ParameterType= ParameterType.Return}
}
});
Add(new InstanceMethod
{
Name = "TotalSum",
Parameters = new List<InstanceMethodParameter>{
new InstanceMethodParameter{ Name="Values", DotNetType="System.Int32[]", ParameterType= ParameterType.Params},
new InstanceMethodParameter{ Name="Result", DotNetType="System.Int32", ParameterType= ParameterType.Return}
}
});
Add(new InstanceMethod
{
Name = "CallSp",
Parameters = new List<InstanceMethodParameter>{
new InstanceMethodParameter{ Name="SpName", DotNetType="System.String", ParameterType= ParameterType.Standart},
new InstanceMethodParameter{ Name="ResultSet", DotNetType="System.Data.DataTable", ParameterType= ParameterType.Ref}
}
});
}
}
}</pre>
<p>Bu tip aslında <strong>Activity Designer'</strong> ın <strong>XAML</strong> tabanlı içeriğinde ele alacağımız <strong>Data Binding</strong> işlemleri için kullanılmaktadır. <strong>ObservableCollection</strong> türevli olmasının sebebi de budur. Amacımız tipin kendisini <strong>ComboBox</strong> ve <strong>DataGrid</strong> kontrollerine bağlamaktır. <strong>Yapıcı(Constructor)</strong> metod içerisinde, örnek metod bilgilerinin eklendiği görülmektedir. Elbetteki bir gerçek hayat senaryosunda ilgili içeriklerin farklı veri ortamlarından tedarik edilmesi de düşünülebilir. Örneğin bu bilgileri bir servis üzerinden veya doğrudan erişilebilen ve <strong>InProc</strong> modda kullanabildiğimiz bir <strong>Assembly</strong> içerisinden de getirtebiliriz.</p>
<p>Gelelim bileşenimizin arayüzünü tasarlayacağımız <strong>Activity Designer</strong> öğesine.<strong> Azon.Workflow.Activity.Design</strong> kütüphanesinde oluşturacağımız <strong>InstanceMethodActivityDesigner.xaml</strong> tipinin içeriğini aşağıdaki gibi tasarlayabiliriz.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><sap:ActivityDesigner x:Class="Azon.Workflow.Activity.Design.InstanceMethodActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
xmlns:Model="clr-namespace:System.Activities.Presentation.Model;assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
xmlns:activity="clr-namespace:Azon.Workflow.Activity;assembly=Azon.Workflow.Activity"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<sap:ActivityDesigner.Resources>
<ResourceDictionary x:Uid="ResourceDictionary_0">
<sapc:ModelToObjectValueConverter x:Key="ModelToObjectValueConverter" />
<ObjectDataProvider x:Key="dsInstanceMethods" ObjectType="{x:Type activity:InstanceMethodList}">
</ObjectDataProvider>
<DataTemplate x:Key="Collapsed">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="5" Text="Method Caller" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Expanded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Metodlar" Grid.Row="0"/>
<ComboBox x:Name="cmbInstanceMethods" Grid.Row="1" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" />
<TextBlock Text="Metod Parametreleri" Grid.Row="2"/>
<DataGrid x:Name="grdMethodParameters" Grid.Row="3" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}, Path=Parameters}"/>
</Grid>
</DataTemplate>
<Style x:Key="ExpandOrCollapsedStyle" TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate" Value="{DynamicResource Expanded}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowExpanded}" Value="true">
<Setter Property="ContentTemplate" Value="{DynamicResource Collapsed}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<Grid>
<ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}" Content="{Binding}" />
</Grid>
</sap:ActivityDesigner></pre>
<p>Vuuuuu!!! <img title="Sealed" src="/editors/tiny_mce3/plugins/emotions/img/smiley-sealed.gif" alt="Sealed" border="0" /> Biraz korkutucu bir içerik gibi görünebilir. Ama korkmayın. Tek tek açıklamaya çalışalım.</p>
<p>Herşeyden önce bileşenimiz içerisinde bazı <strong>Static Resource'</strong> lar tanımlandığı görülmektedir. <strong>dsInstanceMethods</strong> isimli <strong>ObjectDataProvider</strong>, <strong>InstanceMethodList</strong> isimli sınıfa bağlanmaktadır. Dolayısıyla <strong>XAML</strong> içerisinde yer alan bileşenler bu veri kaynağına bağlanıp <strong>InstanceMethod</strong> nesne örnekleri ile etkileşimde bulunabilirler. Örneğin <strong>cmbInstanceMethods</strong> isimli <strong>ComboBox</strong> kontrolü, <strong>ItemsSource</strong> özelliğine <strong>static</strong> bir veri kaynağı olarak bu <strong>ObjectDataProvider</strong> örneğini bağlamıştır. <strong>DisplayMemberPath</strong> özelliğine atanan <strong>Name</strong> değeri ise, <strong>InstanceMethod </strong> örnekleri içerisindeki <strong>Name</strong> özelliğini işaret etmekte olup <strong>ComboBox'</strong> un üzerinde nelerin gösterileceğini belirtmektedir. <strong>ComboBox</strong> üzerinde hareket edildikçe alt tarafta yer alan <strong>grdMethodParameters</strong> isimli <strong>DataGrid</strong> içeriğininde, ilgili metoda ait parametre listesi ile doldurulması beklenmektedir. Bu nedenle her iki bileşenin <strong>IsSychnronizedWithCurrentItem</strong> özelliği <strong>true</strong> değerine sahiptir. <strong>DataGrid</strong> bileşeninin <strong>ItemsSource</strong> özelliği de <strong>static</strong> veri kaynağı olan <strong>dsInstanceMethods'</strong> a bağlanmıştır. Ama!</p>
<p><strong>Path</strong> özelliğinin değerine dikkat edelim. <strong>Parameters</strong> değeri aslında <strong>InstanceMethodList</strong> sınıfındaki özelliğin adıdır. Dolayısıyla <strong>ComboBox</strong> kontrolünde bir öğe seçildiğinde, buna bağlı <strong>Parameters</strong> özelliğinin karşılığı olan liste, <strong>DataGrid</strong> içerisine basılıyor olacaktır. Görüldüğü üzere tipik olarak bir <strong>WPF Data Binding</strong> işlevselliği söz konusudur. Bunun dışında kalan kısımlarda bileşenin <strong>Collapse</strong> veya <strong>Expand</strong> edilmesi hallerinde nasıl görüneceği ifade edilmiştir. Dikkat edilecek olursa iki adet <strong>DataTemplate</strong> elementi vardır. Bunlardan birisi <strong>Collapsed</strong> diğer ise <strong>Expanded</strong> olarak isimlendirilmiştir. Son satırlarda yer alan <strong>Grid</strong> elementi içerisindeki <strong>ContentPresenter'</strong> da buna uygun olacak şekilde bileşenin <strong>Collapsed</strong> veya <strong>Expanded</strong> olarak <strong>designer</strong> üzerinde gösterilebilmesini sağlamaktadır<em>(Ne varki ben Collapsed hale bir türlü getirmeyi başaramadım. Yani örneğimizde şimdiden bir Bug' ımız olduğunu ifade etmek isterim <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> )</em></p>
<p>Bileşenimizin <strong>XAML</strong> içeriğini bu şekilde oluşturmak yeterli değildir. Ayrıca<strong> Visual Studio Designer'</strong> ına söz konusu bileşeni bildirmemiz gerekmektedir. Bunun için ilk olarak <strong>Activity Designer</strong> sınıfının koda tarafını aşağıdaki hale getirmeliyiz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Activities.Presentation.Metadata;
using System.ComponentModel;
namespace Azon.Workflow.Activity.Design
{
public partial class InstanceMethodActivityDesigner
{
#region Constructors and Destructors
public InstanceMethodActivityDesigner()
{
this.InitializeComponent();
}
#endregion
#region Public Methods
public static void RegisterMetadata(AttributeTableBuilder builder)
{
builder.AddCustomAttributes(
typeof(InstanceMethodActivity),
new DesignerAttribute(typeof(InstanceMethodActivityDesigner)),
new DescriptionAttribute("Instance Method Activity"));
}
#endregion
}
}</pre>
<p>Sınıf içerisindeki en önemli metod <strong>RegisterMetadata</strong> isimli <strong>static</strong> fonksiyondur. Bu metod içerisinde parametre olarak gelen <strong>Attribute</strong> tablosuna bazı bildirimlerde bulunularak yeni <strong>niteliklerin(Attribute)</strong> ilave edilmesi sağlanmaktadır. Hatta dilerseniz burada <strong>Component</strong> için bir <strong>Icon(16X16 boyutlarında bir PNG olabilir)</strong> dahi belirtebilirsiniz. Biz şimdilik bu detayı atlıyor olacağız.</p>
<p>Peki söz konusu <strong>static</strong> metod nerede çağırılacaktır? <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> Bunun için <strong>Azon.Workflow.Activity.Design</strong> kütüphanesine <strong>IRegisterMetadata</strong> arayüzünü implemente eden bir sınıfın eklenmesi gerekmektedir. <strong>IRegisterMetadata</strong> arayüzünden gelen <strong>Register</strong> metodu içerisinde ise, <strong>InstanceMethodActivityDesigner</strong> sınıfına dahil edilmiş olan <strong>RegisterMetadata</strong> isimli <strong>static</strong> metod çağrısı gerçekleştirilmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Activities.Presentation.Metadata;
namespace Azon.Workflow.Activity.Design
{
public sealed class ActivityLibraryMetadata
: IRegisterMetadata
{
public void Register()
{
RegisterAll();
}
public static void RegisterAll()
{
var builder = new AttributeTableBuilder();
InstanceMethodActivityDesigner.RegisterMetadata(builder);
MetadataStore.AddAttributeTable(builder.CreateTable());
}
}
}</pre>
<p>Burada kullanılan sınıfın adının çok önemi yoktur. Nitekim <strong>Visual Studio IDE'</strong> si, kendi çalışma zamanı ortamında, ilgili <strong>Activity Designer</strong> kütüphanesinde <strong>IRegisterMetadata</strong> arayüzünü uygulamış olan bir tipe bakmaktadır. Tipik bir <strong>Plug-In</strong> tasarım mantığı olduğunu rahatlıkla ifade edebiliriz.</p>
<p>Artık bileşenimizi deneyebiliriz demek isterdim ama son olarak yapmamız gereken ufak bir işlem daha var. Adı <strong>Design</strong> kelimesi ile biten kütüphanenin <strong>dll</strong> çıktısının, <strong>NativeActivity</strong> bileşenlerini içeren kütüphanenin olduğu yere doğru yapılması gerekmektedir. Aşağıdaki şekilde görüldüğü gibi.</p>
<p><img src="/pics/2012%2f6%2fwda_3.png" alt="" /></p>
<p>Artık basit bir <strong>Workflow</strong> üzerinden bileşenimizi deneyebiliriz. Bileşenimiz otomatik olarak <strong>Toolbox</strong> sekmesinde görünecektir. İşte <strong>Visual Studio 2010</strong> çalışma ortamına ait bir kaç örnek görüntü.</p>
<p><strong>Sum metodu seçildiğinde</strong></p>
<p><img src="/pics/2012%2f6%2fwda_4.png" alt="" /></p>
<p><strong>CallSp metodu seçildiğinde</strong></p>
<p><img src="/pics/2012%2f6%2fwda_5.png" alt="" /></p>
<p>Görüldüğü üzere bileşenimiz içerisinde <strong>Data Binding</strong> tekniklerini de kullanarak bir etkileşim gerçekleştirmeyi başardık. Şimdi bileşenimizi biraz daha geliştirmeyi deniyor olacağız. Buna göre <strong>ComboBox</strong> kontrolünde bir öğe seçildiğinde, <strong>Name</strong> alanının değerinin, o anki <strong>InstanceMethodActivity'</strong> ye ait <strong>Property'</strong> lerden <strong>MethodName</strong> alanında gösterilmesini sağlamaya çalışacağız. Bu bonus senaryoda işi zorlaştıran kısım şu;</p>
<p><strong>ComboBox </strong>bileşeni içerisinde <strong>Binding</strong> sebebi ile <strong>InstanceMethodList</strong> sınıfına ait değerler taşınmaktadır. Bu değerler <strong>InstanceMethod</strong> türünden nesne örnekleridir aslında. <strong>InstanceMethodActivity</strong> bileşeninin, <strong>MethodName</strong> özelliği ise <strong>string</strong> tipindendir. Dolayısıyla <strong>ComboBox</strong> kontrolünün <strong>SelectedValue</strong> özelliği içerisinde <strong>XAML</strong> tarafında bildirilecek şekilde özel bir <strong>Convert</strong> işleminin uygulanması gerekmektedir. Bu amaçla öncelikli olarak bir <strong>Converter</strong> tipini <strong>Azon.Workflow.Activity</strong> kütüphanesine aşağıdaki gibi ilave edelim.</p>
<p><img src="/pics/2012%2f6%2fwda_6.png" alt="" /></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Azon.Workflow.Activity
{
using System;
using System.Globalization;
using System.Windows.Data;
public class InstanceMethodToMethodNameConverter
:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return null; //BURASI SİZE ÖDEV OLSUN
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
InstanceMethod instanceMethod = (InstanceMethod)value;
return instanceMethod.Name;
}
}
}</pre>
<p><strong>InstanceMethodToMethodNameConverter</strong> tipi, <strong>System.Windows.Data</strong> isim alanında<em>(ki PresentationFramework.dll assembly' ının projede referans edilmesi gerekmektedir)</em> yer alan <strong>IValueConverter</strong> <strong>arayüzünü(Inteface)</strong> uygulamaktadır. Buna göre <strong>TwoWay</strong> <strong>Binding'</strong> i destekleyecek şekilde <strong>Convert</strong> ve <strong>ConvertBack</strong> metodlarının implemantasyonunu istemektedir. <strong>Convert</strong> metodu, <strong>Properties</strong> penceresinden girilen değere göre <strong>ComboBox</strong> içerisinde ilgili öğeye gidilmesini sağlamaktadır. <strong>ConvertBack</strong> metodu ise tam tersi işlevi üstlenmekte olup, <strong>ComboBox'</strong> ta seçilen <strong>InstanceMethod</strong> nesne örneğinin <strong>Name</strong> özelliğinin değerini <strong>Properties</strong> penceresindeki <strong>MethodName</strong> alanına basmaktadır. Tabi söz konusu tipin yazılması yeterli değildir. Bu <strong>Converter</strong> tipinin <strong>XAML</strong> tarafında da dekleratif olarak bildirilmesi ve <strong>ComboBox</strong> bileşeni ile ilişkilendirilmesi gerekmektedir. Bunun için <strong>InstanceMethodActivityDesigner.xaml</strong> içeriğini aşağıdaki gibi güncellememiz yeterli olacaktır.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><sap:ActivityDesigner x:Class="Azon.Workflow.Activity.Design.InstanceMethodActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
xmlns:Model="clr-namespace:System.Activities.Presentation.Model;assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
xmlns:activity="clr-namespace:Azon.Workflow.Activity;assembly=Azon.Workflow.Activity"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<sap:ActivityDesigner.Resources>
<ResourceDictionary x:Uid="ResourceDictionary_0">
<sapc:ModelToObjectValueConverter x:Key="ModelToObjectValueConverter" />
<activity:InstanceMethodToMethodNameConverter x:Key="MethodToMethodNameConverter"/>
<ObjectDataProvider x:Key="dsInstanceMethods" ObjectType="{x:Type activity:InstanceMethodList}">
</ObjectDataProvider>
<DataTemplate x:Key="Collapsed">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="5" Text="Method Caller" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Expanded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Metodlar" Grid.Row="0"/>
<ComboBox x:Name="cmbInstanceMethods" Grid.Row="1" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding Path=ModelItem.MethodName, Mode=TwoWay, Converter={StaticResource MethodToMethodNameConverter}}" />
<TextBlock Text="Metod Parametreleri" Grid.Row="2"/>
<DataGrid x:Name="grdMethodParameters" Grid.Row="3" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}, Path=Parameters}"/>
</Grid>
</DataTemplate>
<Style x:Key="ExpandOrCollapsedStyle" TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate" Value="{DynamicResource Expanded}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowExpanded}" Value="true">
<Setter Property="ContentTemplate" Value="{DynamicResource Collapsed}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<Grid>
<ContentPresenter Style="{DynamicResource ExpandOrCollapsedStyle}" Content="{Binding}" />
</Grid>
</sap:ActivityDesigner></pre>
<p>Buna göre <strong>ComboBox</strong> kontrolünde bir <strong>Metod</strong> adı seçilirse bu <strong>Properties</strong> penceresine de bu isim yansıyacaktır. Böylece <strong>developer'</strong> ın işi biraz daha kolaylaştırılmış olmaktadır.</p>
<p><img src="/pics/2012%2f6%2fwda_7.png" alt="" /></p>
<p>Şimdi olayı biraz daha renklendireceğiz. Örneğin <strong>Visual Studio Designer'</strong> ı üzerinde çalışırken, <strong>Activity</strong> bileşenlerine ait <strong>event</strong> methodları kullanmak istediğinizi ve hatta bu <strong>event</strong> metodlar içerisinde, diğer kontrollerin içeriklerine ulaşmak istediğimizi düşünelim. Bu senaryoyu irdelemek için <strong>InstanceMethodActivityDesigner.xaml</strong> içeriğine bir <strong>Button</strong> kontrolü ekleyerek ilerleyebiliriz.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><DataTemplate x:Key="Expanded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Metodlar" Grid.Row="0"/>
<ComboBox x:Name="cmbInstanceMethods" Grid.Row="1" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding Path=ModelItem.MethodName, Mode=TwoWay, Converter={StaticResource MethodToMethodNameConverter}}" />
<TextBlock Text="Metod Parametreleri" Grid.Row="2"/>
<DataGrid x:Name="grdMethodParameters" Grid.Row="3" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource dsInstanceMethods}, Path=Parameters}"/>
<Button x:Name="btnCatchParameters" Click="btnCatchParameters_Click" Content="Parametreleri Çek" Grid.Row="4">
</Button>
</Grid>
</DataTemplate></pre>
<p><strong>btnCatchParameters</strong> isimli <strong>Button</strong> kontrolünün <strong>Click</strong> olay metodunun yüklendiği görülmektedir. Bu olay metodu çok doğal olarak <strong>InstanceMethodActivityDesigner.cs</strong> içerisine açılıyor olacaktır. Olay metodu içeriğini aşağıdaki kod parçasında görüldüğü gibi geliştirebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void btnCatchParameters_Click(object sender, System.Windows.RoutedEventArgs e)
{
Grid parent=((Grid)((Button)e.Source).Parent);
foreach (UIElement control in parent.Children)
{
DataGrid grid=control as DataGrid;
if(grid!=null)
{
StringBuilder builder = new StringBuilder();
foreach (var item in grid.ItemsSource)
{
builder.AppendLine(item.ToString());
}
MessageBox.Show(builder.ToString(),"Parametreler");
}
}
}</pre>
<p>Olay metodu içerisindeki felsefe oldukça basittir. <strong>Button</strong> kontrolü aslında bir <strong>Grid</strong> içerisinde yer almaktadır ve <strong>DataGrid</strong> bileşeni de aynı <strong>seviyede(Level)</strong> durmakta olan bir elementtir. Dolayısıyla <strong>Button</strong> bileşeninin <strong>Parent </strong>elementine(<strong>Container </strong>da diyebiliriz) çıkıp, tüm alt kontrolleri dolaşabilir ve <strong>Grid </strong>tipinde olana vardığımızda da <strong>ItemsSource </strong>özelliğine ait koleksiyon içeriğini ele alabiliriz. Kulağımızı farklı bir şekilde tuttuğumuzu ifade edebiliriz aslında ama şu anda elimizden en iyi çözüm bu. Böylece <strong>Visual Studio Designer</strong>' ı içerisindeyken, <strong>DataGrid</strong> elementlerine ve seçili olan metodun parametre listesine ulaşmamız mümkün olacaktır. Aynen aşağıdaki şekilde görüldüğü gibi.</p>
<p><img src="/pics/2012%2f6%2fwda_8.png" alt="" /></p>
<p>Görüldüğü üzere <strong>Custom Activity</strong> geliştirmek kolay olsa da, bu bileşeni <strong>Designer</strong> desteğine sahip olacak şekilde genişletmek bir kaç ipucu içeren ve dikkat edilmesi gereken bir süreci gerektirmektedir. Geliştirmiş olduğumuz örnekte bazı eksik kısımlar da bulunmaktadır. Örneğin <strong>XAML</strong> tarafında dekleratif olarak <strong>Event</strong> bazlı etkileşimler çok fazla ele alınmamıştır.<em>(Bir veritabanı bağlantısını seçtiren ve hatta <strong>design</strong> tarafında bir <strong>SQL</strong> sorgusunu çalıştırtıp sonuçları bir <strong>DataGrid</strong> kontrolüne basan bir <strong>Activity</strong> <strong>Designer</strong> yazmaya çalıştığınızı hayal edin. Üstelik Connection' ı tanımladığınızda Test' de edebilmelisiniz vs <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> ) </em>Bu konuda detaylı ve derinlemesine araştırmalarıma devam ediyorum. Yeni bilgiler edindikçe sizinle paylaşmaya gayret ediyor olacağım. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/pics/2012%2f6%2fWritingDesignerActivityV2.zip">WritingDesignerActivityV2.zip (155,91 kb)</a></p>2012-06-12T07:00:00+00:00workflow foundationactivity designernative activitycustom workflow componentxamlxaml data bindingivalueconverterobservablecollectionvisual studio 2010 workflow designerbsenyurtGeçtiğimiz günlerde Workflow Foundation tarafında bir Component Set' in geliştirilmesi üzerine açılan POC(Proof of Concept) projesinde görev aldım. Bu anlamda yoğun bir şekilde Custom Activity Designer konusu ile yakın ilişki içerisinde yer almam gerekti. Workflow Foundation' ın bileşen seti her ne kadar geniş bir yelpazeye sahip olsa da, özellikle uygulama geliştiricilerin hızlı bir şekilde Workflow(Flow Chart, Sequential vb) tasarlaması gerektiği durumlarda, işleri kolaylaştıracak Component setlerinin üretilmesi son derece önemlidir. Ne varki XAML tabanlı çalışan Activity Designer örnekleri, Visual Studio IDE' si ile pek kardeşçe yaşamamaktadır(Bu durumun Visual Studio 2012' de devam etmediğini umuyorum). Dikkat edilmesi gereken pek çok nokta ve ip ucu bulunmakta. Dilerseniz ne demek istediğimi örnek bir senaryo üzerinden görmeye çalışalım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=fe49fdcd-d7e7-42e8-96ab-a6b07914d4002https://buraksenyurt.com/trackback.axd?id=fe49fdcd-d7e7-42e8-96ab-a6b07914d400https://buraksenyurt.com/post/Custom-Activity-Designer-Gelistirmek#commenthttps://buraksenyurt.com/syndication.axd?post=fe49fdcd-d7e7-42e8-96ab-a6b07914d400https://buraksenyurt.com/post/Tek-Fotoluk-Ipucu-15Tek Fotoluk İpucu - 15(Self Hosted Workflow Service)2011-07-07T08:05:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p>Elinizde bir Workflow Service kütüphanesi ve XAMLX uzantılı Workflow Service dosyaları var. Bu dosyalardan yararlanarak kendi Workflow Service Host uygulamanızı yazmak niyetindesiniz. Diyelim ki bu uygulama bir Console projesi olacak. Nasıl yaparsınız? İşte böyle <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" src="http://www.buraksenyurt.com/pics/wlEmoticon-winkingsmile_33.png" alt="Winking smile" /></p>
<p><a href="http://www.buraksenyurt.com/pics/PhotoTrick15.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 4px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="PhotoTrick15" src="http://www.buraksenyurt.com/pics/PhotoTrick15_thumb.png" border="0" alt="PhotoTrick15" width="602" height="502" /></a></p>
<p><a href="http://www.buraksenyurt.com/pics/2011%2f7%2fProductsWorkflowHost.rar">ProductsWorkflowHost.rar (38,48 kb)</a></p>2011-07-07T08:05:00+00:00wcfwfworkflow serviceswindows workflow foundationwindows communication foundationbsenyurthttps://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=8b2adca8-a31d-4936-9a07-371dbb3326cd0https://buraksenyurt.com/trackback.axd?id=8b2adca8-a31d-4936-9a07-371dbb3326cdhttps://buraksenyurt.com/post/Tek-Fotoluk-Ipucu-15#commenthttps://buraksenyurt.com/syndication.axd?post=8b2adca8-a31d-4936-9a07-371dbb3326cdhttps://buraksenyurt.com/post/Kod-Bazli-Workflow-Service-Gelistirmek-YayinlamakKod Bazlı Workflow Service Geliştirmek ve Yayınlamak2010-10-04T16:20:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2010%2f5%2fblg193_Giris.jpg" alt="" width="250" height="264" />Merhaba Arkadaşlar,</p>
<p>Bildiğiniz üzere bir süredir <strong><a title="NedirTv?com" href="http://www.nedirtv.com" target="_blank">NedirTv?com</a></strong> desteğinde <a title="Workflow Foundation Öğreniyorum" href="http://www.nedirtv.com/kategori/Workflow-Foundation-Ogreniyorum.aspx" target="_blank"><strong>"Workflow Foundation 4.0 Öğreniyorum"</strong></a> isimli bir seri üzerinde çalışmaktayız. Bu seride başlangıç seviyesinden orta seviyeye kadar, bir kaç ayrı derste <strong>Workflow Foundation </strong>kavramını öğrenmeye gayret ettik. Bu seriye dahil etmek istediğim bir konu da, <strong>Workflow Service</strong> örneklerinin tamamen kod bazında yazılması ve <strong>IIS(Internet Information Services)</strong> dışındaki bir uygulama tarafından host edilmesiydi. Ancak konu biraz karmaşık olduğundan ve tabiri yerinde ise yandaki resimde görülen <strong>Puzzle</strong>' a <span style="text-decoration: underline;">benzemediğinden</span>, yazı haline getirilmesinin daha iyi olacağına karar verdim. Hem böylece ben de unuttuğum zamanlarda bu yazıma bakarak hatırlayabilirim. Öyleyse derin bir nefes alalım ve yola koyulalım.</p>
<p>İlk olarak ne yapmak istediğimizi açık ve net bir şekilde ortaya koymaya çalışalım. <strong>WCF Eco System</strong> yapısının da önemli bir parçası olan<strong> Workflow Service</strong>' ler yardımıyla iş akışlarının servis bazlı olarak dış ortama sunulması mümkündür. Bu noktada özellikle<strong> Visual Studio 2010 </strong>tarafında yer alan <strong>WCF Workflow Service Application</strong> şablonu ve <strong>Workflow Designer</strong> işlerimizi inanılmaz ölçüde kolaylaştırmaktadır. Ancak elimizin altında sadece <strong>.Net Framework 4.0</strong> olduğunu düşünelim. Hımmm...<img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Bu durumda<strong> Visual Studio 2010</strong> gibi bir <strong>IDE</strong>' mizin olmadığını da varsayarsak bir <strong>Workflow Service </strong>örneğinin <strong>XAML </strong>içeriğini yazmak istemeyebiliriz. Dolayısıyla kod tarafında <strong>.Net</strong> tiplerinden yararlanarak ilerlemek daha kolay olabilir<em>(Gerçi biz örneğimizdeki kod parçasını Visual Studio 2010 ile yazıyoruz ama olsun. Kimseye çaktırmayın)</em></p>
<p>Ne yapmak istediğimizi sanıyorum ki biraz daha net anlayabildik. En basit haliyle bir <strong>Workflow Service </strong>örneğini kod tarafında oluşturmak istiyoruz. Ancak bu yeterli değil. Nitekim tasarlanan bu <strong>Workflow Service</strong> örneğinin aynı zamanda <strong>host </strong>edilerek kullanıma sunulması da gerekmektedir. İşte bu noktada<strong> IIS(Internet Information Services) </strong>dışında bir uygulamayı geliştirmek istediğimizi düşünebiliriz. İlk etapta basit bir Console uygulaması işimizi görebilir. <em>(Ancak tabiki WPF, Windows Forms hatta bir Asp.Net uygulamas dahi Workflow Service örneklerini host edip çalıştıracak şekilde tesis edilebilir) </em>Bu Console uygulaması, <strong>WorkflowServiceHost </strong>tipinden de yararlanarak gerekli çalışma zamanını tesis edecek ve bildirilen <strong>Workflow Service</strong> örneğini dış ortama sunuyor olacaktır.</p>
<p>İşe başlamadan önce eğer imkanınız var ise bir <strong>Workflow Service</strong> örneğinin<strong> Visual Studio 2010</strong> ortamında WPF tabanlı Designer yardımıyla geliştirilmesini incelemenizi öneririm. Geliştireceğimiz <strong>Workflow Service </strong>bir servis olduğundan istemciden gelecek olan çağrıları kabul etmeli ve bir iş akışı başlatarak sonuçları istemci tarafına yönlendirmelidir. <strong>WCF </strong>tabanlı bir servis söz konusu olduğuna göre istemcinin çağrıda bulunabileceği operasyonların ve dolayısıyla <strong>servis sözleşmesinin(Service Contract)</strong> önceden tanımlanmış olması gerekmektedir. Ki bu sayede istemcilerin söz konusu adresten yayınlanan servis üzerinden hangi operasyonları çağırabileceği belirlenmiş olacaktır. İşte örneğimizde kullanacağımız servis sözleşmesi.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">[ServiceContract]
public interface IHelloService
{
[OperationContract]
double Sum(double x, double y);
}</pre>
<p><strong>IHelloService interface </strong>tipi dikkat edileceği üzere <strong>ServiceContract </strong>niteliği ile imzalanmıştır. Diğer taraftan çok basit olarak <strong>Sum </strong>isimli bir operasyon içermektedir. Söz konusu operasyon istemci tarafına açılacak olan bir fonksiyonelliği belirtmektedir. <strong>Sum </strong>isimli operayonun aldığı <strong>double </strong>tipinden olan iki parametrede, istemci tarafından gönderilecek değişkenler olduğunu göstermektedir.</p>
<p>Peki <strong>Workflow Service</strong> örnekleri dış dünyadan gelen istemci taleplerini nasıl almaktadır? Diyelim ki aldılar ve işettiler. İş akışı sonucu istemci tarafına bir değer göndermek isterlerse bunu nasıl gerçekleştirebilirler? Bu noktada <strong>Receive </strong>ve <strong>SendReply </strong>isimli aktivite bileşenlerinden yararlanıldığını söyleyebiliriz. Dolayısıyla kod tarafında bu aktivite bileşenlerini ele almamız gerekmektedir. Lakin bu aktivite bileşenleri başka bir <strong>Container</strong> içerisinde yer almalıdır. Örneğin bir <strong>Sequence </strong>aktivite bileşeni Container olarak düşünülebilir. Çok doğal olarak istemcilerin gönderdiği parametrelerin <strong>Sequence </strong>içerisindeki diğer aktivite bileşenleri tarafından da kullanılması gerekebilir. Bu durumda <strong>Sequence </strong>seviyesinde <strong>Variable </strong>tanımlamalarının yapılması uygundur. İhtiyacımız olan <strong>Variable </strong>tanımlamaları ise aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">Variable<CorrelationHandle> __handle=new Variable<CorrelationHandle>("Request_Handle");
Variable<double> x=new Variable<double>("X");
Variable<double> y=new Variable<double>("Y");
Variable<double> result=new Variable<double>("Result");</pre>
<p><strong>x, y</strong> ve <strong>result </strong>isimli <strong>Variable </strong>tanımlamaları tahmin edeceğiniz üzere toplama işlemi için gereklidir. Ancak burada birde <strong>__handle </strong>isimli <strong>CorrelationHandle </strong>tipinden <strong>Variable </strong>tanımlaması yer almaktadır.<strong> Workflow Service</strong> örnekleri oluşturulduğunda bildiğiniz üzere istemci ile arada bir <strong>oturum(Session)</strong> oluşmaktadır. Bu noktada özellikle istemciden gelen mesajın sunucu tarafındaki hangi servis örneğine ait olduğunun anlaşılması noktasında <strong>Correlation </strong>çeşitlerinden yararlanılmaktadır. Burada tanımlanan <strong>Variable, Receive</strong> aktivite bileşeni tarafından kullanılacaktır (Detaylar için <a href="https://buraksenyurt.com/admin/post/Correlation-Nedir-Beta-2.aspx">Correlation Nedir? Yenir mi? İçilir mi?</a> )</p>
<p>Aslında tam bu noktada <strong>Receive </strong>aktivitesini de tanımlayabiliriz. Aşağıdaki gibi.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">Receive receive=new Receive{
CanCreateInstance=true,
OperationName="Sum",
ServiceContractName="IHelloService",
CorrelatesWith=__handle,
Content=ReceiveContent.Create(new Dictionary<string,OutArgument>{
{"xValue",new OutArgument<double>(x)},
{"yValue",new OutArgument<double>(y)},
}
)
};</pre>
<p><strong>Receive </strong>aktivite bileşeni içerisinde <strong>set </strong>edilmiş özellikler önemlidir. <strong>OperationName </strong>ile dikkat edileceği üzere istemciler için kullanılabilecek bir operasyon adı bildirimi yapılmaktadır. Diğer yandan <strong>Content </strong>özelliğine atanan <strong>OutArgument </strong>tipli iki parametre, az önce tanımladığımız <strong>x</strong> ve<strong> y Variable</strong>' larını kullanmaktadır. Bir başka deyişle istemciler için <strong>xValue </strong>ve <strong>yValue </strong>isimli değişkenler tanımlanmıştır. <strong>ServiceContractName </strong>özelliği ile servis sözleşmesi bildirimi yapılmaktadır. <strong>Receive </strong>aktivite bileşeni için gerekli <strong>Correlation </strong>ayarı ise <strong>CorrelatesWith </strong>özelliği ile belirtilmektedir. Aslında <strong>Receive </strong>aktivite bileşeni biraz sonra kodlayacağımız <strong>WorkflowService </strong>örneğinin <strong>Body </strong>özelliği içerisinde kullanılmak istenebilir. Ancak böyle bir durumda <strong>SendReply </strong>aktivite bileşeninin ihtiyacı olan <strong>Request </strong>özelliğinin atanacağı <strong>Receive </strong>referansı tanımlanamayacaktır. <strong>Receive </strong>aktivite bileşeninin dışarıda tanımlanmasının sebebi budur. Artık <strong>WorkflowService </strong>nesne örneğini tanımlayarak yolumuza devam edebiliriz. İşte kod içeriğimiz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">WorkflowService wfService = new WorkflowService
{
Name = "HelloService",
Endpoints = {
new Endpoint
{
ServiceContractName="IHelloService",
Binding=new BasicHttpBinding(),
AddressUri=new Uri("http://localhost:5001/HelloService")
, Name="HelloServiceEndpoint"
}
},
ConfigurationName="HelloServiceConfig",
Body = new Sequence
{
Variables={x,y,__handle,result},
Activities =
{
receive,
new SumActivity{
X=x,
Y=y,
Result=result
},
new SendReply{
Request=receive,
Content=SendContent.Create(new InArgument<double>(result))
}
}
</pre>
<p><strong>WorkflowService </strong>örneği içerisinde yer alan en önemli özelliklerden birisi <strong>Endpoints</strong>' dir. Bu özelliğe göre birden fazla <strong>Endpoint </strong>bildirimi yapılabilmektedir. Örneğimizde <strong>BasicHttpBinding </strong>tabanlı, <strong>IHelloService </strong>isimli servis sözleşmesini kullanan ve <strong>http://localhost:5001/HelloService</strong> adresi üzerinden yayın yapan bir <strong>Endpoint </strong>bildirimi söz konusudur. <strong>WorkflowService </strong>örneği oluşturulurken kullanılan <strong>ConfigurationName </strong>özelliği, konfigurasyon dosyası<em>(Örneğimizde app.config)</em> içerisindeki bir servis bloğunu işaret etmektedir. Aslında bu blokta servisin dış dünyaya <strong>Metadata </strong>paylaşımını yapacağını bildirebiliriz. Bildiğiniz üzere <strong>Metadata Publishing </strong>sayesinde istemcilerin <strong>WSDL </strong>içeriğine ulaşması mümkündür ve bu sayede gerekli <strong>Proxy </strong>tiplerini kolayca üretebilirler. <em>(Ancak tabiki bazı hallerde özellikle güvenlik sebebi ile Metadata bilgisini istemci tarafına indirilebiliyor olması arzu edilmeyebilir)</em> İşte<strong> app.config </strong>dosyasının içeriği.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" /> </startup>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="HelloServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl ="http://localhost:5001/HelloService"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="HelloServiceConfig" behaviorConfiguration="HelloServiceBehavior"/>
</services>
</system.serviceModel>
</configuration></pre>
<p><strong>WorkflowService </strong>örneğinin <strong>Body </strong>özelliği içerisinde ise sırasıyla <strong>Receive, SumActivity</strong> ve <strong>SendReply </strong>aktivite bileşenlerini içeren bir <strong>Sequence </strong>aktivite bileşeni tanımlandığı görülmektedir. Bu bileşenin <strong>Variables </strong>özelliğinde ise, tüm alt aktiviteler tarafından kullanılacak olan <strong>x,y, result</strong>ve <strong>__handle</strong> isimli değişken bildirimleri yer almaktadır. <strong>Sequence </strong>aktivite bileşeninin <strong>Activities </strong>özelliğine bakıldığında ise sırasıyla<strong> Receive, SumActivity</strong> ve <strong>SendReply </strong>aktivite bileşenlerinin tanımlandığı gözlemlenmektedir. Durun bir dakika <strong>SumActivity</strong>' mi? Bu bizim tarafımızdan tanımlanmış <strong>CodeActivity<double> </strong>türevli bir aktivite sınıfıdır ve içeriği aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class SumActivity
: CodeActivity<double>
{
public InArgument<double> X { get; set; }
public InArgument<double> Y { get; set; }
protected override double Execute(CodeActivityContext context)
{
return X.Get(context) + Y.Get(context);
}
}</pre>
<p><strong>SumActivity </strong>tahmin edeceğiniz üzere istemciden gelen<strong> x</strong> ve <strong>y </strong>değişkenlerini kullanmak üzere tasarlanmıştır. Elbette bu nokada küçük bir soru vardır. <strong>Receive </strong>aktivite bileşenine istemci tarafından gelen <strong>xValue </strong>ve <strong>yValue</strong> değeleri, <strong>SumActivity </strong>örneğine nasıl aktarılacaktır?<img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Dikkat edileceği üzere <strong>SumActivity </strong>örneklenirken<strong> X, Y </strong>ve <strong>Result </strong>özelliklerine sırasıyla <strong>x,y</strong> ve <strong>result </strong>değerleri atanmıştır<em>(Büyük küçük harf ayrımına dikkat edelim)</em>. Bu değişkenler <strong>Sequence </strong>seviyesindeki <strong>Variable</strong>' lardır ve <strong>Receive </strong>aktivite bileşeni içerisinde <strong>Content </strong>özelliği içerisindeki bildirim yardımıyla set edilmektedir. Çok doğal olarak <strong>Result </strong>özelliğine gelen değer,<strong> result Variable</strong>' ına aktarılmakta ve bu da <strong>SendReply </strong>aktivite bileşeni tarafından kullanılarak istemciye cevap olarak döndürülmektedir<em>(SendReply bileşeninin Content özelliğine dikkat edelim)</em></p>
<p>Artık <strong>WorkflowService </strong>örneğini host etmek üzere gerekli kodları aşağıdaki gibi yazabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">WorkflowServiceHost serviceHost = new WorkflowServiceHost(wfService);
serviceHost.Open();
Console.WriteLine("Servis açık. Kapatmak için bir tuşa basın");
Console.ReadLine();
serviceHost.Close();</pre>
<p>WorkflowServiceHost nesnesi örneklenirken parametre olarak <strong>wfService </strong>nesne örneği verilmektedir. <strong>WorkflowServiceHost </strong>tipi <strong>Workflow Service</strong>' lerin ayağa kaldırılması, servis olarak sunulması, gerekli çalışma zamanı ortamının kurgulanması gibi işlemleri üstelenen bir tip olarak düşünülmelidir. Buna göre istemci uygulama çalıştırıldığında aşağıdaki sonuçlar ile karşılaşmamız gerekmektedir.</p>
<p><img src="/pics/2010%2f5%2fblg193_Runtime1.gif" alt="" /></p>
<p>Peki ya istemci tarafı? Çalışan bu servisin sunduğu <strong>Workflow Service </strong>örneğinin işe yaradığını nasıl göreceğiz? <strong>WcfTestClient </strong>uygulaması bu noktada işimizi görüyor olacaktır. Söz konusu uygulamayı <strong>Visual Studio Command Prompt </strong>üzerinden çalıştırdıktan sonra<strong> http://localhost:5001/HelloService</strong> için bir talepte bulunmamız yeterlidir. Eğer herşey yolunda giderse <strong>Sum </strong>operasyonunu test edebilir ve aşağıdakine benzer sonuçları alabiliriz.</p>
<p><img src="/pics/2010%2f5%2fblg193_ClientRuntime.gif" alt="" /></p>
<p>İşte bu kadar. <img title="Laughing" src="/editors/tiny_mce3/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" border="0" /> Görüldüğü gibi<strong> Workflow Service</strong> örneğimizi başarılı bir şekilde çağırdık. Peki ya bundan sonrası? Örneğimizi <strong>HTTP</strong> tabanlı olarak tasaladığımızı fark etmişsiniz. Ancak sizde söz konusu örneği <strong>TCP </strong>bazlı çalışacak şekilde geliştirmeyi deneyebilirsiniz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://buraksenyurt.com/pics/2010%2f5%2fWorkflowConsoleApplication1.rar">WorkflowConsoleApplication1.rar (28,99 kb)</a></p>2010-10-04T16:20:00+00:00workflow foundationworkflow foundation 4.0visual studio 2010workflow servicebsenyurtBildiğiniz üzere bir süredir NedirTv?com desteğinde "Workflow Foundation 4.0 Öğreniyorum" isimli bir seri üzerinde çalışmaktayız. Bu seride başlangıç seviyesinden orta seviyeye kadar, bir kaç ayrı derste Workflow Foundation kavramını öğrenmeye gayret ettik. Bu seriye dahil etmek istediğim bir konu da, Workflow Service örneklerinin tamamen kod bazında yazılması ve IIS(Internet Information Services) dışındaki bir uygulama tarafından host edilmesiydi.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=f751551f-9dcf-406a-a812-21a6f8c4ebee1https://buraksenyurt.com/trackback.axd?id=f751551f-9dcf-406a-a812-21a6f8c4ebeehttps://buraksenyurt.com/post/Kod-Bazli-Workflow-Service-Gelistirmek-Yayinlamak#commenthttps://buraksenyurt.com/syndication.axd?post=f751551f-9dcf-406a-a812-21a6f8c4ebeehttps://buraksenyurt.com/post/WF-ExternalDataExchange-Local-Services-ve-CallExternalMethodActivityWF - ExternalDataExchange, Local Services ve CallExternalMethodActivity2009-09-25T07:23:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2009%2f9%2fblg81_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Artık yazın bittiği, okulların açıldığı, şehrin kalabalığının arttığı bu günlerde birde sağnak yağışlar işin içerisine girince, insan ister istemez tatilde üzerinden denize atladığı bir iskelede olmak istiyor. Artık o iskelenin etrafında fazla insan yok ve yağmur yüzünden tahtaların üzerinde gizemli bir şekilde akan su birikintileri var; diyerek yaptığımız duygusal girişimizin aslında yazımızın ilerleyen kısmı ile bir alakası yok. <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> </p>
<p>Ama yine böyle yağmurlu bir günde cama vuran damlacıkları izlerken <strong>Workflow Foundation</strong> ile ilişkili düşündüğüm ve aklıma gelen bir konunun çözümünü sizlerle paylaşmak niyetindeyim.</p>
<p><strong>İhtiyaç : </strong>Birden fazla aktivitenin aynı fonksiyonları ortaklaşa kullanabilmeleri nasıl sağlanır? Yani bir fonksiyonun birden fazla aktivite içerisinde kullanılması gerektiği durumlarda nasıl bir yol izleyebiliriz?</p>
<p><strong>Çözüm :</strong> Böyle bir ihtiyaçta metodların kod içeriklerini tüm aktivitelerde örneğin <strong>CodeActivity</strong> bileşenleri içerisinde değerlendirebiliriz. Ama bu durumda merkezileştirilmemiş ve güncelleştirmeler sırasında kullanıldığı tüm aktivitelerde düşünülmesi gereken bir çözüm üretmiş oluruz. Aslında bir yol olarak söz konusu fonksiyonellikleri ortak bir kütüphane içerisinde toplayabilir ve yine CodeActivity' ler içerisinden çağırabiliriz. Lakin bu noktada değerlendirebileceğimiz başka bir çözüm daha vardır ve gerçekten araştırılmaya değerdir. Buna göre,<strong> Local Service</strong> olarak çalışma zamanına eklenmiş bir arayüzden yararlanılabilir ve ortak fonksiyonelliklerin bu arayüz üzerinden aktiviteler ile mesajlaşması sağlanabilir.</p>
<p>Burada kritik olan nokta <strong>ExternalDataExchange</strong> <strong>niteliği(attribute)</strong> ile işaretleniş bir <strong>arayüzü(Interface)</strong> implemente eden bir tipin fonksiyonelliklerinin, herhangibir aktivite tarafından kullanılabilir hale gelmesidir. Tabi bu kullanımı sağlamak için <strong>CallExternalMethodActivity </strong>aktivite tipinden yararlanılması gerekir. Geliştirici olarak çalışma şeklini iyice kavramak yakalayacağımız kavramlar açısından önemlidir. Öncelikle <strong>CallExternalMethodActivity </strong>bileşeninin bir aktivite tipi olarak harici bir metodu işaret edebileceğini göz önüne almalıyız. Bu durumda <strong>tasarım zamanında(Design Time),</strong> <strong>CallExternalMethodActivity </strong>bileşeninin çağıracağı harici metodun imzasını ve nerede olduğunu bilmesi gerekmektedir ki çalışma zamanında bu bilgilerden yararlanarak, içinde bulunduğu aktivite ile harici metod arasında bir mesajlaşma sağlayabilsin.</p>
<p>Diğer yandan, tasarım zamanında <strong>IDE</strong>' nin <strong>CallExternalMethodActivity </strong>bileşenine kullanabileceği tipleri göstermesi, basit bir plug-in düzeneğine benzetilebilir. Söz konusu bileşen kullanabileceği tipleri bulmak konusunda, <strong>ExternalDataExchange</strong> niteliğini uygulamış interface tiplerini baz almaktadır. Buna göre arayüz tipinin çalışma zamanında gerçek işlevleri içeren bir uygulayıcısı da olmalıdır. Yani söz konusu arayüzü implemente eden bir tipten bahsediyoruz. Aktiviteler birden fazla <strong>CallExternalMethodActivity</strong> bileşeni içerebileceği gibi, birden fazla <strong>ExternalDataExchange</strong> nitelikli arayüz implementasyonunu da değerlendirebilir.</p>
<p>Artık konuyu örnekleyerek devam etmekte yarar olacağı kanısındayım. Örneğimizi <strong>Visual Studio 2008</strong> ortamında ve <strong>.Net Framework 3.5 </strong>odaklı olarak geliştiriyor olacağız. İlk olarak <strong>System.Workflow.Activities</strong> assembly' ını referans eden bir <strong>Class Library </strong>projesi oluşturarak işe başlayalım. Bir sınıf kütüphanesi tasarladığımızdan, herhangibir <strong>Workflow </strong>projesinde kullanılabilir ve tek merkezden güncellenebilir bir ürünümüz söz konusudur. Bu kütüphane, <strong>ExternalDataExchange</strong> nitelikli arayüz ve implementasyonlarını yapan tipleri barındırabilir ki örneğimizde bu amaçla aşağıdaki sınıf diagramında görülen tipler değerlendirilecektir.</p>
<p><img src="/pics/2009%2f9%2fblg81_ClassDiagram.gif" alt="" /></p>
<p><strong>Kod içeriğimiz;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Workflow.Activities;
namespace CommonOperations
{
// ICommonAccounting arayüz tipinin yerel servislerden(Local Service) birisi olduğu belirtilir
[ExternalDataExchange]
public interface ICommonAccounting
{
void IncreaseRate(double rate, int categoryId);
void DecreaseRate(double rate, int categoryId);
}
// Yerel servis metodlarının uygulandığı yer
public class CommonAccounting
:ICommonAccounting
{
// Host uygulamanın değerlendirebileceği basit bir olay
public event EventHandler<AccountingResultsEventArgs> OnCompleted;
#region ICommonAccounting Members
public void IncreaseRate(double rate, int categoryId)
{
Console.WriteLine("{0} kategorisindeki maaşlar % {1} oranında arttırılacak",categoryId.ToString(),rate.ToString());
if (OnCompleted != null)
OnCompleted(this, new AccountingResultsEventArgs { StepType = "Increase", Rate = rate,StepOk=true });
}
public void DecreaseRate(double rate, int categoryId)
{
Console.WriteLine("{0} kategorisindeki maaşlar % {1} oranında azaltılacak", categoryId.ToString(), rate.ToString());
if (OnCompleted != null)
OnCompleted(this, new AccountingResultsEventArgs { StepType = "Decrease", Rate = rate,StepOk=true });
}
#endregion
}
// OnCompleted olayı içerisinde kullanılan ve olay metoduna bilgi taşıyan sınıf
public class AccountingResultsEventArgs
: EventArgs
{
public string StepType { get; set; }
public double Rate { get; set; }
public bool StepOk { get; set; }
}
}</pre>
<p><strong>ICommonAccounting</strong> isimli arayüze <strong>ExternalDataExchange </strong>niteliği uygulanmıştır. Arayüzümüzde, işlevleri bizim için şu aşamada çok önemli olmayan iki basit operasyon tanımlaması yer almaktadır. Diğer taraftan bu arayüzü implemente eden <strong>CommonAccounting</strong> tipi içerisinde operasyonların uygulaması yer almaktadır. <strong>CommonAccounting</strong> sınıf ayrıca, kendisini kullanan aktivitelere bilgi taşıyabilmekte kullanılabilecek bir olay bildirimi de<strong><em>(OnCompleted)</em></strong> içermektedir.</p>
<p>Bu olay içerisinde kullanılan <strong>AccountingResultEventArgs</strong> isimli <strong>EventArgs </strong>türevli tip, çalışma zamanındaki <strong>CommonAccounting </strong>nesne örneğinden, <strong>OnCompleted </strong>olayına abone olan aktiviteye <strong>StepType, Rate </strong>ve <strong>StepOk </strong>gibi bazı yardımcı bilgiler döndürmektedir. <strong>IncreaseReate </strong>ve <strong>DecreaseRate </strong>metodları içerisinde, <strong>OnCompleted </strong>olayının yüklü olması halinde çalıştırılması işlemi gerçekleştirilmektedir.</p>
<p><em><strong>Kişisel Not : </strong>Olayları daha net kavrayabilmek için </em><a title="C# Temelleri - Olayları Kavramak" href="http://www.csharpnedir.com/articles/read/?filter=popular&author=&cat=&id=747&title=C" target="_blank"><em>eski bir makalemden</em></a><em> faydalanabilirsiniz.</em></p>
<p>Artık bu sınıf kütüphanesini kullanacak basit bir <strong>Workflow</strong> projesi geliştirebiliriz. Bu amaçla bir<strong> Sequential Workflow Console Application</strong> projesi oluşturduğumuzu ve geliştirdiğimiz <strong>CommonOperations</strong> isimli sınıf kütüphanesini buraya referans ettiğimizi düşünelim. Boş bir <strong>Activity</strong> öğesini projeye ekledikten sonra içeriğini aşağıdaki gibi kodlayalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Workflow.Activities;
namespace HostApp
{
public partial class Activity1
: SequenceActivity
{
public double IncreaseRate { get; set; }
public double DecreaseRate { get; set; }
public int CategoryId { get; set; }
public Activity1()
{
InitializeComponent();
}
}
}</pre>
<p>Burada tanımlanan <strong>IncreaseRate, DecreaseRate</strong> ve <strong>CategoryId </strong>özellikleri, <strong>CallExternalMethodActivity </strong>bileşenlerinin kullanacağı harici metodlara aktarılacak aktivite seviyesindeki değerleri taşımak üzere kullanılmaktadır. Şimdi tasarım zamanında, <strong>Activity1</strong> içerisine örnek bir <strong>CallExternalMethodActivity </strong>bileşenini sürükleyerek devam edebiliriz. Bu işlemin ardından bileşenin <strong>InterfaceType </strong>özelliğinden yararlanarak hangi arayüzü kullanacağını aşağıdaki şekilden görüldüğü gibi seçebiliriz.</p>
<p><img src="/pics/2009%2f9%2fblg81_InterfaceProp.gif" alt="" /></p>
<p>Görüldüğü gibi <strong>ICommonAccounting</strong> arayüzü otomatik olarak gelmiştir. Böylece hangi operasyonların kullanılabileceği, bu operasyonlara hangi parametrelerin verilmesi gerektiği bilinmektedir. Bizde akışımıza örnek olarak iki <strong>CallExternalMethodActivity</strong> bileşeni ekleyip özelliklerini aşağıdaki gibi ayarlayarak devam edebiliriz.</p>
<p>callExternalMethodActivity1 bileşeninin özellikleri;</p>
<p><img src="/pics/2009%2f9%2fblg81_Prop1.gif" alt="" /></p>
<p>callExternalMethodActivity2 bileşeninin özellikleri;</p>
<p><img src="/pics/2009%2f9%2fblg81_Prop2New.gif" alt="" /></p>
<p>Görüldüğü gibi her iki bileşen için <strong>ICommonAccounting</strong> arayüzü seçilmiş, buna göre sırasıyla <strong>IncreaseRate</strong> ve <strong>DecreaseRate </strong>operasyonlarının kullanılacağı belirtilmiştir. Ayrıca söz konuzu operasyonların parametreleri, otomatik olarak özellikler penceresine gelmiştir<strong>(rate ve categoryId).</strong> Bu özelliklerde aslında, <strong>Activity1</strong> tipi içerisinde tanımlanmış olan <strong>IncreaseRate,DecreaseRate</strong> ve <strong>CategoryId</strong> özelliklerini işaret edecek şekilde bizim tarafımızdan ayarlanmaktadır.</p>
<p>Dolayısıyla <strong>WF çalışma zamanında</strong>, <strong>Activity1</strong> içerisindeki ilgili özelliklere atanabilecek olan değerler, <strong>CallExternalMethodActivity</strong> bileşenleri ile <strong>CommonAccounting</strong> nesnesinin ilgili metodlarına gönderilerek işlenebilecektir. Eğer WF Çalışma zamanını host eden sınıf, <strong>CommonAccounting</strong> tarafından tanımlanmış <strong>OnCompleted</strong> olayınıda yüklerse, <strong>CallExternalMethodActivity</strong> bileşenlerinin çalıştırdığı harici metodlardan bazı bilgileri kendi ortamına alarak değerlendirebilecektir<strong>(AccountingResultEventArgs yardımıyla)</strong>. Bu yapı için WF çalışma zamanına özel bazı kodlamaların yapılmasıda gerekmektedir. İşte uygulama kodlarımız;</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using CommonOperations;
namespace HostApp
{
class Program
{
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
#region Yerel Servisi Bildirme İşlemi
// Yerel servisler için eklenmesi gereken servistir
ExternalDataExchangeService service = new ExternalDataExchangeService();
// ExternalDataExchangeService örneği Workflow çalışma zamanına eklenir
workflowRuntime.AddService(service);
// ExternalMetadaExchange nitelikli interface tipini implemente eden asıl nesne örneklenir
CommonAccounting accounter = new CommonAccounting();
// HostApp uygulamasının ele alacağı OnCompleted olayı yüklenir ve anonymous method yardımıyla değerlendirilir.
accounter.OnCompleted += delegate(object sender, AccountingResultsEventArgs e)
{
// Örnek metodlardan gelen sonuçlar listelenir.
Console.WriteLine("\n\tİşlem tipi {0}\n\tRate {1}\n\tİşlem sonucu {2}", e.StepType, e.Rate.ToString(),e.StepOk.ToString());
};
//accounter isimli ExternalMetadaExchange nitelikli interface tipini implemente eden asıl nesne örneği, ExternalDataExchangeService örneğine eklenir.
service.AddService(accounter);
#endregion
AutoResetEvent waitHandle = new AutoResetEvent(false);
// Workflow tamamlandığında devreye giren olay metodu
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {
waitHandle.Set();
};
// Exception gibi nedenlerle Workflow sonlandığında devreye giren olay metodu
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
// Yerel servis içerisindeki metodların kullanacağı parametrelere işaret eden özellikler yüklenir.
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{"IncreaseRate",1.12},
{"DecreaseRate",2.25},
{"CategoryId",1}
};
// Aktivite nesnesi örneklenir, özellikleri için ilk değerleri yüklenir.
WorkflowInstance mathActivity = workflowRuntime.CreateWorkflow(typeof(HostApp.Activity1),parameters);
// Aktivite başlatılır
mathActivity.Start();
// Asenkron işleyişi ispat etmek için
Console.WriteLine("İşlemler başladı");
// İşlemler tamamlanmadıysa bekle
waitHandle.WaitOne();
}
}
}
}</pre>
<p>Görüldüğü üzere<strong> Local Service'</strong> in tanımlanmasını takiben, <strong>ExternalDataExchange </strong>nitelikli tipe ait nesne örneklenmiş ve yerel servise bildirilmiştir. Ayrıca <strong>CommonAccounting</strong> nesnesinin <strong>OnCompleted</strong> olayı yüklenmiş ve <strong>Program'</strong> ın bu olaya abone olması sağlanmıştır. <strong>Activity1</strong> nesnesine ait özellikleri set etmek için <strong>Dictionary<string,object></strong> koleksiyonundan yararlanılmış ve son olarak aktivitemiz başlatılmıştır. İşte çalışma zamanı sonuçları.</p>
<p><img src="/pics/2009%2f9%2fblg81_Runtime.gif" alt="" /></p>
<p>Evet...Önce belirli oranda arttırım yapıp sonra azaltım yapmak son derece saçma gözükmektedir <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> Ancak yakalamamız gereken nokta elbetteki bu değildir. Önemli olan, bir aktivite' nin kendi sınırları dışındaki fonksiyonellikleri kullanabilmek için yerel servislerden nasıl yararlanıldığı ve bunun için <strong>ExternalDataExchange</strong> niteliğinin nasıl değerlendirildiğidir. Üstelik bu değerlendirme, WF tasarım zamanı içinde önem arz eder. Tekrardan görüşünceye dek hepinize mutlu günler dilerim. </p>
<p><a href="https://buraksenyurt.com/pics/2009%2f9%2fUsingExternalCode.rar">UsingExternalCode.rar (50,98 kb)</a></p>2009-09-25T07:23:00+00:00wfbsenyurtBirden fazla aktivitenin aynı fonksiyonları ortaklaşa kullanabilmeleri nasıl sağlanır? Yani bir fonksiyonun birden fazla aktivite içerisinde kullanılması gerektiği durumlarda nasıl bir yol izleyebiliriz?https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=8ce29f9a-f643-4917-878a-6d36ef5c002d0https://buraksenyurt.com/trackback.axd?id=8ce29f9a-f643-4917-878a-6d36ef5c002dhttps://buraksenyurt.com/post/WF-ExternalDataExchange-Local-Services-ve-CallExternalMethodActivity#commenthttps://buraksenyurt.com/syndication.axd?post=8ce29f9a-f643-4917-878a-6d36ef5c002d