Lab Windows Workflow Foundation (VS2008) Zadanie 1- utworzenie prostej własnej czynności (custom activity). Otworzyć nowy projekt typu Workflow -> Workflow Activity Library W Properties ach zmień w polu Name nazwę Activity1 na np. WritelineActivity Wybrać z menu kontekstowego View Code Utworzymy nową własną czynność WritelineActivity wypisującą na konsolę treść przekazaną do niej w polu właściwości komunikat. Należy więc nadpisać metodę Execute: ///<summary> /// wykonuje aktywnosc - wyswietla komunikat na ekranie ///</summary> ///<param name="executioncontext"></param> ///<returns></returns> protected override ActivityExecutionStatus Execute(ActivityExecutionContext executioncontext) Console.WriteLine(komunikat); Console.WriteLine("wciśnij klawisz aby kontynuować wykonanie workflow..."); Console.ReadLine(); return ActivityExecutionStatus.Closed; /// <summary> /// get/set tresc komunikatu do wyswietlenia /// </summary> [Description("komunikat do wyswietlenia")] [Category("Parameters")] public string komunikat get return _mes; set _mes = value; private string _mes; Teraz dodamy w tym samym namespace nową klasę WritelineValidator sprawdzającą czy pole komunikat naszej czynności jest wypełnione jeżeli nie to ma się na ikonce naszej czynności zapalić czerwony wykrzyknik i pojawić błąd przy kompilacji. public class WritelineValidator : ActivityValidator public override ValidationErrorCollection Validate(ValidationManager manager, object obj) if (null == manager) throw new ArgumentNullException("manager"); if (null == obj) throw new ArgumentNullException("obj"); ValidationErrorCollection errors = base.validate(manager, obj); WritelineActivity act = obj as WritelineActivity; if (null!= act) if (null!= act.parent) if (string.isnullorempty(act.komunikat)) errors.add(validationerror.getnotsetvalidationerror("komunikat")); return errors;
Tak zdefiniowany validator należy załączyć do naszej klasy czynności: [ActivityValidator(typeof(WritelineValidator))] public partial class WritelineActivity : SequenceActivity public WritelineActivity() InitializeComponent();... Skompilować i usunąć ewentualne błędy. Aby sprawdzić jak wygląda i działa nasza czynność utworzymy w istniejącym solution nowy projekt typu Workflow->Sequential Workflow Console Application (jest to projekt z założoną aplikacją konsolowa hosta i pustym szablonem workflow). Na Toolbox ie prawym przyciskiem myszy aktywujemy menu kontekstowe i wybieramy AddTab tworząc nowa zakładkę kontrolek o nazwie np. moje kontrolki. Następnie w ramach tej zakładki podobnie wybieramy Choose Items - > zakładka Activities. Za pomocą Browse wskazujemy położenie pliku dll zawierającego naszą czynność ->OK. Nacza kontrolka czynności została dodana do listy kontrolek w toolbox ie pod nazwą WritelineActivity. Po przeciągnięciu jej do pustego workflow otrzymujemy postać: Teraz zajmiemy się sposobem w jaki nasza czynność ma być prezentowana graficznie wracamy do projektu kontrolki WritelineActivity: Ponieważ korzystamy z gradientu liniowego należy dodać w jej kodzie namespace System.Drawing.Drawing2D; oraz klasę definiującą jej postać graficzną w oknie szablonu workflow. public class WritelineTheme : ActivityDesignerTheme ///<summary> /// tworzy temat i ustawia mu pewne wartości ///</summery> ///<param name="theme"></param> public WritelineTheme(WorkflowTheme theme) : base(theme) this.bordercolor = Color.Green; this.backcolorstart = Color.Yellow; this.backcolorend = Color.Orange; this.backgroundstyle = LinearGradientMode.ForwardDiagonal;
[ActivityDesignerTheme(typeof(WritelineTheme))] public class WritelineDesigner : ActivityDesigner Należy jeszcze związać klasę WritelineDesigner z nasza klasą czynności : [ActivityValidator(typeof(WritelineValidator))] [Designer(typeof(WritelineDesigner))] public partial class WritelineActivity: SequenceActivity Skompilować dll kę, przejść do projektu workflow konsole application i zobaczyć jak teraz prezentuje się nasza kontrolka w oknie projektanta workflow. Powinna wyglądać tak: Zadanie 2 utworzenie workflow akceptacji kredytów Opis procesu biznesowego akceptacji kredytów: Klient podaje następujące dane: kwote kredytu (w PLN) jaką chciałby otrzymać swój wiek symbol waluty w jakiej ma być udzielony kredyt (np. USD, EUR, PLN) na ile lat chciałby wziąść kredyt Kredytodawca ma przykładowo 2 minuty aby rozpatrzyć wniosek albo zdąży go rozpatrzyć albo informuje kredytobiorcę, że czas założony na rozpatrzenie wniosku minął.rozpatrzenie wniosku polega na analizie danych wejściowych i ocenie stopnia ryzyka związanego z udzieleniem kredytu w następujący sposób. Osoby ponizej (<=) 20 roku życia mogą uzyskać kredyt wyskości max 10 000 PLN. Następuje sprawdzenie jakiej kwoty żąda kredytobiorca. Jeżeli kwota żądana > 10000 PLN to ustawiany jest komunikat o odrzucieniu kredytu nie spełnienie wymogów kredytowania i wypisywana jest na konsolę informacja Jestś za młody na tak dużą kwotę. Jeżeli kwota żądana <= 10 000 PLN to sprawdzamy czy przypadkiem kilient nie chce aby kredyt został udzielony w obcej walucie. Jeżeli w obcej walucie to należy uzyskać wartość przelicznika dla danej waluty (z WebService) i wyliczyć kwotę żądanego kredytu w danej walucie. Na bazie kwoty żadanego kredytu (odpowiednio w PLN lub obcej walucie) i zakładanego okresu spłaty (w latach) wyliczana jest rata miesięczna do spłacania. Generowany jest komunikat do kredytobiorcy o pomyślnym rozpatrzeniu wniosku kredytowego. Osoby starsze (wiek>20 lat) otrzymują komunikat o odrzucieniu kredytu nie spełnienie wymogów kredytowania i wypisywana jest na konsolę informacja jesteś powyżel limitu wiekowego Klient otrzymuje następujące informacje zwrotne: Komentarz czy przydzielono czy nie kredyt; Kwota_kredytu_w_walucie Symbol waluty Żadana_kwota kredytu w PLN wiek Okeres_na ile lat wziął kredyt
Wysokość wyliczonej raty kredytu (w pln lub w zadanej walucie na wejściu) Status przydzielenia kredytu Kroki do wykonania: W projekcie typu Workflow->Sequential Workflow Console Application usuwamy naszą testowo wstawioną kontrolkę WritelineActivity i modyfikujmy program hosta (plik program.cs) o: wczytanie od kredytobiorcy wartości wejściowych przekazanie ich jako kolekcje parametrów do instancji workflow zwrócenie i wypisanie na konsole parametrów wyjściowych instancji workflow Powinien przyjąć postać: static void Main(string[] args) try //zdefiniowanie i wczytanie wartości startowych dla instancji workflow double kwota_kr = 0; int wiek = 0; int okres = 0; Console.WriteLine("podaj parametry dla wykonania workflow przydziału kredytu "); Console.Write("kwota kredytu w PLN:"); double.tryparse(console.readline(), out kwota_kr); Console.Write("wiek:"); int.tryparse(console.readline(), out wiek); Console.Write("symbol waluty w jakiej ma być udzielony kredyt:"); string waluta = Console.ReadLine(); Console.Write("na ile lat brany kredyt: "); int.tryparse(console.readline(), out okres); //utoworzenie kolekcji parametrów Dictionary<string, object> parametry = new Dictionary<string, object>(); parametry.add("kwota_kredytu", kwota_kr); parametry.add("wiek", wiek); parametry.add("okres", okres); parametry.add("symbol_waluty", waluta); //powołanie do życia runtime u i podpięcie funkcji obsługi zdarzeń utworzono, zakończono pomyślnie i zakończono z błędem wykonanie workflow using (WorkflowRuntime workflowruntime = new WorkflowRuntime()) Console.WriteLine("Runtime Started."); AutoResetEvent waithandle = new AutoResetEvent(false); workflowruntime.workflowcompleted += delegate(object sender, WorkflowCompletedEventArgs e) waithandle.set(); Console.WriteLine("parametry wyjściowe z workflow daj kredyt"); //kolekcja ta zawiera wszystkie publiczne właściwości zdefiniowane w workflow foreach (KeyValuePair<string, object> par in e.outputparameters) Console.WriteLine("0 =1", par.key, par.value); Console.ReadLine(); ; workflowruntime.workflowterminated += delegate(object sender, WorkflowTerminatedEventArgs e) Console.WriteLine(e.Exception.Message); Console.ReadLine(); waithandle.set(); ; //powołanie do życia instancji workflow i przekazanie kolekcji parametrów
//typeof(namespace.nazwa_klasy z pliku workflow1.cs definiującego szablon naszego workflow) WorkflowInstance instance = workflowruntime.createworkflow(typeof(workflowconsoleapplication1.workflow1), parametry); //uruchomienie wykonywania instancji instance.start(); // Wait for the event to be signaled waithandle.waitone(); workflowruntime.stopruntime(); Console.WriteLine("Program Complete."); Console.ReadLine(); catch (Exception exception) Console.WriteLine("Exception occured: " + exception.message); Console.ReadLine(); Teraz kolej na zdefiniowanie naszego workflow przez kolejne umieszczanie elementów z toolboxa i definiowanie ich właściwości: 1. Ponieważ nasz workflow ma się zakończyć gdy minie określony czas lub też dokonamy rozpatrzenia wniosku w czasie krótszym więc dodajemy czynność ListenActivity (każda z gałęzi musi zawierać czynność sterowaną zdarzeniem). W lewej gałęzi zdefiniujemy: oczekiwanie na upłynięcie czasu maksymalnego (2 minuty) delayactivity TimeoutDuration = 00:02:00, w którym to powinien być rozpatrzony wniosek czynność Code, dla której ustawimy pole name = powiadomienie, ExecuteCode= minal_czas (jest to przypisanie funkcji jaka ma się wykonać na wołanie metody ExecuteCode dla tej czynności)- zostanie utworzony w kodzie szablon tej metody tworzymy zawartość metody minal_czas ustawiającej zawartość informacji w polu komunikat private void minal_czas(object sender, EventArgs e) komentarz = "Minął czas przewidziany na udzielenie odpowiedzi - przepraszamy - zapraszamy ponownie "+ Status.ToString(); Ustawmy w kodzie naszego szablonu workflow (plik Workflow1.cs) wszystkie pola publiczne potrzebne do przechowywania przekazywanych parametrów wejściowych i umieszczenia parametrów wyjściowych public sealed partial class Workflow1: SequentialWorkflowActivity private int _wiek; private double _kwota_kredytu; private int _okres; private double _rata; private string _symbol_waluty; private string _komentarz; private double _kwota_kredytu_wal; [CategoryAttribute("Parameters")] public double kwota_kredytu_wal get return this._kwota_kredytu_wal; set this._kwota_kredytu_wal = value;
public string komentarz get return this._komentarz; set this._komentarz = value; public string symbol_waluty get return this._symbol_waluty; set this._symbol_waluty = value; public double kwota_kredytu get return this._kwota_kredytu; set this._kwota_kredytu = value; public int wiek get return this._wiek; set this._wiek = value; public int okres get return this._okres; set this._okres = value; public double rata get return this._rata; set this._rata = value; private StatusType statusvalue; public enum StatusType None, Approved, Rejected public StatusType Status get return this.statusvalue; set this.statusvalue = value; public double przelicznik_waluty; public Workflow1() InitializeComponent(); Status = StatusType.None; 2. W prawej gałęzi umieścimy również czynność delay z minimalnym czasem opoźnienienia = 1 sek oraz naszą własną kontrolkę WritelineActivity z komunikatem tu będzie akceptacja kredytu Powinniśmy otrzymać postać :
sprawdźmy jej wykonanie: 3. Teraz zajmiemy się analizą danych wejściowych podanych przez kredytobiorcę. Na początek w czynności IfElse sprawdzimy wiek kredytobiorcy. Umieszczamy czynność IfElse zamiennie za naszą WritelineActivity1. Zmieniamy jej nazwę w polu Name na wiek1. Dla ifelsebranchactivity1 modyfikujemy nazwe na mniej_niż20 pole Condition na CodeCondition i po rozwinięciu +Condition ustawiamy w polu Condition nazwę metody na sprawdz_wiek, która będzie definiować i sprawdzać warunek wejścia do lewej gałęzi. Szablon metody zostanie wygenerowany uzupełniamy go do postaci: private void sprawdz_wiek(object sender, ConditionalEventArgs e) e.result = (wiek <= 20); Warunku dla drugiej gałęzi IfElse nie ustawiamy działa domyślnie jak ELSE 4. W gałęzi ELSE czyli warunek wiek>20 umieścimy dwie czynności: Code nazwę zmieniamy na odmowa2 a pole ExecuteCode= odrzuc_kredyt
private void odrzuc_kredyt(object sender, EventArgs e) Status = StatusType.Rejected; komentarz = "Bardzo nam przykro ale nie spełnia Pan/Pani warunków kredytowania \n" + Status.ToString(); WritelineActivity z komunikatem jesteś powyżej limitu wiekowego Bieżąca postać workflow akceptacji wniosku kredytowego: 5. Uzupełnimy teraz lewą gałąź IfElse dla warunku wiek<=20. Należy umieścić kolejną czynność IfElse dla sprawdzenia czy zadana kwota kredytu <=10000. Ustawiamy dla niej Name = kwota_kred, i w lewej gałęzi dla ifelsebranchactivity1 name = mniej10tys oraz Condition=Code Condition, +Condition= spr_zadana_kwote private void spr_zadana_kwote(object sender, ConditionalEventArgs e) e.result = (kwota_kredytu <= 10000); W prawej gałęzi czynności (tj. kwota kredytu >10000) umieszczamy czynność code odmowa1 (wykonuje metodę odrzuc_kredyt) i WritelineActivity2 z komunikatem jesteś młody i chcesz zbyt dużą kwotę. Bieżąca postać to:
6. Teraz zajmiemy się lewą gałęzią - kwota kredytu <=10000. Z punktu biznesowego nie ma tu przeciwwskazań do udzielenia kredytu mimo, iż wiek jest młody to kwota jest niska i kredytodawca jest w stanie ponieść takie ryzyko. Zanim jednak obliczymy ratę kredytu i przyznamy go należy sprawdzić, czy czasem klient nie chce go uzyskać w walucie obcej a jeżeli tak to należy pozyskać przelicznik tej waluty. Umieszczamy kolejnego IfElse o nazwie w_walucie - z warunkiem spr_walute i nazwą warunku obca: private void spr_walute(object sender, ConditionalEventArgs e) e.result = (symbol_waluty.toupper()!= "PLN"); 7. Jeżeli mamy do czynienia z walutą obcą to wykorzystamy usługę sieciową do pozyskania przelicznika. Usługa dostępna pod adresem: http://www.webservicex.net/wcf/servicedetails.aspx?sid=18 Umieścimy w tym celu czynność InvokeWebService, wskazać powyższy adres w polu URL, kliknąć GO, dodać referencje webową o proponowanej nazwie. We właściwościach ustawić MethodName= ConversionRate. Dla pola FromCurrency double-click na żółtej ikonce właściwości wiązanej ->zakładka Bind to a new member -> Create Field utworzymy sobie nowe pole związane z tą właściwością o nazwie invokewebserviceactivity1_fromcurrency1 i potem podstawimy do niego odpowiednią wartość. W kodzie zostanie wygenerowany zapis: public WorkflowConsoleApplication3_usun.net.webservicex.www.Currency invokewebserviceactivity1_fromcurrency1 = new WorkflowConsoleApplication3_usun.net.webservicex.www.Currency(); Powtarzamy te czynności również dla pola ToCurrency tworząc pole związane invokewebserviceactivity1_tocurrency1 oraz dla (ReturnValue) wybieramy z okna bind to existing member pole przelicznik_waluty.
W kodzie odpowiednio: public WorkflowConsoleApplication3_usun.net.webservicex.www.Currency invokewebserviceactivity1_tocurrency1 = new WorkflowConsoleApplication3_usun.net.webservicex.www.Currency(); Teraz ustawimy dla pola Invoking nazwę metody, która ma być wykonana przed wywołaniem usługi. U nas Invoking = przypisz_wartosci zostanie utworzony szablon dla niej, który uzupełniamy do postaci: private void przypisz_wartosci(object sender, InvokeWebServiceEventArgs e) invokewebserviceactivity1_fromcurrency1 = WorkflowConsoleApplication1.net.webservicex.www.Currency.PLN; switch (symbol_waluty.toupper()) case "USD": invokewebserviceactivity1_tocurrency1 = WorkflowConsoleApplication1.net.webservicex.www.Currency.USD; break; case "EUR": invokewebserviceactivity1_tocurrency1 = WorkflowConsoleApplication1.net.webservicex.www.Currency.EUR; break; default: break; Gdzie WorkflowConsoleApplication1 jest namespacem, w którym jest szablon workflow i referencja (o nazwie net.webservicex.www) do definicji typu wyliczeniowego waluty -Currency 8. Teraz należałoby wyliczyć wartość kredytu w żądanej przez klienta walucie umieszczamy pod wołaniem usługi sieciowej kolejną czynność typu Code o nazwie kredyt_w_walucie2 i przypisujemy jej wykonanie metody kredyt_w_walucie o postaci: private void kredyt_w_walucie(object sender, EventArgs e) this.kwota_kredytu_wal = this._kwota_kredytu * przelicznik_waluty; 9. Na koniec należy wyliczyć ratę kredytu i poinformować o przyznaniu kredytu. jest to kontynuacja lewej gałęzi od czynności warunkowej o nazwie kwota_kredytu2 ale już poza warunkiem sprawdzającym walutę. Dodajemy czynność Code o nazwie wylicz_rate1 i przypisujemy jej wykonanie metody wylicz_rate o postaci: private void wylicz_rate(object sender, EventArgs e) if (symbol_waluty.toupper() == "PLN") rata = kwota_kredytu / (okres * 12); else rata = kwota_kredytu_wal / (okres * 12); Dodajemy czynność Code o nazwie przyznanie i przypisujemy jej wykonanie metody przyznaj_kredyt o postaci: private void przyznaj_kredyt(object sender, EventArgs e) Status = StatusType.Approved; komentarz = "Gratulujemy kredyt został przyznany " + Status.ToString(); 10. Otrzymana postać szablonu workflow dla rozpatrywania kredytu według przyjętego opisu procesu biznesowego ma postać:
Przeanalizować działanie spróbować zmodyfikować aplikacje hosta i uruchomić wiele instancji tego workflow. Rozwinięcie procesu biznesowego zadanie indywidualne (Poniższy opis modyfikuje proces w zakresie wiek>20 lat) Osoby powyżej (>) 20 roku życia mające miesięczny zarobek <=3500 PLN mogą uzuskać kredyt w kwocie max 50 000 PLN, a o zarobku >3500 PLN max 80 0000 PLN. W pozostałych przypadkach wołany jest ekspert indywidualnie oceniającego stopień ryzyka i podejmującego decyzję o przyznaniu lub odrzuceniu kredytu. Ekspert ma max 1 dzień na dokonanie ekspertyzy. Osobom powyżej 80 roku życia kredyt nie przysługuje (otrzymują komunikat o odrzucieniu kredytu nie spełnienie wymogów kredytowania i wypisywana jest na konsolę informacja jesteś powyżel limitu wiekowego )
Przykład zaimplementowania experta bankowego jako własnego Runtime Service komunikującego się z daną instancja workflow. Do definicji pól w Workflow1.cs dodać; private double miesieczny_zarobek; public double Miesieczny_zarobek get return miesieczny_zarobek; set miesieczny_zarobek = value; oraz definicję interfejsu experta bankowego (poza klasą Workflow1 ale w tym samym namespace) [ExternalDataExchange] public interface IExpertBankowy void ocenexperciewniosek(double kwota,double mies_zar); event EventHandler<ExternalDataEventArgs> opiniaexperta; Dodaj do projektu nowy plik klasy ExpertBankowy.cs oraz definicje klas przekazywanych argumentów między instancja, a hostem public class ExpertBankowy : IExpertBankowy public event EventHandler<ExternalDataEventArgs> opiniaexperta; public void ocenexperciewniosek(double kwota, double mies_zar) ThreadPool.QueueUserWorkItem(GetResponse, new ocenwniosekeventargs(workflowenvironment.workflowinstanceid, kwota, mies_zar)); private void GetResponse(object o) ocenwniosekeventargs args = o as ocenwniosekeventargs; double podzial = args.kwota/ args.mies_zar; Console.WriteLine("kwota przez zarobek wynosi" + podzial +"czy chcesz zatwierdzić pozyczkę? " ); // read the user's response from the command line char response = Console.ReadKey().KeyChar; Console.WriteLine(); // check the user's response // and raise the appropriate event if (response == 'y') opiniaexperta(null, new opiniaeventargs(args.instanceid,true)); else opiniaexperta(null, new opiniaeventargs(args.instanceid, false)); //def parametrów przekazywanych do metody usługi związanej wołanej z wewnątrz workflow [Serializable] public class ocenwniosekeventargs : ExternalDataEventArgs private double _kwota; public double Kwota
get return _kwota; set _kwota = value; private double _mies_zar; public double Mies_zar get return _mies_zar; set _mies_zar = value; public ocenwniosekeventargs(guid instanceid, double _mies_zar, double kwota) :base(instanceid) this._kwota = kwota; this._mies_zar = _mies_zar; //def parametrów przekazywanych z serwisu do wewnątrz workflow za pomocą generowanego zdarzenia (na które nasłuchujemy w workflow) [Serializable] public class opiniaeventargs : ExternalDataEventArgs private bool _przyznano; public bool Przyznano get return this._przyznano; public opiniaeventargs(guid instanceid, bool _przyznano) : base(instanceid) this._przyznano = _przyznano; W pliku program.cs należy dopisać pobranie z klawiatury kwoty miesięcznego zarobku kredytobiorcy: double zarobek = 0; Console.Write("twoj miesieczny zarobek:"); double.tryparse(console.readline(), out zarobek);... parametry.add("miesieczny_zarobek", zarobek); W tym samym pliku zanim powołamy do życia instancję naszego workflow należy dodać do usług naszą usługę experta bankowego ale jako usługę typu ExternalDataExchangeService //dodamy usługę do wymiany komunikacji do/z instancją workflow ExternalDataExchangeService edes = new ExternalDataExchangeService(); workflowruntime.addservice(edes); ExpertBankowy expertb = new ExpertBankowy(); edes.addservice(expertb); Teraz należy zmodyfikować nasz szablon workflow dodając w gałęzi warunku wieku między 20 a 80 CallExternalMethodActivity w celu wywołania metody ocenexperciewniosek z naszego serwisu ExpertBankowy. Należy dla niej wybrać InterfaceType -> IExpertBankowy, methodname= ocenexperciewniosek, oraz związać właściwość mies_zar z polem Miesięczny_zarobek (z naszego Workflw1) i kwota z kwota_kredytu (z naszego Workflow1). (patrz rys. poniżej)
Przekazaliśmy wniosek kredytowy do oceny eksperta bankowego teraz należy oczekiwać na jego odpowiedź. Dajemy mu 1 dzień na dokonanie ekspertyzy. Należy więc wstawić jako kolejną czynność nasłuchującą na zaistnienie zdarzenia ListenActivity (wykona się jedynie pierwsze zaistniałe zdarzenie). W jednej gałęzi dajemy DelayActivity z 1 dniem oczekiwania ( TimeoutDuration = 1.00:00:00) i informację o tym, że minął czas dla odpowiedzi eksperta wniosek kredytowy w statusie none. W drugiej gałęzi oczekujemy za pomocą HandleExternalEventActivity na nadejście powiadomienia od experta o dokonanej ekspertyzie z parametrów tego zdarzenia należy odczytać jaka zapadła decyzja (przyznany czy nie). Ustawiamy dla niej nazwę = czekaj_odpexperta, InterfaceType na
IExpertBankowy oraz EventName = opiniaexperta. Ustawiamy też nazwę metody jaka ma się wykonać po nadejściu zdarzenia (Invoked = przyszła_odp_exp). W kodzie tej metody sprawdzimy jaka jest nadesłana odpowiedź i ustawimy odpowiednio status dla kredytu. private void przyszła_odp_exp(object sender, ExternalDataEventArgs e) opiniaeventargs a = e as opiniaeventargs; if (a.przyznano) Status = StatusType.Approved; else Status = StatusType.Rejected; Console.WriteLine("Odpowiedź experta o przyznaniu kredytu: " + Status.ToString()); Zdebagować projekt Ogólny widok na układ projektu: