https://www.buraksenyurt.com/Burak Selim Şenyurt - WF 4.02019-04-30T11:43:58+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/Workflow-Foundation-Oracle-WCF-ve-TransactionScopeWorkflow Foundation, Oracle, WCF ve TransactionScope2013-01-31T22:36:00+00:00bsenyurt<p><a href="https://www.buraksenyurt.com/pics/Proof-of-Concept-Prototypes-Dont-Have-to-Be-as-Complicated-as-This.jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="Proof-of-Concept-Prototypes-Dont-Have-to-Be-as-Complicated-as-This" src="/pics/Proof-of-Concept-Prototypes-Dont-Have-to-Be-as-Complicated-as-This_thumb.jpg" alt="Proof-of-Concept-Prototypes-Dont-Have-to-Be-as-Complicated-as-This" width="250" height="288" align="right" /></a><span style="color: #ff0000;">[Örnek Visual Studio 2010, .Net Framework 4.0 tabanlıdır]</span></p>
<p>Merhaba Arkadaşlar,</p>
<p>Yandaki fotoğrafta görülen buluşa baktığınızda aslında gerçekten bu pilotun o koca pervaneler ile uçup uçamayacağına pek kanaat getiremiyoruz öyle değil mi?</p>
<p>Sonuçta en azından kağıt üstünde ve teorik olarak da bu tip bir uçuş aracının çalışacağının ispat edilmesi ve sonrasında pratikteki kullanımı için teste çıkılması beklenir<em>(Tabi buna cesaret edecek de bir pilotun olması gerekir)</em></p>
<p>Bir dostumuzun söylediği üzere <strong>"tasarlanan her uçak uçmuş ama her yazılım çalışmamıştır"</strong> <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_109.png" alt="Winking smile" /></p>
<p>Bir başka deyişle yazılım tarafında bir şeylerin ispatını yaparken bir uçağı uçuracakmış gibi düşünerek hareket etmeyiz genelde. İstesek de edemiyoruz sanırım. Yine de elimizden geldiğince titiz çalışmamız da yarar var. Öyleyse gelelim bu günün konusuna.</p>
<p>Geçtiğimiz günlerde <strong>Workflow Foundation</strong> tabanlı bir uygulama içerisinde <strong>Transaction</strong> <strong>Scope</strong> kullanımına ihtiyacım oldu. <strong>Transaction'</strong> a dahil olan işlemler <strong>Oracle</strong> tabloları üzerinde gerçekleştirilecekti. Senaryoyu zorlaştıran noktalardan birisi ise, akış içerisinde harici bir <strong>WCF</strong> servis çağrısının yapılacak olmasıydı. Nitekim söz konusu <strong>WCF</strong> servisi içerisindeki operasyonda da, yine <strong>Oracle</strong> veritabanı üzerinde yapılması planlanan <strong>Transactional</strong> bir işlem söz konusuydu.</p>
<p>Dolayısıyla <strong>Oracle'</strong> ın ve <strong>WCF</strong> servisinin işin içerisinde yer aldığı bir <strong>Workflow</strong> senaryosunda, <strong>TransactionScope</strong> bileşeninin işe yarayıp yaramadığının araştırılması ve gerekli ispatların yapılması gerekmekteydi. Bir başka deyişle bir <strong>POC(Proof Of Concept)</strong> çalışması ile karşı karşıyaydık. Ben de <strong>Visual Studio 2010</strong> un başına oturdum ve yola koyuldum.</p>
<p>Senaryoda kendi oluşturduğum iki <strong>Oracle</strong> tablosu söz konusu idi. Bu tabloların içerdiği alanların çok fazla önemi yoktu aslında. <strong>Account</strong> ve <strong>Branch</strong> olarak adlandırdığım tablolar üzerinde iki basit <strong>Insert</strong> işlemi gerçekleştirecektim. Buna ek olarak bir de <strong>WCF</strong> servisi söz konusu olmalıydı. İlgili <strong>WCF</strong> servisi de yine benzer bir <strong>Insert</strong> işlemi gerçekleştirilmek üzere tasarlanmalıydı.</p>
<p>Teorik olarak <strong>Workflow</strong> <strong>Activity'</strong> si içerisinde kullanılacak <strong>TransactionScope</strong> <strong>Component'</strong> i servis tarafına <strong>Transaction'</strong> ı aktarabilmeliydi. Tam bu noktada <strong>Distributed</strong> <strong>Transaction</strong> <strong>Coordinator</strong> servisinin de işlevselliği söz konusuydu. Elbette servis tarafının da, <strong>Transaction</strong> akışına izin verecek şekilde tesis edilmesi şarttı.</p>
<blockquote>
<p>Bu noktada özellikle Binding tipinin Transaction Flow' a destek veriyor olması önemlidir. Nitekim varsayılan bağlayıcı tip olan BasicHttpBinding, istemciden gelen Transaction akışına izin vermez. Ama örneğin wsHttpBinding buna olanak tanır.</p>
</blockquote>
<p>Dilerseniz işe ilk olarak bu <strong>WCF</strong> servisini geliştirerek başlayalım. Servisimize ait basit kod içeriği aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ServiceModel;
namespace CRUDer
{
[ServiceContract]
public interface IAccountService
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
int DoWork(int AccountId,string Name,string Surname);
}
}</pre>
<p>Sözleşme tipi içerisindeki en önemli nokta <strong>TransactionFlow</strong> niteliğinin kullanılması ve <strong>Mandatory</strong> değerinin verilmesidir. Bu nitelik <strong>Allowed</strong>, <strong>NotAllowed</strong> şeklinde iki farklı değer daha alabilir. <strong>Mandatory</strong>, istemci tarafında bir <strong>Transaction</strong> <strong>Scope</strong> başlatılma zorunluluğunu ifade etmektedir. Nitekim operasyon içerisindeki işlemin bir <strong>Transaction</strong> <strong>Scope'</strong> a dahil olması şarttır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Configuration;
using System.ServiceModel;
using System.Transactions;
using Oracle.DataAccess.Client;
namespace CRUDer
{
//[ServiceBehavior(TransactionIsolationLevel=IsolationLevel.Serializable)]
public class AccountService
: IAccountService
{
[OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=true)]
public int DoWork(int AccountId, string Name, string Surname)
{
int result = -1;
var currentTransaction=Transaction.Current;
using (OracleConnection conn = new OracleConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
{
using (OracleCommand command = new OracleCommand("INSERT INTO ACCOUNT (ACCOUNTID,NAME,SURNAME) VALUES (:pACCOUNTID,:pNAME,:pSURNAME)", conn))
{
command.Parameters.Add(":pACCOUNTID",AccountId);
command.Parameters.Add(":pNAME", Name);
command.Parameters.Add(":pSURNAME", Surname);
conn.Open();
result = command.ExecuteNonQuery();
}
}
return result;
}
}
}</pre>
<p>Servisin asıl iş yapan kodlarında yine dikkat çekici ve önemli olan nokta <strong>OperationBehavior</strong> niteliği ile atanan <strong>özellik(Property)</strong> değerleridir. Operasyonun bir <strong>TransactionScope</strong> gerektirdiği ve ayrıca <strong>unhandled</strong> <strong>exception</strong> oluşması halinde operasyona ait <strong>transaction </strong>örneğinin otomatik olarak tamamlanıp tamamlanmayacağı belirtilmektedir.</p>
<blockquote>
<p>Servis tarafında System.Transactions assembly' ının referans edilmesi <span style="text-decoration: underline;">unutulmamalıdır</span>. Ayrıca hem servis hem de istemci tarafı Oracle fonksiyonellikleri için <a href="http://www.oracle.com/technetwork/topics/dotnet/index-085163.html" target="_blank">ODP.Net' e(Oracle DataProvider for .Net)</a> ait assembly' ları kullanmaktadır. Dolayısıyla Oracle.DataAccess assembly’ ının uygun olan versiyonunun referans edilmelidir.</p>
</blockquote>
<p>Servis tarafındaki <strong>config</strong> dosyasının içeriği de oldukça önemlidir. Daha önceden de belirttiğimiz üzere <strong>Binding</strong> tipi, <strong>Transaction</strong> akışına izin verir nitelikte olmalıdır. Ancak buna ek olarak <strong>Transaction</strong> akışına izin verileceğinin de konfigurasyon dosyası içerisinde belirtilmesi şarttır. Çünkü varsayılan olarak <strong>Transaction</strong> akışı etkin değildir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="ConStr" connectionString="User Id=bir kullanıcı adı;Password=bir şifre;Data Source=oracle veri kaynağı" providerName="Oracle.DataAccess.Client"/>
</connectionStrings>
<system.serviceModel>
<services>
<service name="CRUDer.AccountService">
<endpoint address="http://localhost:35662/AccountService.svc"
binding="wsHttpBinding" bindingConfiguration="wsB" contract="CRUDer.IAccountService" />
</service>
</services>
<bindings>
<wsHttpBinding>
<binding transactionFlow="true" name="wsB"/>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="false"/>
</system.serviceModel>
<system.web>
<compilation debug="true"/>
</system.web>
</configuration></pre>
<p>Dikkat edileceği üzere <strong>wsHttpBinding</strong> elementi içerisinde yer alan <strong>binding</strong> alt elementinin <strong>transactionFlow</strong> niteliğinin değeri <strong>true</strong> olarak set edilmiştir. Servis tarafında yapılması gerekenler aslında bu kadardır. Öyleyse <strong>Workflow</strong> tasarımına başlayabiliriz. <strong>Workflow</strong> <strong>Console</strong> <strong>Application</strong> olarak tasarlanan uygulamamızda çalışma zamanını izleyebilmek için basit de bir <strong>Tracker</strong> tipine yer verilmektedir. Bu tipin içeriği aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Activities.Tracking;
using System.Collections.Generic;
using System.Text;
namespace HowToTransaction
{
public class CustomTracker
: TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
WorkflowInstanceRecord instanceRecord = record as WorkflowInstanceRecord;
if (instanceRecord != null)
Console.WriteLine(instanceRecord.ToString());
ActivityStateRecord activity = record as ActivityStateRecord;
if (activity != null)
{
var variables = activity.Variables;
StringBuilder builder = new StringBuilder();
if (variables.Count > 0)
{
builder.AppendLine(" Variables:");
foreach (KeyValuePair<string, object> variable in variables)
{
builder.AppendLine(String.Format(" {0} Value: [{1}]", variable.Key, variable.Value));
}
}
Console.WriteLine(
String.Format(" Activity: {0} State: {1} {2}",
activity.Activity.Name
, activity.State
, builder.ToString())
);
}
}
}
}</pre>
<p><strong>Tracker</strong> sınıfı içerisinde hem <strong>Workflow Actvitiy'</strong> si hem de içeride yürümekte olan <strong>Activity</strong> örneklerinin çalışmalarının izlenmesi işlemi gerçekleştirilmektedir. Bu, <strong>TrackingRecord</strong> tipinden olan parametrenin <strong>WorkflowInstanceRecord</strong> ve <strong>ActivityStateRecord</strong> nesne örneklerine dönüştürülmesi ile gerçekleştirilmektedir. <strong>Tracker</strong> tipi dışında <strong>Account</strong> ve <strong>Branch</strong> tablolarına <strong>insert</strong> işlemi yapan iki <strong>Code Activiy</strong> bileşeni de bulunmaktadır. Söz konusu Activity bileşenlerinin kodları aşağıdaki gibidir.</p>
<p>Account insert işlemini üstlenen Activity,</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace HowToTransaction
{
using System.Activities;
using System.Configuration;
using Oracle.DataAccess.Client;
public class InsertAccountActivity
:CodeActivity
{
public InArgument<int> AccountId { get; set; }
public InArgument<string> Name { get; set; }
public InArgument<string> Surname { get; set; }
public OutArgument<int> ExecuteNonQueryResult { get; set; }
protected override void Execute(CodeActivityContext context)
{
using (OracleConnection conn = new OracleConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
{
using (OracleCommand command = new OracleCommand("INSERT INTO ACCOUNT (ACCOUNTID,NAME,SURNAME) VALUES (:pACCOUNTID,:pNAME,:pSURNAME)", conn))
{
command.Parameters.Add(":pACCOUNTID",context.GetValue(AccountId));
command.Parameters.Add(":pNAME",context.GetValue(Name));
command.Parameters.Add(":pSURNAME",context.GetValue(Surname));
conn.Open();
int result=command.ExecuteNonQuery();
context.SetValue(ExecuteNonQueryResult, result);
}
}
}
}
}</pre>
<p>ve Branch insert işlemini üstlenen Code Activity</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace HowToTransaction
{
using System.Activities;
using System.Configuration;
using Oracle.DataAccess.Client;
using System;
public class InsertBranchActivity
:CodeActivity
{
public InArgument<int> BranchId { get; set; }
public InArgument<string> Title { get; set; }
public InArgument<int> Code { get; set; }
public OutArgument<int> ExecuteNonQueryResult { get; set; }
protected override void Execute(CodeActivityContext context)
{
using (OracleConnection conn = new OracleConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
{
using (OracleCommand command = new OracleCommand("INSERT INTO BRANCH (BRANCHID,TITLE,CODE) VALUES (:pBRANCHD,:pTITLE,:pCODE)", conn))
{
command.Parameters.Add(":pBRANCHID",context.GetValue(BranchId));
command.Parameters.Add(":pTITLE",context.GetValue(Title));
command.Parameters.Add(":pCODE",context.GetValue(Code));
conn.Open();
int result = command.ExecuteNonQuery();
context.SetValue(ExecuteNonQueryResult, result);
//throw new Exception("Some error");
}
}
}
}
}</pre>
<p>Burada yorum satırı olarak bırakılmış kısım daha sonradan yapılacak testler sırasında açılacak ve <strong>Transaction Scope'</strong> un çalışması izlenecektir.</p>
<p>Gelelim test amaçlı kullanacağımız <strong>Workflow Activity </strong>içeriğine.</p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_1.png"><img style="margin: 4px 0px; display: inline;" title="wfts_1" src="/pics/wfts_1_thumb.png" alt="wfts_1" width="460" height="432" /></a></p>
<p><strong>FlowChart</strong> şeklinde tasarladığımız akışın içerisindeki en kritik yer <strong>TryCatch</strong> bileşenin içerisidir.</p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_2.png"><img style="margin: 4px 0px; display: inline;" title="wfts_2" src="/pics/wfts_2_thumb.png" alt="wfts_2" width="431" height="546" /></a></p>
<p><strong>Try</strong> bloğundan <strong>TransactionScope</strong> bileşeni altında sırasıyla <strong>Account</strong> <strong>Insert</strong> işlemi, <strong>DoWork</strong> ile WCF servis çağrısı ve tekrar <strong>Branch</strong> <strong>Insert</strong> işlemi gerçekleştirilmektedir.</p>
<blockquote>
<p>WCF servisinin Workflow uygulamasına Add Service Reference ile eklenmesi sonrası Component sekmesine çıkan aktivite bileşeni kullanılmaktadır<em>(DoWork bileşeni)</em></p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_3.png"><img style="margin: 4px 0px; display: inline;" title="wfts_3" src="/pics/wfts_3_thumb.png" alt="wfts_3" width="272" height="164" /></a></p>
</blockquote>
<p><strong>Workflow</strong> un <strong>XAML(eXtensibleApplicationMarkupLanguage)</strong> içeriği aşağıdaki gibidir. Burada, kullanılan <strong>variable’</strong> lar daha net bir şekilde görülebilmektedir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Activity mc:Ignorable="sads sap" x:Class="HowToTransaction.Workflow1" sap:VirtualizedContainerService.HintSize="654,676" mva:VisualBasic.Settings="Assembly references and imported namespaces for internal implementation"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:hc="clr-namespace:HowToTransaction.CRUDerReference"
xmlns:local="clr-namespace:HowToTransaction"
xmlns:local1="clr-namespace:HowToTransaction.CRUDerReference.Activities"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System"
xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities"
xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/servicemodel"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:s1="clr-namespace:System;assembly=System"
xmlns:s2="clr-namespace:System;assembly=System.Xml"
xmlns:s3="clr-namespace:System;assembly=System.Core"
xmlns:s4="clr-namespace:System;assembly=System.ServiceModel"
xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities"
xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities"
xmlns:sads="http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger"
xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System"
xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel"
xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core"
xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:sd="clr-namespace:System.Data;assembly=System.Data"
xmlns:sl="clr-namespace:System.Linq;assembly=System.Core"
xmlns:st="clr-namespace:System.Text;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Flowchart sad:XamlDebuggerXmlReader.FileName="d:\users\bsenyurt\documents\visual studio 2010\Projects\Windows Phone\HowToTransaction\HowToTransaction\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="614,636">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">False</x:Boolean>
<av:Point x:Key="ShapeLocation">270,2.5</av:Point>
<av:Size x:Key="ShapeSize">60,75</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,77.5 300,139.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Flowchart.StartNode>
<FlowStep x:Name="__ReferenceID0">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">194.5,139.5</av:Point>
<av:Size x:Key="ShapeSize">211,59</av:Size>
<av:PointCollection x:Key="ConnectorLocation">300,198.5 300,228.5 160,228.5 160,245.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<WriteLine sap:VirtualizedContainerService.HintSize="211,59" Text="Account ve Branch tabloları için Insert işlemleri başlıyor">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</WriteLine>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID2">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">60,245.5</av:Point>
<av:Size x:Key="ShapeSize">200,49</av:Size>
<av:PointCollection x:Key="ConnectorLocation">160,294.5 160,324.5 290,324.5 290,340.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<TryCatch sap:VirtualizedContainerService.HintSize="418,529">
<TryCatch.Variables>
<Variable x:TypeArguments="x:Int32" Name="ServiceCallResult" />
</TryCatch.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>
<TryCatch.Try>
<TransactionScope AbortInstanceOnTransactionFailure="False" sap:VirtualizedContainerService.HintSize="258,351">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<Sequence sap:VirtualizedContainerService.HintSize="222,270">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<local:InsertAccountActivity ExecuteNonQueryResult="{x:Null}" AccountId="2" sap:VirtualizedContainerService.HintSize="200,22" Name="Delinin" Surname="Biri">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</local:InsertAccountActivity>
<local1:DoWork AccountId="99" DoWorkResult="[ServiceCallResult]" EndpointConfigurationName="WSHttpBinding_IAccountService" sap:VirtualizedContainerService.HintSize="200,22" Name="Maykıl" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" Surname="Cordın" />
<local:InsertBranchActivity ExecuteNonQueryResult="{x:Null}" BranchId="34" Code="4" sap:VirtualizedContainerService.HintSize="200,22" Title="germany">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</local:InsertBranchActivity>
</Sequence>
</TransactionScope>
</TryCatch.Try>
<TryCatch.Catches>
<Catch x:TypeArguments="s:Exception" sap:VirtualizedContainerService.HintSize="404,20">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">False</x:Boolean>
<x:Boolean x:Key="IsPinned">False</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<ActivityAction x:TypeArguments="s:Exception">
<ActivityAction.Argument>
<DelegateInArgument x:TypeArguments="s:Exception" Name="exception" />
</ActivityAction.Argument>
<WriteLine sap:VirtualizedContainerService.HintSize="211,59" Text="[exception.Message]" />
</ActivityAction>
</Catch>
</TryCatch.Catches>
</TryCatch>
<FlowStep.Next>
<FlowStep x:Name="__ReferenceID1">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<av:Point x:Key="ShapeLocation">184.5,340.5</av:Point>
<av:Size x:Key="ShapeSize">211,59</av:Size>
<av:PointCollection x:Key="ConnectorLocation">310,210.5 310,309.5</av:PointCollection>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<WriteLine sap:VirtualizedContainerService.HintSize="211,59" Text="İşlemler tamamlandı">
<sap:WorkflowViewStateService.ViewState>
<scg3:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg3:Dictionary>
</sap:WorkflowViewStateService.ViewState>
</WriteLine>
</FlowStep>
</FlowStep.Next>
</FlowStep>
</FlowStep.Next>
</FlowStep>
</Flowchart.StartNode>
<x:Reference>__ReferenceID0</x:Reference>
<x:Reference>__ReferenceID1</x:Reference>
<x:Reference>__ReferenceID2</x:Reference>
</Flowchart>
</Activity></pre>
<p><strong>Workflow Application</strong> tarafındaki en önemli ayarlardan birisi de <strong>config</strong> dosyasında yer almaktadır.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<connectionStrings>
<add name="ConStr" connectionString="User Id=bir kullanıcı adı;Password=bir şifre;Data Source=bir veri kaynağı" providerName="Oracle.DataAccess.Client"/>
</connectionStrings>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IAccountService" transactionFlow="true" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:35662/AccountService.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IAccountService"
contract="IAccountService" name="WSHttpBinding_IAccountService">
<identity>
<userPrincipalName value="XXXXXXXXX" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration></pre>
<p>İstemci tarafındaki <strong>Binding</strong> ayarlarında da <strong>transactionFlow</strong> niteliğinin mutlak suretle <strong>true</strong> olması gerekmektedir. Gelelim <strong>Main</strong> metodu içerisindeki kodlarımıza.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Activities;
using System.Threading;
namespace HowToTransaction
{
class Program
{
static void Main(string[] args)
{
var activity = new Workflow1();
AutoResetEvent arEvent = new AutoResetEvent(false);
WorkflowApplication wfApp = new WorkflowApplication(activity);
wfApp.Extensions.Add(new CustomTracker());
wfApp.Completed = (o) =>
{
arEvent.Set();
};
wfApp.Run();
arEvent.WaitOne();
}
}
}</pre>
<p><strong>WorkflowApplication</strong> tipinden yararlanılarak <strong>Workflow1</strong> örneğinin başlatılması işlemi gerçekleştirilmektedir. <strong>Tracking</strong> işlemi için yazılmış olan <strong>CustomTracker</strong> sınıfının da, bir <strong>Extension</strong> olarak <strong>WorkflowApplication</strong> örneğine bildirilmesi gerekir.</p>
<p>Örneği bu hali ile çalıştırdığımda aşağıdaki sonuçları elde ettiğimi gördüm.</p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_4.png"><img style="margin: 4px 0px; display: inline;" title="wfts_4" src="/pics/wfts_4_thumb.png" alt="wfts_4" width="400" height="585" /></a></p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_5.png"><img style="margin: 4px 0px; display: inline;" title="wfts_5" src="/pics/wfts_5_thumb.png" alt="wfts_5" width="400" height="585" /></a></p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_6.png"><img style="display: inline;" title="wfts_6" src="/pics/wfts_6_thumb.png" alt="wfts_6" width="400" height="585" /></a></p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_7.png"><img style="margin: 4px 0px; display: inline;" title="wfts_7" src="/pics/wfts_7_thumb.png" alt="wfts_7" width="400" height="202" /></a></p>
<p>Ekran çıktısını okumak biraz zahmetli olabilir<em>(Özellikle WCF servis çağrısının yapıldığı aktivite mesajlaşma içeriğini de bastığından…)</em> ama işlemlerin başarılı bir şekilde yapıldığı görülmektedir ve doğal olarak veritabanı tarafındaki <strong>insert</strong> işlemleri de başarılı olmuştur. Özellikle <strong>activity</strong> tipleri için yapılan <strong>State</strong> bildirimlerine dikkatinizi çekerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_8.png"><img style="background-image: none; margin: 4px 0px; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;" title="wfts_8" src="/pics/wfts_8_thumb.png" alt="wfts_8" width="244" height="238" border="0" /></a></p>
<p>Hatta <strong>Debug</strong> işlemi yapıldığında servis tarafındaki operasyon içerisinde, istemci tarafından gelen <strong>Transaction</strong> bilgileri de açık bir şekilde görülebilmektedir.</p>
<p><a href="https://www.buraksenyurt.com/pics/wfts_9.png"><img style="margin: 4px 0px; display: inline;" title="wfts_9" src="/pics/wfts_9_thumb.png" alt="wfts_9" width="590" height="243" /></a></p>
<p>Dikkat edileceği üzere <strong>DistributedIdentifier</strong> özelliğinin <strong>GUID</strong> tipinden bir değeri mevcuttur. Bir başka deyişle <strong>DTC</strong> devreye girmiş ve şu andaki servis operasyonu içerisinde yapılacak işlemler, <strong>Workflow</strong> <strong>Application</strong> tarafında açılan <strong>TransactionScope’</strong> a dahil edilmiştir.</p>
<p>Eğer <strong>InsertBranch</strong> <strong>Code Activity</strong> bileşeni içerisindeki <strong>Exception</strong> fırlatılan satır etkinleştirilirse, <strong>Transaction</strong> işlemlerinin <strong>Commit</strong> edilmediği görülecektir. Bu kısım oldukça önemlidir. Çünkü <strong>Workflow1</strong> akışında önce akış içi bir <strong>Transaction</strong> işlemi, sonrasında servis tarafında bir <strong>Transaction</strong> işlemi ve son olarak da yine akış içerisinde bir <strong>Transaction</strong> işlemi söz konusudur.</p>
<p>Son işlemde oluşan <strong>Exception</strong> nedeni ile bir adım önceki servis <strong>Transaction</strong> işleminin iptal edilmesi ve edildiğinin görülmesi<em>(tabi o ana kadarki tüm Insert’ lerin de iptal edildiğinin görülmesi)</em> son derece önemlidir. Bu, <strong>TransactionScope’</strong> un başarılı çalıştığının bir ispatı olarak düşünülebilir.</p>
<p>Görüldüğü üzere bir <strong>Workflow</strong> içerisinden başlatılan <strong>Transaction’</strong> ın, bir <strong>WCF</strong> servis operasyonuna aktarılabilmesi ve söz konusu operasyonun ilgili <strong>Transaction</strong> <strong>Scope’</strong> a dahil hale gelerek <strong>Two Phase Commit</strong> metodolojisine uygun biçimde sisteme dahil edilmesi mümkündür. Bu, tipik anlamda bir <strong>Atomic Transaction</strong> senaryosudur. Sadece bir kaç küçük detaya ve ayarlamaya dikkat etmek gerekmektedir. Böylece geldik bir yazımızın daha sonuna. Tekrarda görüşünceye dek hepinize mutlu günler dilerim <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_109.png" alt="Winking smile" /></p>
<p><a href="https://www.buraksenyurt.com/pics/2012%2f8%2fHowToTransaction.zip">HowToTransaction.zip (457,18 kb)</a></p>
<p><span style="color: #ff0000;">[Örnek Visual Studio 2010, .Net Framework 4.0 tabanlıdır]</span></p>2013-01-31T22:36:00+00:00workflow foundationworkflow foundation 4.0workflow foundation öğreniyorumwcfwcf 4.0wcf servicetransaction managementtransaction scopeoracleodp.netoracle data provider for .netbsenyurtGeçtiğimiz günlerde Workflow Foundation tabanlı bir uygulama içerisinde Transaction Scope kullanımına ihtiyacım oldu. Transaction' a dahil olan işlemler Oracle tabloları üzerinde gerçekleştirilecekti. Senaryoyu zorlaştıran noktalardan birisi ise, akış içerisinde harici bir WCF servis çağrısının yapılacak olmasıydı. Nitekim söz konusu WCF servisi içerisindeki operasyonda da, yine Oracle veritabanı üzerinde yapılması planlanan Transactional bir işlem söz konusuydu.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=d95dd793-1234-443f-bd71-182b47595dec0https://www.buraksenyurt.com/trackback.axd?id=d95dd793-1234-443f-bd71-182b47595dechttps://www.buraksenyurt.com/post/Workflow-Foundation-Oracle-WCF-ve-TransactionScope#commenthttps://www.buraksenyurt.com/syndication.axd?post=d95dd793-1234-443f-bd71-182b47595dechttps://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=1f62e593-fe0a-4cc8-94ed-0df571c063461https://www.buraksenyurt.com/trackback.axd?id=1f62e593-fe0a-4cc8-94ed-0df571c06346https://www.buraksenyurt.com/post/WF-Rule-Engine-How-To#commenthttps://www.buraksenyurt.com/syndication.axd?post=1f62e593-fe0a-4cc8-94ed-0df571c06346https://www.buraksenyurt.com/post/Developing-Re-Hosted-Workflow-DesignerWorkflow Designer’ ı Yeniden Host Etmek (WF 4.0)2012-07-12T23:05:00+00:00bsenyurt<p><a href="https://www.buraksenyurt.com/pics/total-recall-2012-official-trailer-teaser-00%20(1).jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="total-recall-2012-official-trailer-teaser-00 (1)" src="/pics/total-recall-2012-official-trailer-teaser-00%20(1)_thumb.jpg" alt="total-recall-2012-official-trailer-teaser-00 (1)" width="280" height="220" align="right" /></a>Merhaba Arkadaşlar,</p>
<p>Çoğu zaman sinemada daha önceden vizyona girmiş olan bir filmin yeniden çekilmiş bir versiyonuna rastlarız. Örneğin <strong>Batman Begins</strong> veya vizyona bu yaz girecek <strong>Total Recall</strong> gibi. Hatta bazen <strong>Cover</strong> olarak adlandırdığımız bir durum söz konusu olur ve çeşitli müzik guruplarının önemli parçalarının tekrardan, aynı ekipçe veya başkalarınca yorumlandığını görür, duyarız.</p>
<p>Sonuç itibariyle insanlar zaman zaman yapılmış olan bazı çalışmaları hem teknolojinin yeni nimetleri, hem de farklı şekilde yorumlayabilme isteği nedeni ile tekrardan ele alabilirler. </p>
<p>Hatta bu felsefe yazılım dünyasında da zaman zaman vuku bulan bir senaryodur. Özellikle <strong>IDE</strong> tarafında. Bir <strong>IDE’</strong> nin kabuğu üstüne giyidirilebilen parçaları farklılaştırabildiğinizi veya var olan <strong>IDE’</strong> lerden farklı olan alternatiflerini üretebildiğinizi düşünün. Örneğin <strong>SharpDevelop</strong> <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_39.png" alt="Smile" /> </p>
<p>Aslına bakarsanız <strong>Visual Studio</strong> gerçekten harika bir <strong>IDE</strong> ortamı sunmaktadır. Hatta <strong>UX</strong> olarak bilien <strong>User eXperience</strong> değil de tam anlamıya <strong>Developer eXperience’</strong> ın hat safhada olduğu bir geliştirme ortamıdır. Lakin genişletilebilir olması<em>(Extension Manager’ a dikkatiniz çekmek isterim)</em> haricinde çok gelişmiş özellikleri olmakla birlikte, zaman zaman daha hafif bir sürüme ihtiyaç duyabiliriz. Örneğin <strong>Workflow Foundation </strong>tabanlı olarak bir iş akışı tasarım uygulaması geliştirmek istediğinizi düşünün <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_98.png" alt="Winking smile" /> </p>
<p>Bu tip uygulamalarda herşeyi baştan ele alıp Amerikayı tekrardan keşfetmeyi deneyebilirsiniz elbette. Ancak zaten elimizde var olan bir <strong>Designer</strong> ortamı var ise, sadece bunu alıp yeni bir kabuk giydirmeye çalışmak daha etkili ve hızlı bir çözüm olabilir. İşte bu yazımızda çok basit olarak <strong>Workflow Designer</strong> ortamının <strong>Visual Studio</strong> dışarısında nasıl kullanılabileceğini öğrenmeye çalışıyor olacağız. Aracımızdan beklediğimiz özellikler temel olarak aşağıdaki maddeler halinde ifade edilebilir.</p>
<ol>
<li>Var olan <strong>Primitive Workflow Component’ </strong>leri veya bizim tarafımızdan geliştirilmiş bileşenleri içeren <strong>Toolbox’</strong> a sahip olmalıdır.</li>
<li><strong>Workflow</strong> içeriğinin tasarlanabileceği <strong>Visual Studio</strong> içerisindeki <strong>Designer</strong> bulunmalıdır.</li>
<li>Herhangibir <strong>Workflow</strong> bileşeni seçildiğinde, buna ait özelliklerin dolacağı ve tabiki değiştirilebileceği bir <strong>Properties</strong> penceresi yer almalıdır.</li>
<li>Tasarlanan <strong>Workflow</strong> örnekleri kayıt edilebilmeli veya <strong>XAML<em>(eXtensible Application Markup Language)</em></strong> içerikli dosyalardan yüklenebilmelidir.</li>
<li>Tasarlanan veya yüklenen <strong>Workflow</strong> örnekleri çalıştırılabilmelidir.</li>
<li>Kullanıcı deneyimini yüksek tutmak istediğimizden <strong>WPF(Windows Presentation Foundation)</strong> tabanlı bir arayüz sunulabilmelidir.</li>
</ol>
<p>Bu temel özellikleri gerçekleştirdiğimiz takdirde elimizde basit bir <strong>Workflow</strong> geliştirme aracı oluşacaktır. Söz konusu aracın daha da etkin hale getirilmesi için genişletilebilir bir yapıda tasarlanması önemlidir, ancak bu örneğimizde bu biraz daha göz ardı edilecek bir unsurdur <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_98.png" alt="Winking smile" /> Peki bu tip bir uygulama geliştirmek için elimizde neler var bir de buna bakalım dilerseniz.</p>
<ol>
<li><strong>WorkflowDesigner</strong> sınıfı ile tasarım ortamının birerbir kullanılabilmesi mümkün olacaktır.</li>
<li><strong>ToolboxCategory, ToolboxControl, ToolboxItemWrapper</strong> tiplerinden yararlanarak <strong>Toolbox</strong> oluşturulabilir ve içeriğine <strong>Workflow</strong> bileşenleri atılabilir.</li>
<li><strong>WorkflowDesigner</strong> tipinin <strong>PropertyInspectorView</strong> özelliği ile, <strong>Property</strong> penceresinin set edilmesi sağlanabilecektir.</li>
<li><strong>WorkflowDesigner’</strong> ın sunduğu <strong>Load</strong> ve <strong>Save</strong> metodları ile, bir akışın yüklenmesi veya kayıt altına alınması işlemleri gerçekleştirilebilir. Bu akışlar XAML tabanlı dosyalardan gelebileceği gibi<em>(ki buna göre istediğimiz yerde bir Workflow Repository’ miz olabilir)</em> canlı çalışma zamanı Activity örnekleri de olabilir.</li>
<li><strong>XAML</strong> formatında saklanacak olan <strong>Workflow</strong> içeriklerinin <strong>çalışma zamanında(Runtime)</strong> yürütülebilmesi için elimizde <strong>ActivityXamlServices</strong> sınıfı bulunmaktadır.</li>
<li>Yüklenen bir <strong>Workflow’</strong> un asenkron olarak çalıştırılabilmesi sağlamak için de <strong>WorkflowApplication</strong> tipinden yararlanılabilir.</li>
</ol>
<p>Görüldüğü üzere elimizde hayal ettiğimiz gibi(?) bir <strong>Designer’</strong> ın geliştirilebilmesi için gerekli materyaller bulunmaktadır. Tabi ilgili düşüncenin gerçek bir ürün haline getirilmesi için epey bir çaba da sarf etmek gerekecektir.</p>
<p>Biz şu an için sadece giriş noktasını tasarladığımızı ve aslında <strong>Workflow Designer’</strong> ı <strong>Visual Studio IDE’</strong> si dışında çalıştırabildiğimizi ispatlarsak önemli bir aşamayı geçmiş olduğumuzu var sayabiliriz. Öyleyse gelin hiç vakit kaybetmeden işe koyulalım. Örneğimizi<strong> Visual Studio 2012 RC</strong> sürümü üzerinde geliştiriyor olacağız ancak <strong>Visual Studio 2010</strong> ortamında da test ettiğimizi ve çalıştırdığımızı ifade edebiliriz. Dolayısıyla kod parçalarını aynen <strong>Copy-Paste</strong> yöntemi ile 2010 ortamında da uygulatabilirsiniz.</p>
<p><strong>WPF</strong> uygulaması olarak geliştireceğimiz projemizde, aşağıdaki şekilde görülen ve sarı kutucuk içerisine alınmış referansların bulunması gerekmektedir.</p>
<p><a href="https://www.buraksenyurt.com/pics/rwd_1.png"><img style="margin: 4px 0px; display: inline;" title="rwd_1" src="/pics/rwd_1_thumb.png" alt="rwd_1" width="273" height="496" /></a></p>
<p>Dikkat edileceği üzere</p>
<p><strong>System.Activities</strong></p>
<p><strong>System.Activities.Core.Presentation </strong></p>
<p>ve <strong>System.Activities.Presentation </strong></p>
<p><strong>assembly’</strong> larının yüklenmesi yukarıda bahsettiğimiz temel <strong>Designer</strong> tipleri için gereklidir. Kendi IDE’ mizin ana ekranını oluşturacak <strong>MainWindow.xaml</strong> içeriğini ise aşağıdaki kod parçasında görüldüğü gibi tasarlayabiliriz.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Window x:Class="Designer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ING Composition Designer" Height="350" Width="800" WindowState="Maximized">
<Grid x:Name="grdScene" Background="Black">
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="4*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="1" Width="auto" Grid.ColumnSpan="3" Background="Black">
<Button Background="Black" BorderBrush="Pink" Content="Yeni" Foreground="Plum" Width="75" x:Name="btnNew" Click="btnNew_Click" Margin="5,5,5,5" />
<Button Background="Black" Foreground="Plum" BorderBrush="Pink" Content="Kaydet" x:Name="btnSave" Click="btnSave_Click" Margin="5,5,5,5" Width="75" />
<Button Background="Black" Foreground="Plum" BorderBrush="Pink" Content="Yükle" x:Name="btnLoad" Click="btnLoad_Click" Margin="5,5,5,5" Width="75" />
<Button Background="Black" BorderBrush="Pink" Content="Çalıştır" Foreground="Plum" Width="75" Margin="5,5,5,5" x:Name="btnRun" Click="btnRun_Click" />
</StackPanel>
<StackPanel Background="Black" Orientation="Horizontal" Grid.Row="2" Width="auto" Grid.ColumnSpan="3">
<Label x:Name="labelStatus" Foreground="PaleVioletRed" FontWeight="Bold" FontStyle="Italic" HorizontalAlignment="Right"/>
</StackPanel>
</Grid>
</Window></pre>
<p>Aslında tasarım olarak <strong>Visual Studio IDE’</strong> sini sadece ucundan andıran bir görselliğimiz bulunmakta <img class="wlEmoticon wlEmoticon-embarrassedsmile" style="border-style: none;" src="/pics/wlEmoticon-embarrassedsmile_2.png" alt="Embarrassed smile" /> Uygulamanın sol tarafında <strong>Toolbox’</strong> ımız, ortasında <strong>Workflow</strong> tasarımının yapılacağı alanımız ve en sağda bileşenlere ait <strong>Properties</strong> penceremiz bulunmaktadır. Şimdilik basit bir <strong>POC(Proof of Concept)</strong> çalışması olarak öngördüğümüzden temel fonksiyonelliklerimiz <em>(Load,Save,Run,New)</em> birer <strong>Button</strong> halinde pencerenin alt kısmında yer alacaktır. Olayı kafamızda daha iyi canlandırmak için uygulamamızın bitmiş halinin çalışma zamanındaki bir görüntüsüne bakalım arzu ederseniz.</p>
<p><a href="https://www.buraksenyurt.com/pics/rwd_2.png"><img style="margin: 4px 0px; display: inline;" title="rwd_2" src="/pics/rwd_2_thumb.png" alt="rwd_2" width="640" height="387" /></a></p>
<p>Sanırım bu ekran görüntüsüne bakınca biraz daha heyecanlanmış ve iştahlanmış olabilirsiniz yanılıyor muyum? <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_98.png" alt="Winking smile" /> O halde kod tarafında neler yaptığımıza bir bakalım. İşte kodlarımız.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Activities;
using System.Activities.Core.Presentation;
using System.Activities.Presentation;
using System.Activities.Presentation.Toolbox;
using System.Activities.Statements;
using System.Activities.XamlIntegration;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace Designer
{
// Illegal Cross Thread Exception' dan kaçmak için kullandığımız temsilci tipi
delegate void BindCompletionStateToLabelDelegate(WorkflowApplicationCompletedEventArgs state);
public partial class MainWindow
: Window
{
// WorkflowDesigner
private WorkflowDesigner wfDesigner;
private string currentFileName = String.Empty;
public MainWindow()
{
InitializeComponent();
RegisterMetadata();
AddDesigner(new Flowchart());
AddToolBox();
AddPropertyInspector();
}
// Designer için Metadata içeriği register edilir
private void RegisterMetadata()
{
DesignerMetadata dMetadata = new DesignerMetadata();
dMetadata.Register();
}
// Bir instance' dan WorkflowDesigner' ın yüklenilmesi sağlanır. Örneğin bir Flowchart bileşeni veya Sequence designer içerisine açılabilir.
private void AddDesigner(object instance)
{
wfDesigner = new WorkflowDesigner();
wfDesigner.Load(instance);
BindDesignerEvents(wfDesigner);
BindWfDesignerToGrid();
}
// WorkflowDesigner içeriğinin XAML tabanlı bir dosya içeriğindeki activity ile yüklenmesini sağlar
private void AddDesigner(string xamlFileName)
{
wfDesigner = new WorkflowDesigner();
wfDesigner.Load(xamlFileName);
BindDesignerEvents(wfDesigner);
BindWfDesignerToGrid();
}
// WorkflowDesigner' ın örnek olay metodlarını yükler
private void BindDesignerEvents(WorkflowDesigner wfDesigner)
{
wfDesigner.ModelChanged += (obj, e) =>
{
labelStatus.Content = "Model Changed";
};
}
// WorkflowDesigner bileşeninin WPF içeriğine element olarak eklenmesini sağlar
private void BindWfDesignerToGrid()
{
Grid.SetColumn(wfDesigner.View, 1);
if (Grid.GetColumn(wfDesigner.View) == 1)
grdScene.Children.Remove(wfDesigner.View);
grdScene.Children.Add(wfDesigner.View);
}
// Toolbox içeriğinin WPF penceresine eklenmesini sağlar
private void AddToolBox()
{
ToolboxControl control = GetToolboxControl();
Grid.SetColumn(control, 0);
grdScene.Children.Add(control);
}
// Örnek Workflow Component' lerinin Toolbox içeriğine eklenmesini sağlar
private ToolboxControl GetToolboxControl()
{
ToolboxControl tbxControl = new ToolboxControl();
#region Genel
ToolboxCategory tbxStandartControls = new ToolboxCategory("Genel");
ToolboxItemWrapper toolAssign = new ToolboxItemWrapper(typeof(Assign), "Atama");
ToolboxItemWrapper toolFlowDecision = new ToolboxItemWrapper(typeof(FlowDecision), "Karar");
ToolboxItemWrapper toolFlowchart = new ToolboxItemWrapper(typeof(Flowchart), "Akış Diagramı");
ToolboxItemWrapper toolIf = new ToolboxItemWrapper(typeof(If), "Eğer");
ToolboxItemWrapper toolSequence = new ToolboxItemWrapper(typeof(Sequence), "Sekans");
ToolboxItemWrapper toolDelay = new ToolboxItemWrapper(typeof(Delay), "Duraksat");
ToolboxItemWrapper toolDoWhile = new ToolboxItemWrapper(typeof(DoWhile), "Do While Döngü");
tbxStandartControls.Add(toolAssign);
tbxStandartControls.Add(toolSequence);
tbxStandartControls.Add(toolDelay);
tbxStandartControls.Add(toolDoWhile);
tbxStandartControls.Add(toolFlowchart);
tbxStandartControls.Add(toolFlowDecision);
tbxStandartControls.Add(toolIf);
#endregion
#region Adapterler
ToolboxCategory tbxAdapterControls = new ToolboxCategory("External Adapters");
tbxControl.Categories.Add(tbxStandartControls);
tbxControl.Categories.Add(tbxAdapterControls);
#endregion
return tbxControl;
}
// Seçilen Workflow bileşeni veya component için gerekli özelliklerin yüklenmesini sağlar
private void AddPropertyInspector()
{
if (Grid.GetColumn(wfDesigner.PropertyInspectorView) == 2)
{
grdScene.Children.Remove(wfDesigner.PropertyInspectorView);
}
else
{
Grid.SetColumn(wfDesigner.PropertyInspectorView, 2);
grdScene.Children.Add(wfDesigner.PropertyInspectorView);
}
}
// Tasarlanan Workflow içeriğinin XAML uzantılı olarak kayıt edilmesini sağlar
private void btnSave_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "XAML Files|*.xaml";
sfd.InitialDirectory = Environment.CurrentDirectory;
if (sfd.ShowDialog().Value == true)
{
wfDesigner.Save(sfd.FileName);
currentFileName = sfd.FileName;
labelStatus.Content = string.Format("{0}(Saved)", sfd.FileName);
}
}
// Workflow' un designer içerisine XAML tabanlı bir içerikten okunmasını sağlar
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "XAML Files|*.xaml";
ofd.InitialDirectory = Environment.CurrentDirectory;
if (ofd.ShowDialog().Value == true)
{
AddDesigner(ofd.FileName);
AddPropertyInspector();
currentFileName = ofd.FileName;
labelStatus.Content = string.Format("{0}(Loaded)", ofd.FileName);
}
}
// Boş bir Flowchart içerikli Workflow oluşturulmasını sağlar
private void btnNew_Click(object sender, RoutedEventArgs e)
{
AddDesigner(new Flowchart());
AddPropertyInspector();
labelStatus.Content = "Yeni Akışı";
currentFileName = String.Empty;
}
// Workflow' un Run edilmesi için kullanılır
private void btnRun_Click(object sender, RoutedEventArgs e)
{
try
{
Activity activity = LoadActivity(currentFileName);
ExecuteActivity(activity);
}
catch (Exception excp)
{
labelStatus.Content = string.Format("{0} nedeni ile activity başarılı bir şekilde çalıştırılamadı", excp.Message);
}
}
// Workflow icrasını gerçekleştirir
private void ExecuteActivity(Activity activity)
{
AutoResetEvent aReseter = new AutoResetEvent(false);
WorkflowApplication wfApp = new WorkflowApplication(activity);
wfApp.Completed += ea =>
{
aReseter.Set();
labelStatus
.Dispatcher
.Invoke(
new BindCompletionStateToLabelDelegate(BindCompletaionStateToLabel)
, ea
);
};
wfApp.Run();
aReseter.WaitOne();
}
// Illegal Cross Thread Exception' dan kaçtığımız ve Label kontrolüne diğer Thread içerisinden değiştirmemizi sağlayan metod
private void BindCompletaionStateToLabel(WorkflowApplicationCompletedEventArgs state)
{
labelStatus.Content = string.Format(
"{0} ID li akış için Completion State {1}"
, state.InstanceId, state.CompletionState
);
}
// XAML içeriğinden Activity bileşenini üretmek için kullanılır
private Activity LoadActivity(string currentFileName)
{
Activity loadedActivity = null;
if (!string.IsNullOrEmpty(currentFileName)
&& Path.GetExtension(currentFileName).ToUpper() == ".XAML")
{
loadedActivity = ActivityXamlServices.Load(currentFileName);
labelStatus.Content = string.Format(
"{0}({2}) run edilmek üzere {1} lokasyonundan yüklendi"
, loadedActivity.DisplayName
, currentFileName
, loadedActivity.Id
);
}
return loadedActivity;
}
}
}</pre>
<p>Kodlar uzun görünmesine rağmen çok karmaşık değildir. <strong>Designer’</strong> ın yüklenmesi veya designer içeriğinin kayıt edilmesi gibi işlevsellikler <strong>WorkflowDesigner</strong> tipi üzerinden gerçekleştirilebilmektedir. <strong>Toolbox</strong> içeriğinin yüklenmesi veya <strong>Properties</strong> penceresinin üretilmesi için gerekli tipler de başta belirttiğimiz gibidir. Örnek olması açısında basit bir kaç Workflow bileşeni yüklenmiştir<em>(Assign, Delay vb)</em> Burada önemli olan bir diğer nokta da Toolbox üzerinde istediğimiz gibi kategorilendirme yapabilmemizdir. Hatta <strong>ToolboxItemWrapper</strong> tipinin aşırı yükleniş yapıcılarına bakıldığında farklı şekillerde <strong>Component</strong> yükleyebileceğimizi de görebiliriz ki bu bize önemli bir esneklikte sağlayacaktır. <strong>Component’</strong> lerin herhangibir <strong>Repository’</strong> den alınması gibi. Bunun dışında kalan kısımlar aslında bakarsanız <strong>Workflow</strong> tiplerinin <strong>Runtime’</strong> daki davranışları için kullandığımız basit tiplerdir<em>(WorkflowApplication, ActivityXamlServices vb)</em></p>
<blockquote>
<p>Ne yazık ki yeni oluşturmak istediğimiz her Workflow örneği için WorkflowDesigner tipinin tekrardan örneklenmesi ve Toolbox ile Property penceresi içeriklerinin WPF Grid kontrolüne sıfırdan bağlanması gibi henüz aşmayı başaramadığım bazı zorunluluklar/aksaklıklar da bulunmaktadır.</p>
</blockquote>
<p>Aslında bu tip<strong> Visual Studio IDE’</strong> si dışında bir <strong>Designer</strong> geliştirmenin ne gibi artıları olabileceğini de bir düşünmemiz ve masaya koymamız gerekmektedir. Bunları aşağıdaki maddeler halinde sıralayabiliriz.</p>
<ol>
<li><strong>Visual Studio</strong> ürünü dışında daha basit içeriğe sahip olup örneğin sadece <strong>İş Analistlerini</strong> hedef alan bir araca sahip olabiliriz.</li>
<li><strong>Workflow Foundation</strong> içerisindeki tüm <strong>Component</strong> seti yerine sadece işe ve ihtiyaca yönelik bileşenlerin yer aldığı bir <strong>Toolbox’</strong> un ürün bazlı olarak sunulabilmesini sağlayabiliriz.</li>
<li>Söz konusu <strong>Toolbox </strong>içeriği <strong>yetkilendirilebilir(Authorization)</strong> ve ürünü kullananların pozisyonlarına göre yapabilecekleri sınırlanabilir.</li>
<li>Bir dezavantaj olarak görebileceğimiz <strong>Debug</strong> etme güçlüğüne karşılık ürünün <strong>Developer</strong> profili dışında kullanılacağı tezini öne sürebiliriz <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_39.png" alt="Smile" /></li>
<li>Geliştirilen araç, <strong>Visual Studio</strong> bağımsız bir ürün olarak düşünülüp lisanslanabilir veya ücretsiz olarak şirket içi çalışmalarda değerlendirilebilir.</li>
<li>Özellikle görsel yeteneğe sahip <strong>Workflow Activity</strong> bileşenlerinin, bu aracın <strong>Visual Studio</strong> ile birlikte çalıştırılması halinde, <strong>Designer’</strong> a bağlandıklarındaki davranışlarının <strong>Debug</strong> edilmesi çok daha kolay olacaktır<em>(Acısını çok çektim o yüzden kulak verin bu avantajı yaban atmayın <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_39.png" alt="Smile" /> ) <br /></em></li>
</ol>
<p>Görüldüğü üzere <strong>Visual Studio IDE’</strong> sinin bir parçası olarak sunulan ve kullanılan <strong>Workflow Designer’</strong> ın harici bir ürün haline dönüştürülmesi son derece kolaydır. Umarım vizyonunuza değer katacak bir çalışma olmuştur. Bir başka yazımızda görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2012%2f7%2fReHostedWFDesigner.zip">ReHostedWFDesigner.zip (75,05 kb)</a> <br /><em>(Örnek Visual Studio 2012 RC sürümünde geliştirilmiştir ancak kodlar Visual Studio 2010 üzerinde de çalışmaktadır)</em></p>2012-07-12T23:05:00+00:00workflow foundation 4.0rehosted workflow designerxamlworkflowdesigneractivitycustom activity designercode activitynative activityworkflow contextbsenyurtAslına bakarsanız Visual Studio gerçekten harika bir IDE ortamı sunmaktadır. Hatta UX olarak bilien User eXperience değil de tam anlamıya Developer eXperience’ ın hat safhada olduğu bir geliştirme ortamıdır. Lakin genişletilebilir olması(Extension Manager’ a dikkatiniz çekmek isterim) haricinde çok gelişmiş özellikleri olmakla birlikte, zaman zaman daha hafif bir sürüme ihtiyaç duyabiliriz. Örneğin Workflow Foundation tabanlı olarak bir iş akışı tasarım uygulaması geliştirmek istediğinizi düşününhttps://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=9d333b2a-72a2-4bd7-8365-f3c54fe06a532https://www.buraksenyurt.com/trackback.axd?id=9d333b2a-72a2-4bd7-8365-f3c54fe06a53https://www.buraksenyurt.com/post/Developing-Re-Hosted-Workflow-Designer#commenthttps://www.buraksenyurt.com/syndication.axd?post=9d333b2a-72a2-4bd7-8365-f3c54fe06a53https://www.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://www.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://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=fe49fdcd-d7e7-42e8-96ab-a6b07914d4002https://www.buraksenyurt.com/trackback.axd?id=fe49fdcd-d7e7-42e8-96ab-a6b07914d400https://www.buraksenyurt.com/post/Custom-Activity-Designer-Gelistirmek#commenthttps://www.buraksenyurt.com/syndication.axd?post=fe49fdcd-d7e7-42e8-96ab-a6b07914d400https://www.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://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=8b2adca8-a31d-4936-9a07-371dbb3326cd0https://www.buraksenyurt.com/trackback.axd?id=8b2adca8-a31d-4936-9a07-371dbb3326cdhttps://www.buraksenyurt.com/post/Tek-Fotoluk-Ipucu-15#commenthttps://www.buraksenyurt.com/syndication.axd?post=8b2adca8-a31d-4936-9a07-371dbb3326cd