Udostępnij za pośrednictwem


Wprowadzenie dewelopera do programu Windows Workflow Foundation (WF) na platformie .NET 4

Matt Milner, Pluralsight

Listopad 2009 r.

Zaktualizowano do wydania: kwiecień 2010 r.

Przegląd

Ponieważ deweloperzy oprogramowania wiedzą, pisanie aplikacji może być trudne i stale szukamy narzędzi i struktur, aby uprościć proces i pomóc nam skupić się na wyzwaniach biznesowych, które próbujemy rozwiązać.  Przenieśliśmy się z pisania kodu w językach maszynowych, takich jak asemblera, do języków wyższego poziomu, takich jak C# i Visual Basic, które ułatwiają tworzenie aplikacji, usuwają problemy niższego poziomu, takie jak zarządzanie pamięcią, i zwiększają produktywność deweloperów.  W przypadku deweloperów firmy Microsoft przejście na platformę .NET umożliwia środowisku uruchomieniowemu języka wspólnego (CLR) przydzielanie pamięci, czyszczenie niepotrzebnych obiektów i obsługa konstrukcji niskiego poziomu, takich jak wskaźniki. 

Znaczna część złożoności aplikacji znajduje się w logice i przetwarzaniu, które odbywa się za kulisami.  Problemy, takie jak wykonywanie asynchroniczne lub równoległe i ogólnie koordynujące zadania reagowania na żądania użytkowników lub żądania obsługi, mogą szybko prowadzić deweloperów aplikacji z powrotem do niskiego poziomu kodowania dojść, wywołań zwrotnych, synchronizacji itp. Jako deweloperzy potrzebujemy tej samej mocy i elastyczności modelu programowania deklaratywnego dla wewnętrznych aplikacji, co w przypadku interfejsu użytkownika w programie Windows Presentation Foundation (WPF). Program Windows Workflow Foundation (WF) udostępnia strukturę deklaratywną do tworzenia logiki aplikacji i usługi oraz udostępnia deweloperom język wyższego poziomu do obsługi asynchronicznych, równoległych zadań i innych złożonych przetwarzania.

Posiadanie środowiska uruchomieniowego do zarządzania pamięcią i obiektami uwolniło nas, aby skupić się bardziej na ważnych aspektach biznesowych pisania kodu.  Podobnie posiadanie środowiska uruchomieniowego, które może zarządzać złożonością koordynowania pracy asynchronicznej, zapewnia zestaw funkcji, które zwiększają produktywność deweloperów.  WF to zestaw narzędzi do deklarowania przepływu pracy (logiki biznesowej), działań ułatwiających zdefiniowanie przepływu logiki i sterowania oraz środowisko uruchomieniowe do wykonywania wynikowej definicji aplikacji.  Krótko mówiąc, WF dotyczy używania języka wyższego poziomu do pisania aplikacji, a celem jest zwiększenie produktywności deweloperów, łatwiejsze zarządzanie aplikacjami i szybsze wdrażanie zmian.  Środowisko uruchomieniowe WF nie tylko wykonuje przepływy pracy, ale także udostępnia usługi i funkcje ważne podczas pisania logiki aplikacji, takie jak trwałość stanu, zakładki i wznowienie logiki biznesowej, z których wszystkie prowadzą do elastyczności wątków i procesów, co umożliwia skalowanie w górę i skalowanie w poziomie procesów biznesowych. 

Aby uzyskać więcej informacji koncepcyjnych na temat korzystania z platformy WF do tworzenia aplikacji, można zwiększyć produktywność, polecam przeczytanie "The Workflow Way" autorstwa Davida Chappella, znajdującego się w sekcji Dodatkowe zasoby. 

Co nowego w programie WF4

W wersji 4 programu Microsoft® .NET Framework program Windows Workflow Foundation wprowadza znaczną zmianę z poprzednich wersji technologii dostarczanych w ramach platformy .NET 3.0 i 3.5.  W rzeczywistości zespół ponownie zwrócił się do rdzenia modelu programowania, środowiska uruchomieniowego i narzędzi i ponownie zaprojektował każdy z nich, aby zwiększyć wydajność i produktywność, a także rozwiązać ważne opinie zebrane z zaangażowania klientów przy użyciu poprzednich wersji.  Wprowadzono istotne zmiany, aby zapewnić najlepsze środowisko dla deweloperów przyjmujących platformę WF i umożliwić usłudze WF dalsze tworzenie podstawowych składników, które można wykorzystać w aplikacjach. Przedstawię tutaj zmiany wysokiego poziomu, a w całym dokumencie każdy temat uzyska bardziej szczegółowe leczenie. 

Przed kontynuowaniem ważne jest, aby zrozumieć, że zgodność z poprzednimi wersjami była również kluczowym celem w tej wersji.  Nowe składniki struktury znajdują się głównie w zestawach System.Activities.* podczas gdy składniki struktury zgodne z poprzednimi wersjami znajdują się w zestawach System.Workflow.*.  Zestawy System.Workflow.* są częścią programu .NET Framework 4 i zapewniają pełną zgodność z poprzednimi wersjami, dzięki czemu można migrować aplikację do platformy .NET 4 bez zmian w kodzie przepływu pracy. W tym dokumencie użyję nazwy WF4, aby odwołać się do nowych składników znajdujących się w zestawach System.Activities.* i WF3, aby odwoływać się do składników znalezionych w zestawach System.Workflow.* . 

Projektantów

Jednym z najbardziej widocznych obszarów poprawy jest projektant przepływu pracy. Użyteczność i wydajność były kluczowymi celami dla zespołu w wersji VS 2010.  Projektant obsługuje teraz możliwość pracy z znacznie większymi przepływami pracy bez obniżenia wydajności i projektantów są oparte na Windows Presentation Foundation (WPF), korzystając w pełni z bogatego środowiska użytkownika, które można utworzyć za pomocą deklaratywnej struktury interfejsu użytkownika.  Deweloperzy działań będą używać języka XAML do definiowania sposobu, w jaki ich działania wyglądają i wchodzą w interakcje z użytkownikami w środowisku projektowania wizualnego.  Ponadto ponowne hostowanie projektanta przepływu pracy we własnych aplikacjach w celu umożliwienia deweloperom wyświetlania przepływów pracy i interakcji z nimi jest teraz znacznie łatwiejsze. 

Przepływ danych

W programie WF3 przepływ danych w przepływie pracy był nieprzezroczystym.  Platforma WF4 zapewnia przejrzysty, zwięzły model przepływu danych i określania zakresu przy użyciu argumentów i zmiennych.  Te pojęcia, znane wszystkim deweloperom, upraszczają zarówno definicję magazynu danych, jak i przepływ danych do i z przepływów pracy oraz działań.  Model przepływu danych sprawia również, że bardziej oczywiste są oczekiwane dane wejściowe i wyjściowe danego działania oraz poprawia wydajność środowiska uruchomieniowego, ponieważ dane są łatwiej zarządzane. 

Schemat blokowy

Dodano nowe działanie przepływu sterowania o nazwie Schemat blokowy, aby umożliwić deweloperom używanie modelu schematu blokowego do definiowania przepływu pracy.  Schemat blokowy bardziej przypomina pojęcia i procesy myślowe, które wielu analityków i deweloperów przechodzi podczas tworzenia rozwiązań lub projektowania procesów biznesowych.  W związku z tym warto zapewnić działanie, aby ułatwić modelowanie koncepcji i planowania, które zostało już wykonane.  Schemat blokowy umożliwia takie koncepcje jak powrót do poprzednich kroków i dzielenie logiki na podstawie jednego warunku lub logiki przełączenia/przypadku. 

Model programowania

Model programowania WF został odnowiony, aby był zarówno prostszy, jak i bardziej niezawodny.  Działanie jest podstawowym typem podstawowym w modelu programowania i reprezentuje zarówno przepływy pracy, jak i działania.  Ponadto nie trzeba już tworzyć przepływu pracyRuntime w celu wywołania przepływu pracy, można po prostu utworzyć wystąpienie i wykonać je, upraszczając scenariusze testowania jednostkowego i aplikacji, w których nie chcesz przechodzić przez problemy z konfigurowaniem określonego środowiska.  Na koniec model programowania przepływu pracy staje się w pełni deklaratywnym składem działań, bez konieczności programowania kodu, upraszczając tworzenie przepływu pracy.

Integracja z programem Windows Communication Foundation (WCF)

Korzyści wynikające z platformy WF na pewno mają zastosowanie zarówno do tworzenia usług, jak i korzystania z usług lub koordynowania interakcji z usługą.  Wiele wysiłków poszło w celu zwiększenia integracji między usługami WCF i WF.  Nowe działania obsługi komunikatów, korelacja komunikatów i ulepszona obsługa hostingu wraz z w pełni deklaratywną definicją usługi są głównymi obszarami poprawy. 

Wprowadzenie do przepływu pracy

Najlepszym sposobem zrozumienia rozwiązania WF jest rozpoczęcie korzystania z niego i zastosowanie pojęć.  Omówię kilka podstawowych pojęć związanych z przepływem pracy, a następnie omówimy tworzenie kilku prostych przepływów pracy, aby zilustrować, jak te koncepcje odnoszą się do siebie nawzajem. 

Struktura przepływu pracy

Działania to bloki konstrukcyjne programu WF, a wszystkie działania ostatecznie pochodzą z działania.  Uwaga dotycząca terminologii — działania są jednostką pracy w programie WF. Działania można tworzyć razem w większe działania. Gdy działanie jest używane jako punkt wejścia najwyższego poziomu, jest nazywane "przepływem pracy", podobnie jak Main jest po prostu inną funkcją, która reprezentuje punkt wejścia najwyższego poziomu do programów CLR.   Na przykład rysunek 1 przedstawia prosty przepływ pracy kompilowany w kodzie. 

Sekwencja s = nowa sekwencja

{

    Działania = {

        new WriteLine {Text = "Hello"},

        nowa sekwencja {

            Działania =

            {

                new WriteLine {Text = "Workflow"},

                new WriteLine {Text = "World"}

            }

        }

    }

};

Rysunek 1. Prosty przepływ pracy

Zwróć uwagę na rysunek 1, że działanie Sekwencja jest używane jako działanie główne do zdefiniowania stylu przepływu sterowania głównego dla przepływu pracy. Dowolne działanie może być używane jako element główny lub przepływ pracy i wykonywane, nawet proste polecenie WriteLine.   Ustawiając właściwość Działania w sekwencji z kolekcją innych działań, zdefiniowałem strukturę przepływu pracy.  Ponadto działania podrzędne mogą mieć działania podrzędne, tworząc drzewo działań tworzących ogólną definicję przepływu pracy. 

Szablony przepływów pracy i projektant przepływu pracy

Program WF4 jest dostarczany z wieloma działaniami, a program Visual Studio 2010 zawiera szablon do definiowania działań. Dwa najbardziej typowe działania używane w głównym przepływie sterowania to Sekwencja i Schemat blokowy.  Chociaż dowolne działanie można wykonać jako przepływ pracy, te dwa zapewniają najbardziej typowe wzorce projektowe do definiowania logiki biznesowej.   Rysunek 2 przedstawia przykład modelu sekwencyjnego przepływu pracy definiującego przetwarzanie odebranego zamówienia, zapisane, a następnie powiadomienia wysyłane do innych usług.   

 

Rysunek 2. Sekwencyjny projekt przepływu pracy

Typ przepływu pracy schematu blokowego jest wprowadzany w programie WF4 w celu rozwiązywania typowych żądań od istniejących użytkowników, takich jak możliwość powrotu do poprzednich kroków w przepływie pracy i dlatego, że bardziej przypomina projekt koncepcyjny wykonany przez analityków i deweloperów pracujących nad definiowaniem logiki biznesowej.  Rozważmy na przykład scenariusz obejmujący wprowadzanie danych przez użytkownika do aplikacji.  W odpowiedzi na dane dostarczone przez użytkownika program powinien kontynuować proces lub wrócić do poprzedniego kroku, aby ponownie wyświetlić monit o podanie danych wejściowych. W przypadku sekwencyjnego przepływu pracy może to obejmować coś podobnego do przedstawionego na rysunku 3, w którym działanie DoWhile jest używane do kontynuowania przetwarzania do momentu spełnienia pewnego warunku. 

Rysunek 3. Sekwencja rozgałęziania decyzji

Projekt na rysunku 3 działa, ale jako deweloper lub analityk przeglądający przepływ pracy model nie reprezentuje oryginalnej logiki ani wymagań, które zostały opisane.   Przepływ pracy schematu blokowego na rysunku 4 daje podobny wynik techniczny do modelu sekwencyjnego używanego na rysunku 3, ale projekt przepływu pracy ściślej pasuje do myślenia i wymagań zgodnie z pierwotnym definicją. 

Rysunek 4. Przepływ pracy schematu blokowego

Przepływ danych w przepływach pracy

Pierwszą myślą, że większość osób myśli o przepływie pracy, to proces biznesowy lub przepływ aplikacji.  Jednak tak samo krytyczne jak przepływ to dane, które napędzają proces i informacje, które są zbierane i przechowywane podczas wykonywania przepływu pracy.  WF4 magazyn danych i zarządzanie nimi były głównym obszarem projektowania. 

Istnieją trzy główne pojęcia, które należy zrozumieć w odniesieniu do danych: Zmienne, Argumenty i Wyrażenia.  Proste definicje dla każdego z nich to zmienne do przechowywania danych, argumenty służą do przekazywania danych, a wyrażenia służą do manipulowania danymi. 

Zmienne — przechowywanie danych

Zmienne w przepływach pracy są bardzo podobne do zmiennych używanych w językach imperatywnych: opisują nazwaną lokalizację danych do przechowywania i przestrzegają określonych reguł określania zakresu.  Aby utworzyć zmienną, należy najpierw określić zakres, w jakim zmienna musi być dostępna.  Podobnie jak w przypadku zmiennych w kodzie, które są dostępne na poziomie klasy lub metody, zmienne przepływu pracy można definiować w różnych zakresach.  Rozważmy przepływ pracy na rysunku 5.  W tym przykładzie można zdefiniować zmienną na poziomie głównym przepływu pracy lub w zakresie zdefiniowanym przez działanie sekwencji Zbieraj dane źródła danych. 

Rysunek 5. Zmienne w zakresie działań

Argumenty — przekazywanie danych

Argumenty są definiowane dla działań i definiują przepływ danych do i z działania.  Argumenty działań można traktować podobnie jak argumenty do metod w kodzie imperatywnego.  Argumenty mogą mieć wartość In, Out lub In/Out i mają nazwę i typ.  Podczas tworzenia przepływu pracy można zdefiniować argumenty dla działania głównego, co umożliwia przekazywanie danych do przepływu pracy podczas wywoływanego przepływu pracy.  Argumenty w przepływie pracy są definiowane tak samo jak zmienne przy użyciu okna argumentów. 

Podczas dodawania działań do przepływu pracy należy skonfigurować argumenty dla działań i jest to wykonywane głównie przez odwoływanie się do zmiennych w zakresie lub przy użyciu wyrażeń, które omówię dalej.  W rzeczywistości klasa bazowa Argument zawiera właściwość Expression, która jest działaniem, które zwraca wartość typu argumentu, więc wszystkie te opcje są powiązane i polegają na działaniu. 

W przypadku używania projektanta przepływu pracy do edytowania argumentów można wpisać wyrażenia reprezentujące wartości literału w siatce właściwości lub użyć nazw zmiennych, aby odwołać się do zmiennej w zakresie, jak pokazano na rysunku 6, gdzie emailResult i emailAddress są zmiennymi zdefiniowanymi w przepływie pracy. 

Rysunek 6. Konfigurowanie argumentów dotyczących działań

Wyrażenia — działanie na danych

Wyrażenia to działania, których można użyć w przepływie pracy do działania na danych.  Wyrażenia mogą być używane w miejscach, w których używasz działania i interesują się wartością zwracaną, co oznacza, że można użyć wyrażeń w argumentach ustawiania lub definiowania warunków dotyczących działań, takich jak działanie While lub If.  Należy pamiętać, że większość elementów w programie WF4 wynika z działania i wyrażeń nie różni się, są pochodną działania<TResult> co oznacza, że zwracają wartość określonego typu.  Ponadto WF4 zawiera kilka typowych wyrażeń do odwoływania się do zmiennych i argumentów, a także wyrażeń Języka Visual Basic.  Ze względu na tę warstwę wyspecjalizowanych wyrażeń wyrażenia używane do definiowania argumentów mogą zawierać kod, wartości literału i odwołania do zmiennych.  Tabela 1 zawiera małe próbkowanie typów wyrażeń, których można użyć podczas definiowania argumentów przy użyciu projektanta przepływu pracy.  Podczas tworzenia wyrażeń w kodzie istnieje kilka innych opcji, w tym użycie wyrażeń lambda. 

Wyrażenie Typ wyrażenia

"hello world"

Wartość ciągu literału

10

Wartość literału int32

System.String.Concat("hello", " ", "world")

Wywołanie metody imperatywnej

"hello" & "world"

Wyrażenie Języka Visual Basic

argInputString

Odwołanie do argumentu (nazwa argumentu)

varResult

Odwołanie do zmiennej (nazwa zmiennej)

"hello: " & argInputString

Literały i argumenty/zmienne mieszane

Tabela 1: Przykładowe wyrażenia

Tworzenie pierwszego przepływu pracy

Teraz, gdy omówiliśmy podstawowe pojęcia związane z przepływem działań i danych, mogę utworzyć przepływ pracy przy użyciu tych pojęć.  Zacznę od prostego przepływu pracy hello world, aby skupić się na pojęciach, a nie prawdziwej wartości propozycji WF.  Aby rozpocząć, utwórz nowy projekt testów jednostkowych w programie Visual Studio 2010.  Aby użyć rdzenia platformy WF, dodaj odwołanie do zestawu System.Activities i dodaj instrukcje using dla elementu System.Activities, System.Activities.Statements i System.IO w pliku klasy testowej.  Następnie dodaj metodę testową, jak pokazano na rysunku 7, aby utworzyć podstawowy przepływ pracy i wykonać go. 

[TestMethod]

public void TestHelloWorldStatic()

{

    StringWriter writer = new StringWriter();

    Console.SetOut(writer);

    Sekwencja wf = nowa sekwencja

    {

        Działania = {

            new WriteLine {Text = "Hello"},

            new WriteLine {Text = "World"}

        }

    };

    WorkflowInvoker.Invoke(wf);

    Assert.IsTrue(String.Compare(

        "Witaj,\r\nWorld\r\n",

        pisarz. GetStringBuilder(). ToString()) == 0,

        "Nieprawidłowy ciąg zapisany");

}

Rysunek 7. Tworzenie świata hello world w kodzie

Właściwość Text w działaniu WriteLine jest ciągiem InArgument<>, a w tym przykładzie przekazano wartość literału do tej właściwości.

Następnym krokiem jest zaktualizowanie tego przepływu pracy w celu użycia zmiennych i przekazanie tych zmiennych do argumentów działania.  Rysunek 8 przedstawia nowy test zaktualizowany w celu używania zmiennych.

[TestMethod]

public void TestHelloWorldVariables()

{

    StringWriter writer = new StringWriter();

    Console.SetOut(writer);

Sekwencja wf = nowa sekwencja

{

    Zmienne = {

        nowy ciąg<zmiennej>{Default = "Hello", Name = "greeting"},

        new Variable<string> { Default = "Bill", Name = "name" } },

        Działania = {

            new WriteLine { Text = new VisualBasicValue<ciąg>("greeting"),

            new WriteLine { Text = new VisualBasicValue<ciąg>(

            "name + \"Gates\"")}

        }

    };

    WorkflowInvoker.Invoke(wf);

}

Rysunek 8. Przepływ pracy kodu ze zmiennymi

W tym przypadku zmienne są definiowane jako typ Zmienna<ciąg> i podane wartości domyślnej.  Zmienne są deklarowane w działaniu Sekwencja, a następnie odwołuje się do argumentu Text dwóch działań.  Alternatywnie można użyć wyrażeń, aby zainicjować zmienne, jak pokazano na rysunku 9, gdzie klasa TResult<TResult><TResult służy do przekazywania ciągu reprezentującego wyrażenie.  W pierwszym przypadku wyrażenie odnosi się do nazwy zmiennej, a w drugim przypadku zmienna jest połączona z wartością literału.  Składnia używana w wyrażeniach tekstowych to Visual Basic, nawet podczas pisania kodu w języku C#.

Działania = {

    new WriteLine { Text = new VisualBasicValue<ciąg>("greeting"),

        TextWriter = writer },

    new WriteLine { Text = new VisualBasicValue<ciąg>("name + \"Gates\""),

        TextWriter = writer }

}

Rysunek 9. Używanie wyrażeń do definiowania argumentów

Chociaż przykłady kodu pomagają zilustrować ważne kwestie i ogólnie czuć się komfortowo dla deweloperów, większość osób będzie używać projektanta do tworzenia przepływów pracy.  W projektancie przeciągasz działania na powierzchnię projektową i używasz zaktualizowanej, ale znanej siatki właściwości, aby ustawić argumenty.  Ponadto projektant przepływu pracy zawiera możliwe do rozwinięcia regiony u dołu, aby edytować argumenty dla przepływu pracy i zmiennych.   Aby utworzyć podobny przepływ pracy w projektancie, dodaj nowy projekt do rozwiązania, wybierając szablon Biblioteka działań, a następnie dodaj nowe działanie. 

Gdy projektant przepływu pracy jest wyświetlany po raz pierwszy, nie zawiera żadnych działań.  Pierwszym krokiem definiowania działania jest wybranie działania głównego.  W tym przykładzie dodaj działanie Schemat blokowy do projektanta, przeciągając je z kategorii Schemat blokowy w przyborniku.  W projektancie schematu blokowego przeciągnij dwa działania writeLine z przybornika i dodaj je jeden poniżej drugiego do schematu blokowego.  Teraz musisz połączyć działania ze sobą, aby schemat blokowy znał ścieżkę do naśladowania.  W tym celu najpierw umieść kursor nad zielonym okręgiem "start" w górnej części schematu blokowego, aby wyświetlić uchwyty chwytania, a następnie kliknij i przeciągnij jeden do pierwszego elementu WriteLine i upuść go na uchwytu przeciągania, który pojawia się w górnej części działania.  Wykonaj to samo, aby połączyć pierwszy element WriteLine z drugim wierszem zapisu.  Powierzchnia projektowa powinna wyglądać jak Rysunek 10. 

Rysunek 10. Układ schematu blokowego hello

Po utworzeniu struktury należy skonfigurować pewne zmienne.  Klikając powierzchnię projektową, a następnie klikając przycisk Zmienne w dolnej części projektanta, możesz edytować kolekcję zmiennych dla schematu blokowego.  Dodaj dwie zmienne o nazwie "greeting" i "name", aby wyświetlić listę i ustawić wartości domyślne odpowiednio na "Hello" i "Bill" — pamiętaj, aby uwzględnić cudzysłowy podczas ustawiania wartości, ponieważ jest to wyrażenie, więc ciągi literału muszą być cytowane.  Rysunek 11 przedstawia okno zmiennych skonfigurowane z dwiema zmiennymi i ich wartościami domyślnymi. 

Rysunek 11. Zmienne zdefiniowane w projektancie przepływu pracy

Aby użyć tych zmiennych w działaniach WriteLine, wprowadź ciąg "greeting" (bez cudzysłowów) w siatce właściwości dla argumentu Text pierwszego działania i "name" (ponownie bez cudzysłowów) dla argumentu Text w drugim polem WriteLine.   W czasie wykonywania, gdy argumenty tekstowe są oceniane, wartość w zmiennej zostanie rozpoznana i użyta przez działanie WriteLine.

W projekcie testowym dodaj odwołanie do projektu zawierającego przepływ pracy.  Następnie możesz dodać metodę testową, taką jak pokazaną na rysunku 12, aby wywołać ten przepływ pracy i przetestować dane wyjściowe.  Na rysunku 12 widać, że przepływ pracy jest tworzony przez utworzenie wystąpienia utworzonej klasy.  W tym przypadku przepływ pracy został zdefiniowany w projektancie i skompilowany w klasie pochodnej z działania, które następnie można wywołać. 

[TestMethod]

public void TestHelloFlowChart()

{

    StringWriter tWriter = new StringWriter();

    Console.SetOut(tWriter);

    Workflows.HelloFlow wf = new Workflows.HelloFlow();

    WorkflowInvoker.Invoke(wf);

    Pominięto asercyjnie

}

Rysunek 12. Testowanie schematu blokowego

Do tej pory używałem zmiennych w przepływie pracy i używałem ich do podawania wartości argumentom w działaniach writeLine.  Przepływ pracy może również mieć zdefiniowane argumenty, które umożliwiają przekazywanie danych do przepływu pracy podczas wywoływania go i odbierania danych wyjściowych po zakończeniu przepływu pracy.  Aby zaktualizować schemat blokowy z poprzedniego przykładu, usuń zmienną "name" (wybierz ją i naciśnij Delete), a zamiast tego utwórz argument typu "name".  Można to zrobić w taki sam sposób, z wyjątkiem przycisku Argumenty, aby wyświetlić edytor argumentów.  Zwróć uwagę, że argumenty mogą również mieć kierunek i nie trzeba podawać wartości domyślnej, ponieważ wartość zostanie przekazana do działania w czasie wykonywania.  Ponieważ używasz tej samej nazwy argumentu, co w przypadku zmiennej, argumenty text działań WriteLine pozostają prawidłowe.  Teraz w czasie wykonywania te argumenty będą oceniać i rozpoznawać wartość argumentu "name" w przepływie pracy i używać tej wartości.  Dodaj dodatkowy argument ciągu typu z kierunkiem wyjścia i nazwą "fullGreeting"; zostanie zwrócony kod wywołujący. 

Rysunek 13. Definiowanie argumentów dla przepływu pracy

W schemacie blokowym dodaj działanie Przypisywanie i połącz je z ostatnim działaniem WriteLine.  W polu Argument To wprowadź wartość "fullGreeting" (bez cudzysłowów) i w polu Argument Value wprowadź wartość "greeting & name" (bez cudzysłowów). Spowoduje to przypisanie łączenia zmiennej powitania z argumentem name do argumentu wyjściowego fullGreeting. 

Teraz w teście jednostkowym zaktualizuj kod, aby podać argument podczas wywoływania przepływu pracy.  Argumenty są przekazywane do przepływu pracy jako ciąg słownika<,obiekt>, gdzie klucz jest nazwą argumentu.  Możesz to zrobić po prostu, zmieniając wywołanie w celu wywołania przepływu pracy, jak pokazano na rysunku 14. Zwróć uwagę, że argumenty wyjściowe są również zawarte w ciągu słownika<, obiekt> kolekcji kluczem w nazwie argumentu. 

IDictionary<ciąg, obiekt> results = WorkflowInvoker.Invoke(wf,

    new Dictionary<string,object> {

        {"name", "Bill" } }

    );

string outValue = results["fullGreeting"]. ToString();

Rysunek 14. Przekazywanie argumentów do przepływu pracy

Teraz, gdy znasz już podstawy sposobu łączenia działań, zmiennych i argumentów, poprowadzę Cię w przewodniku po działaniach zawartych w strukturze, aby umożliwić bardziej interesujące przepływy pracy skoncentrowane na logice biznesowej zamiast pojęć niskiego poziomu.

Przewodnik po palecie działań przepływu pracy

W przypadku dowolnego języka programowania oczekujesz, że masz podstawowe konstrukcje do definiowania logiki aplikacji.  W przypadku korzystania z platformy programistycznej wyższego poziomu, takiej jak WF, to oczekiwanie nie zmienia się.   Podobnie jak w przypadku instrukcji w językach .NET, takich jak If/Else, Switch i While, do zarządzania przepływem sterowania potrzebne są również te same możliwości podczas definiowania logiki w deklaratywnego przepływu pracy.  Te możliwości są dostępne w postaci podstawowej biblioteki działań dostarczanej ze strukturą.  W tej sekcji przedstawię ci krótki przewodnik po działaniach dostarczanych ze strukturą, aby dać ci pomysł funkcjonalności dostarczonej z pudełka.

Działania pierwotne i działania kolekcji

Podczas przechodzenia do modelu programowania deklaratywnego można zacząć zastanawiać się, jak wykonywać typowe zadania manipulowania obiektami, które są drugą naturą podczas pisania kodu.  W przypadku tych zadań, w których można samodzielnie pracować z obiektami i trzeba ustawić właściwości, wywołać polecenia lub zarządzać kolekcją elementów, istnieje zestaw działań zaprojektowanych specjalnie z tymi zadaniami. 

Aktywność Opis

Przypisać

Przypisuje wartość do lokalizacji — włączanie zmiennych ustawień.

Zwłoka

Opóźnia ścieżkę wykonywania przez określony czas.

InvokeMethod

Wywołuje metodę na obiekcie .NET lub metodzie statycznej w typie platformy .NET, opcjonalnie z zwracanym typem T.

WriteLine

Zapisuje określony tekst w składniku zapisywania tekstu — wartość domyślna to Console.Out

AddToCollection<T>

Dodaje element do wpisanej kolekcji.

RemoveFromCollection<T>

Usuwa element z wpisanej kolekcji.

ClearCollection<T>

Usuwa wszystkie elementy z kolekcji.

ExistsInCollection<T>

Zwraca wartość logiczną wskazującą, czy określony element istnieje w kolekcji. 

 

Działania przepływu sterowania

Podczas definiowania logiki biznesowej lub procesów biznesowych kontrola przepływu wykonywania ma kluczowe znaczenie. Działania przepływu sterowania obejmują podstawy, takie jak Sekwencja, która udostępnia wspólny kontener, gdy trzeba wykonać kroki w kolejności, oraz typową logikę rozgałęziania, taką jak działania If i Switch.  Działania przepływu sterowania obejmują również logikę pętli na podstawie danych (ForEach) i Warunków(While).  Najważniejsze dla uproszczenia złożonego programowania są działania równoległe, które umożliwiają wykonywanie wielu działań asynchronicznych w tym samym czasie. 

Aktywność Opis

Kolejność

Wykonywanie działań w serii

While/DoWhile

Wykonuje działanie podrzędne, gdy warunek (wyrażenie) ma wartość true

ForEach<T>

Iteruje w kolekcji z możliwością wyliczania i wykonuje działanie podrzędne raz dla każdego elementu w kolekcji, czekając na ukończenie działania podrzędnego przed rozpoczęciem następnej iteracji. Zapewnia wpisany dostęp do pojedynczego elementu, który umożliwia iterację w postaci nazwanego argumentu.

Jeśli

Wykonuje jedno z dwóch działań podrzędnych w zależności od wyniku warunku (wyrażenia).

Przełączanie<T>

Oblicza wyrażenie i planuje działanie podrzędne z pasującym kluczem. 

Równoległy

Planuje wszystkie działania podrzędne jednocześnie, ale także zapewnia warunek ukończenia, aby umożliwić działanie anulowania wszelkich zaległych działań podrzędnych, jeśli zostaną spełnione określone warunki. 

ParallelForEach<T>

Iteruje w kolekcji wyliczalnej i wykonuje działanie podrzędne raz dla każdego elementu w kolekcji, planując jednocześnie wszystkie wystąpienia. Podobnie jak forEach<T>, to działanie zapewnia dostęp do bieżącego elementu danych w postaci nazwanego argumentu.

Wybrać

Planuje wszystkie podrzędne działania PickBranch i anuluje wszystkie, ale pierwsze, aby jego wyzwalacz został ukończony.  Działanie PickBranch ma zarówno wyzwalacz, jak i akcję; każda z nich jest działaniem.  Po zakończeniu działania wyzwalacza wybranie anuluje wszystkie pozostałe działania podrzędne. 

 W dwóch poniższych przykładach pokazano kilka z tych działań w użyciu, aby zilustrować sposób tworzenia tych działań razem.  Pierwszy przykład, Rysunek 15, zawiera użycie parallelForEach<T> do używania listy adresów URL i asynchronicznego pobierania kanału informacyjnego RSS pod określonym adresem.  Po zwróceniu kanału informacyjnego<T> jest używany do iterowania elementów kanału informacyjnego i przetwarzania ich. 

Należy pamiętać, że kod deklaruje i definiuje wystąpienie VisualBasicSettings z odwołaniami do typów System.ServiceModel.Syndication.  Ten obiekt ustawień jest następnie używany podczas deklarowania klasy VisualBasicValue<wystąpień T> odwołujących się do typów zmiennych z tej przestrzeni nazw w celu włączenia rozpoznawania typów dla tych wyrażeń.  Należy również pamiętać, że treść działania ParallelForEach przyjmuje działanie ActivityAction, które zostało wymienione w sekcji dotyczącej tworzenia działań niestandardowych.  Te akcje korzystają z funkcji DelegateInArgument i DelegateOutArgument w taki sam sposób, w jaki działania używają metod InArgument i OutArgument. 

Rysunek 15. Działania przepływu sterowania

Drugi przykład, Rysunek 16, jest typowym wzorcem oczekiwania na odpowiedź z przekroczeniem limitu czasu.  Na przykład oczekiwanie na zatwierdzenie żądania przez menedżera i wysłanie przypomnienia, jeśli odpowiedź nie dotarła do przydzielonego czasu.  Działanie DoWhile powoduje powtórzenie oczekiwania na odpowiedź, gdy działanie Pick jest używane do wykonywania działania ManagerResponse i działania Delay w tym samym czasie co wyzwalacze.  Po pierwszym zakończeniu działania Opóźnienie jest wysyłane przypomnienie, a gdy działanie ManagerResponse zostanie ukończone jako pierwsze, flaga zostanie ustawiona na przerwanie pętli DoWhile. 

Rysunek 16. Wybieranie i wykonywanie działań w czasie

W przykładzie na rysunku 16 pokazano również, jak zakładki mogą być używane w przepływach pracy.  Zakładka jest tworzona przez działanie, aby oznaczyć miejsce w przepływie pracy, dzięki czemu przetwarzanie może wznawiać od tego momentu w późniejszym czasie.  Działanie Delay ma zakładkę, która jest wznawiana po upływie określonego czasu.  Działanie ManagerResponse to działanie niestandardowe, które oczekuje na dane wejściowe i wznawia przepływ pracy po nadejściu danych.  Działania obsługi komunikatów, omówione wkrótce, to główne działania związane z wykonywaniem zakładek.  Gdy przepływ pracy nie jest aktywnie przetwarzany, gdy oczekuje tylko na wznowienie zakładek, jest uważany za bezczynny i może być utrwalany w trwałym magazynie.  Zakładki zostaną omówione bardziej szczegółowo w sekcji dotyczącej tworzenia działań niestandardowych. 

Migracja

W przypadku deweloperów korzystających z platformy WF3 działanie międzyoperacyjne może odgrywać istotną rolę w ponownym wykorzystaniu istniejących zasobów. Działanie znalezione w zestawie System.Workflow.Runtime opakowuje istniejący typ działania i wyświetla właściwości działania jako argumenty w modelu WF4.  Ponieważ właściwości są argumentami, można użyć wyrażeń do zdefiniowania wartości.  Rysunek 17 przedstawia konfigurację działania Międzyoperajności w celu wywołania działania WF3.  Argumenty wejściowe są definiowane przy użyciu odwołań do zmiennych w zakresie w definicji przepływu pracy.    Działania wbudowane w WF3 miały właściwości zamiast argumentów, więc każda właściwość otrzymuje odpowiedni argument wejściowy i wyjściowy, co umożliwia odróżnienie danych wysyłanych do działania i danych, które mają zostać pobrane po wykonaniu działania. W nowym projekcie WF4 nie znajdziesz tego działania w przyborniku, ponieważ platforma docelowa jest ustawiona na profil klienta programu .NET Framework 4.  Zmień strukturę docelową we właściwościach projektu na .NET Framework 4, a działanie pojawi się w przyborniku.

Rysunek 17. Konfiguracja działań międzyoperaowych

Schemat blokowy

Podczas projektowania przepływów pracy schematu blokowego istnieje kilka konstrukcji, które mogą służyć do zarządzania przepływem wykonywania w schemacie blokowym.  Same konstrukcje te zapewniają proste kroki, proste punkty decyzyjne oparte na jednym warunku lub instrukcję switch.  Rzeczywiste możliwości schematu blokowego to możliwość łączenia tych typów węzłów z żądanym przepływem.

Konstruowanie/działanie Opis

Schemat blokowy

Kontener dla serii kroków przepływu, każdy krok przepływu może być dowolnym działaniem lub jedną z poniższych konstrukcji, ale do wykonania, musi być połączony w schemacie blokowym. 

FlowDecision

Zapewnia logikę rozgałęziania na podstawie warunku.

> T> Flow<Switch

Włącza wiele gałęzi na podstawie wartości wyrażenia. 

FlowStep

Reprezentuje krok w schemacie blokowym z możliwością łączenia się z innymi krokami. Ten typ nie jest wyświetlany w przyborniku, ponieważ jest niejawnie dodawany przez projektanta.  To działanie opakowuje inne działania w przepływie pracy i udostępnia semantyka nawigacji dla następnych kroków w przepływie pracy. 

Należy pamiętać, że chociaż istnieją konkretne działania dla modelu schematu blokowego, w przepływie pracy mogą być używane inne działania.  Podobnie działanie schematu blokowego można dodać do innego działania w celu zapewnienia semantyki wykonywania i projektowania schematu blokowego w ramach tego przepływu pracy.  Oznacza to, że możesz mieć sekwencję z kilkoma działaniami i schematem blokowym w środku. 

Działania związane z obsługą komunikatów

Jednym z głównych elementów w programie WF4 jest ściślejsza integracja między platformami WF i WCF.  Jeśli chodzi o przepływy pracy, oznacza to działania do modelowania operacji obsługi komunikatów, takich jak wysyłanie i odbieranie komunikatów.  Istnieje kilka różnych działań zawartych w strukturze obsługi komunikatów, z których każda ma nieco inne funkcje i przeznaczenie. 

Aktywność Opis

Wysyłanie/odbieranie

Jedno ze sposobów wysyłania lub odbierania wiadomości.  Te same działania składają się z interakcji w stylu żądania/odpowiedzi. 

ReceiveAndSendReply

Modeluje operację usługi, która odbiera komunikat i wysyła odpowiedź. 

SendAndReceiveReply

Wywołuje operację usługi i odbiera odpowiedź. 

InitializeCorrelation

Zezwalaj na jawne inicjowanie wartości korelacji w ramach logiki przepływu pracy, a nie wyodrębnianie wartości z komunikatu. 

CorrelationScope

Definiuje zakres wykonywania, w którym uchwyt korelacji jest dostępny do odbierania i wysyłania działań upraszczających konfigurację dojścia współużytkowanego przez kilka działań obsługi komunikatów. 

TransactedReceiveScope

Umożliwia uwzględnianie logiki przepływu pracy w tej samej transakcji przepływanej do operacji WCF przy użyciu działania Odbieranie.

Aby wywołać operację usługi z poziomu przepływu pracy, wykonaj znane kroki dodawania odwołania do usługi do projektu przepływu pracy.  System projektu w programie Visual Studio będzie następnie używać metadanych z usługi i utworzyć działanie niestandardowe dla każdej operacji usługi znalezionej w kontrakcie.  Można to traktować jako odpowiednik przepływu pracy serwera proxy WCF, który zostanie utworzony w projekcie języka C# lub Visual Basic.   Na przykład biorąc istniejącą usługę, która wyszukuje rezerwacje hotelowe z umową serwisową pokazaną na rysunku 18; dodanie odwołania do usługi daje działanie niestandardowe pokazane na rysunku 19. 

[ServiceContract]

interfejs publiczny IHotelService

{

    [OperationContract]

    Lista<HotelSearchResult> SearchHotels(

        HotelSearchRequest requestDetails);

}

Rysunek 18. kontraktu usługi

Rysunek 19. Niestandardowe działanie programu WCF

Więcej informacji na temat tworzenia przepływów pracy udostępnianych jako usługi WCF można znaleźć w sekcji Usługi przepływu pracy w dalszej części tego artykułu. 

Transakcje i obsługa błędów

Pisanie niezawodnych systemów może być trudne i wymaga, aby dobrze obsługiwać błędy i zarządzać, aby zachować spójny stan w aplikacji.  W przypadku przepływu pracy zakresy zarządzania obsługą wyjątków i transakcjami są modelowane przy użyciu działań z właściwościami typu ActivityAction lub Activity, aby umożliwić deweloperom modelowanie logiki przetwarzania błędów.  Poza tymi typowymi wzorcami spójności platforma WF4 obejmuje również działania, które umożliwiają modelowanie długotrwałej rozproszonej koordynacji poprzez kompensację i potwierdzenie. 

Aktywność Opis

Zakres anulowania

Służy do zezwalania deweloperowi przepływu pracy na reagowanie w przypadku anulowania treści pracy. 

TransactionScope

Umożliwia semantyka podobna do używania zakresu transakcji w kodzie przez wykonanie wszystkich działań podrzędnych w zakresie w ramach transakcji. 

TryCatch/Catch<T>

Służy do modelowania obsługi wyjątków i przechwytywania wpisanych wyjątków. 

Rzucać

Może służyć do zgłaszania wyjątku od działania.  

Rethrow

Służy do ponownego wznawiania wyjątku, zazwyczaj tego, który został przechwycony przy użyciu działania TryCatch. 

CompensableActivity

Definiuje logikę wykonywania działań podrzędnych, które mogą wymagać zrekompensowania akcji po powodzeniu.  Udostępnia symbol zastępczy logiki kompensacji, logiki potwierdzenia i obsługi anulowania.

Wyrównania

Wywołuje logikę obsługi wynagrodzeń dla działania, które można kompensować.

Potwierdzić

Wywołuje logikę potwierdzenia dla działania, które można zrównać. 

Działanie TryCatch umożliwia określanie zakresu zestawu pracy w celu przechwycenia wszelkich wyjątków, które mogą wystąpić, a działanie Catch<T> zapewnia kontener dla logiki obsługi wyjątków.  Możesz zobaczyć przykład użycia tego działania na rysunku 20, przy czym każdy typowany blok wyjątków jest definiowany przez działanie Catch<T>, zapewniając dostęp do wyjątku za pośrednictwem nazwanego argumentu widocznego po lewej stronie każdego przechwytu.   Jeśli pojawi się potrzeba, działanie Rethrow może służyć do ponownego wrócenia przechwyconego wyjątku lub działania Throw w celu zgłoszenia nowego wyjątku. 

Rysunek 20. Działanie TryCatch

Transakcje pomagają zapewnić spójność pracy krótkotrwałej, a działanie TransactionScope zapewnia ten sam rodzaj określania zakresu, który można uzyskać w kodzie platformy .NET przy użyciu klasy TransactionScope. 

Na koniec, gdy musisz zachować spójność, ale nie można użyć niepodzielnej transakcji w zasobach, możesz użyć rekompensaty.  Rekompensata umożliwia zdefiniowanie zestawu pracy, który, jeśli zostanie ukończony, może mieć zestaw działań, aby zrekompensować wprowadzone zmiany.  W prostym przykładzie rozważ działanie compensable na rysunku 21, w którym jest wysyłana wiadomość e-mail.  Jeśli po zakończeniu działania wystąpi wyjątek, można wywołać logikę kompensacji, aby przywrócić system do stanu spójnego; w takim przypadku monitu e-mail o wycofaniu wcześniejszej wiadomości. 

Rysunek 21. działania z możliwością wykonywania

Tworzenie i wykonywanie przepływów pracy

Podobnie jak w przypadku dowolnego języka programowania, istnieją dwie podstawowe czynności, które należy wykonać w przypadku przepływów pracy: definiowanie ich i wykonywanie.  W obu przypadkach platforma WF oferuje kilka opcji zapewniających elastyczność i kontrolę. 

Opcje projektowania przepływów pracy

Podczas projektowania lub definiowania przepływów pracy istnieją dwie główne opcje: kod lub XAML. Język XAML zapewnia prawdziwie deklaratywne środowisko i umożliwia zdefiniowanie całej definicji przepływu pracy w adiustacji XML, odwołując się do działań i typów utworzonych przy użyciu platformy .NET.  Większość deweloperów będzie prawdopodobnie używać projektanta przepływu pracy do tworzenia przepływów pracy, co spowoduje deklaratywną definicję przepływu pracy XAML.  Ponieważ język XAML jest tylko kodem XML, można jednak użyć dowolnego narzędzia do jego utworzenia, co jest jednym z powodów, dla których jest to tak zaawansowany model tworzenia aplikacji.  Na przykład kod XAML pokazany na rysunku 22 został utworzony w prostym edytorze tekstów i można go skompilować lub użyć bezpośrednio do wykonania wystąpienia zdefiniowanego przepływu pracy. 

<p:Activity x:Class="Workflows.HelloSeq" xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="https://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">

  <x:Członkowie>

    <x:Property Name="greeting" Type="p:InArgument(x:String)" />

    <x:Property Name="name" Type="p:InArgument(x:String)" />

  </x:Członkowie>

  <p:Sequence>

    <p:WriteLine>[powitanie & " from workflow"]</p:WriteLine>

    <p:WriteLine>[name]</p:WriteLine>

  </p:Sequence>

</p:> działania

Rysunek 22. Przepływ pracy zdefiniowany w XAML

Ten sam kod XAML jest generowany przez projektanta i może być generowany przez narzędzia niestandardowe, co znacznie ułatwia zarządzanie.  Ponadto możliwe jest również, jak pokazano wcześniej, tworzenie przepływów pracy przy użyciu kodu.  Obejmuje to tworzenie hierarchii działań przy użyciu różnych działań w strukturze i działaniach niestandardowych.  W kodzie zdefiniowano już kilka przykładów przepływów pracy.  W przeciwieństwie do WF3, teraz można utworzyć przepływ pracy w kodzie i łatwo serializować go do XAML; zapewnienie większej elastyczności modelowania definicji przepływu pracy i zarządzania nimi. 

Jak pokażę, aby wykonać przepływ pracy, wszystko, czego potrzebujesz, to działanie i które może być wystąpieniem wbudowanym w kodzie lub utworzonym na podstawie kodu XAML.  Wynikiem końcowym każdej z technik modelowania jest klasa pochodząca z działania lub reprezentacja XML, która może zostać zdeserializowana lub skompilowana w działanie. 

Opcje wykonywania przepływów pracy

Aby wykonać przepływ pracy, potrzebne jest działanie, które definiuje przepływ pracy.  Istnieją dwa typowe sposoby uzyskiwania działania, które można wykonać: tworzenie go w kodzie lub odczytywanie w pliku XAML i deserializowanie zawartości w działaniu.  Pierwsza opcja jest prosta i już pokazano kilka przykładów.  Aby załadować plik XAML, należy użyć klasy ActivityXamlServices, która udostępnia statyczną metodę ładowania.  Wystarczy przekazać obiekt Stream lub XamlReader i przywrócić działanie reprezentowane w języku XAML.  

Po utworzeniu działania najprostszym sposobem jego wykonania jest użycie klasy WorkflowInvoker, tak jak wcześniej w testach jednostkowych.  Metoda Invoke tej klasy ma parametr typu Activity i zwraca ciąg<IDictionary, obiekt>.  Jeśli musisz przekazać argumenty do przepływu pracy, najpierw zdefiniuj je w przepływie pracy, a następnie przekaż wartości wraz z działaniem do metody Invoke jako słownika par nazwa/wartość.  Podobnie wszystkie argumenty Out lub In/Out zdefiniowane w przepływie pracy zostaną zwrócone w wyniku wykonania metody.  Rysunek 23 przedstawia przykład ładowania przepływu pracy z pliku XAML, przekazywania argumentów do przepływu pracy i pobierania wynikowych argumentów wyjściowych. 

Aktywność mathWF;

using (Stream mathXaml = File.OpenRead("Math.xaml"))

{

    mathWF = ActivityXamlServices.Load(mathXaml);

}

var outputs = WorkflowInvoker.Invoke(mathWF,

    nowy ciąg<słownika, obiekt> {

    { "operand1", 5 },

    { "operand2", 10 },

    { "operation", "add" } });

Assert.AreEqual<int>(15, (int)outputs["result"], "Nieprawidłowy wynik zwrócony");

Rysunek 23. Wywoływanie przepływu pracy "Loose XAML" z argumentami in i out

Zwróć uwagę na to, że działanie jest ładowane z pliku XAML i nadal może akceptować i zwracać argumenty.  Przepływ pracy został opracowany przy użyciu projektanta w programie Visual Studio w celu ułatwienia, ale można go opracowywać w projektancie niestandardowym i przechowywać w dowolnym miejscu.  Kod XAML można załadować z bazy danych, biblioteki programu SharePoint lub innego magazynu przed przekazaniem go do środowiska uruchomieniowego w celu wykonania.  

Użycie elementu WorkflowInvoker zapewnia najprostszy mechanizm uruchamiania krótkotrwałych przepływów pracy.  Zasadniczo sprawia, że przepływ pracy działa jak wywołanie metody w aplikacji, dzięki czemu można łatwiej korzystać ze wszystkich zalet platformy WF bez konieczności wykonywania wielu czynności w celu hostowania samej platformy WF.  Jest to szczególnie przydatne podczas testowania jednostkowego działań i przepływów pracy, ponieważ zmniejsza konfigurację testu niezbędną do wykonania testu składnika. 

Inną typową klasą hostingu jest aplikacja WorkflowApplication, która zapewnia bezpieczne dojście do przepływu pracy wykonywanego w środowisku uruchomieniowym i umożliwia łatwiejsze zarządzanie długotrwałymi przepływami pracy.  Dzięki aplikacji WorkflowApplication można nadal przekazywać argumenty do przepływu pracy w taki sam sposób, jak w przypadku elementu WorkflowInvoker, ale używasz metody Run, aby faktycznie uruchomić przepływ pracy.  W tym momencie przepływ pracy rozpoczyna wykonywanie w innym wątku, a kontrolka powraca do kodu wywołującego. 

Ponieważ przepływ pracy jest teraz uruchomiony asynchronicznie, w kodzie hostingu prawdopodobnie zechcesz wiedzieć, kiedy przepływ pracy zakończy się, lub jeśli zgłasza wyjątek itp. W przypadku tych typów powiadomień klasa WorkflowApplication ma zestaw właściwości typu Akcja<T>, które mogą służyć jako zdarzenia do dodawania kodu w celu reagowania na określone warunki wykonywania przepływu pracy, w tym: przerwany, nieobsługiwany wyjątek, ukończony, idled i zwolniony.  Podczas wykonywania przepływu pracy przy użyciu funkcji WorkflowApplication można użyć kodu podobnego do przedstawionego na rysunku 24 przy użyciu akcji do obsługi zdarzeń. 

WorkflowApplication wf = new WorkflowApplication(new Flowchart1());

Wf. Completed = delegate(WorkflowApplicationCompletedEventArgs e)

{

    Console.WriteLine("Workflow {0} complete", np. InstanceId);

};

Wf. Aborted = delegate(WorkflowApplicationAbortedEventArgs e)

{

    Console.WriteLine(e.Reason);

};

Wf. OnUnhandledException =

  delegate(WorkflowApplicationUnhandledExceptionEventArgs e)

  {

      Console.WriteLine(e.UnhandledException.ToString());

      return UnhandledExceptionAction.Terminate;

  };

Wf. Run();

Rysunek 24. Akcje workflowApplication

W tym przykładzie celem kodu hosta, prostej aplikacji konsolowej, jest powiadomienie użytkownika za pośrednictwem konsoli po zakończeniu przepływu pracy lub wystąpieniu błędu.  W rzeczywistym systemie host będzie zainteresowany tymi zdarzeniami i prawdopodobnie będzie reagować na nie inaczej, aby zapewnić administratorom informacje o awariach lub lepiej zarządzać wystąpieniami.

Rozszerzenia przepływu pracy

Jedną z podstawowych funkcji WF, ponieważ WF3, było to, że jest wystarczająco lekki, aby być hostowane w dowolnej domenie aplikacji platformy .NET.  Ponieważ środowisko uruchomieniowe może być wykonywane w różnych domenach i może wymagać dostosowanej semantyki wykonywania, różne aspekty zachowań środowiska uruchomieniowego muszą być zewnętrzne ze środowiska uruchomieniowego.  W tym miejscu pojawiają się rozszerzenia przepływu pracy.  Rozszerzenia przepływu pracy umożliwiają deweloperowi pisanie kodu hosta, jeśli tak wybierzesz, aby dodać zachowanie do środowiska uruchomieniowego, rozszerzając go przy użyciu kodu niestandardowego. 

Istnieją dwa typy rozszerzeń, których dotyczy środowisko uruchomieniowe platformy WF, to trwałość i rozszerzenia śledzenia. Rozszerzenie trwałości zapewnia podstawowe funkcje zapisywania stanu przepływu pracy w trwałym magazynie i pobierania tego stanu, gdy jest potrzebny.  Rozszerzenie trwałości wysyłki z platformą obejmuje obsługę programu Microsoft SQL Server, ale rozszerzenia można zapisywać w celu obsługi innych systemów baz danych lub magazynów trwałych. 

Wytrwałość

Aby użyć rozszerzenia trwałości, należy najpierw skonfigurować bazę danych do przechowywania stanu, który można wykonać przy użyciu skryptów SQL znajdujących się w %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<języka> (np. c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). Po utworzeniu bazy danych wykonaj dwa skrypty, aby utworzyć strukturę (SqlWorkflowInstanceStoreSchema.sql) i procedury składowane (SqlWorkflowInstanceStoreLogic.sql) w bazie danych. 

Po skonfigurowaniu bazy danych należy użyć klasy SqlWorkflowInstanceStore wraz z klasą WorkflowApplication.  Najpierw utwórz magazyn i skonfiguruj go, a następnie podaj go do aplikacji WorkflowApplication, jak pokazano na rysunku 25.

WorkflowApplication application = new WorkflowApplication(activity);

InstanceStore instanceStore = new SqlWorkflowInstanceStore(

    @"Data Source=.\\SQLEXPRESS;Integrated Security=True");

Widok wystąpienia = instanceStore.Execute(

    instanceStore.CreateInstanceHandle(), nowy createWorkflowOwnerCommand(),

    TimeSpan.FromSeconds(30));

instanceStore.DefaultInstanceOwner = view. Właściciel wystąpienia;

aplikacja. InstanceStore = instanceStore;

Rysunek 25. Dodawanie dostawcy trwałości SQL

Istnieją dwa sposoby utrwalania przepływu pracy.  Pierwszy polega na bezpośrednim użyciu działania Utrwalanie w przepływie pracy.  Gdy to działanie jest wykonywane, powoduje, że stan przepływu pracy jest utrwalany w bazie danych, zapisując bieżący stan przepływu pracy.  Zapewnia to autorowi przepływu pracy kontrolę, gdy ważne jest zapisanie bieżącego stanu przepływu pracy.  Druga opcja umożliwia aplikacji hostingowej utrwalanie stanu przepływu pracy, gdy wystąpią różne zdarzenia w wystąpieniu przepływu pracy; najbardziej prawdopodobne, gdy przepływ pracy jest bezczynny.  Rejestrując się pod kątem akcji PersistableIdle w usłudze WorkflowApplication, kod hosta może następnie odpowiedzieć na zdarzenie, aby wskazać, czy wystąpienie powinno być utrwalane, zwalniane lub nie.  Rysunek 26 przedstawia aplikację hosta rejestrującą się w celu otrzymywania powiadomień, gdy przepływ pracy jest bezczynny i powoduje jego trwałość. 

Wf. PersistableIdle = (waie) => PersistableIdleAction.Persist;

Rysunek 26. Zwalnianie przepływu pracy podczas bezczynności

Gdy przepływ pracy jest bezczynny i utrwalone, środowisko uruchomieniowe platformy WF może zwolnić go z pamięci, zwalniając zasoby dla innych przepływów pracy, które wymagają przetwarzania.  Możesz zwolnić wystąpienie przepływu pracy, zwracając odpowiednią wyliczenie z akcji PersistableIdle.  Gdy przepływ pracy zostanie zwolniony, w pewnym momencie host będzie chciał załadować go z powrotem z magazynu trwałości, aby wznowić go.  Aby to zrobić, wystąpienie przepływu pracy musi zostać załadowane przy użyciu magazynu wystąpień i identyfikatora wystąpienia. Spowoduje to wyświetlenie monitu o załadowanie stanu magazynu wystąpień.  Po załadowaniu stanu można użyć metody Run w aplikacji WorkflowApplication lub zakładki można wznowić, jak pokazano na rysunku 27.  Aby uzyskać więcej informacji na temat zakładek, zobacz sekcję działania niestandardowe. 

WorkflowApplication application = new WorkflowApplication(activity);

aplikacja. InstanceStore = instanceStore;

aplikacja. Load(id);

aplikacja. ResumeBookmark(readLineBookmark, input);

Rysunek 27. Wznawianie utrwalonego przepływu pracy

Jedną ze zmian w programie WF4 jest to, jak stan przepływów pracy jest utrwalany i w dużym stopniu opiera się na nowych technikach zarządzania danymi argumentów i zmiennych.  Zamiast serializować całe drzewo działań i utrzymywać stan dla okresu istnienia przepływu pracy, utrwalane są tylko zmienne zakresu i wartości argumentów wraz z niektórymi danymi środowiska uruchomieniowego, takimi jak informacje o zakładce.  Zwróć uwagę na rysunek 27, że po ponownym załadowaniu utrwalonego wystąpienia oprócz korzystania z magazynu wystąpień działanie definiujące przepływ pracy również jest przekazywany. Zasadniczo stan z bazy danych jest stosowany do podanego działania.  Ta technika zapewnia znacznie większą elastyczność przechowywania wersji i zapewnia lepszą wydajność, ponieważ stan ma mniejsze zużycie pamięci. 

Śledzenia

Trwałość umożliwia hostowi obsługę długotrwałych procesów, równoważenia obciążenia wystąpień na hostach i innych zachowaniach odpornych na błędy.  Jednak po zakończeniu wystąpienia przepływu pracy stan w bazie danych jest często usuwany, ponieważ nie jest już przydatny.  W środowisku produkcyjnym informacje o tym, co obecnie wykonuje przepływ pracy i co zrobił, ma kluczowe znaczenie zarówno w zarządzaniu przepływami pracy, jak i uzyskiwaniu wglądu w proces biznesowy.  Możliwość śledzenia tego, co dzieje się w aplikacji, jest jedną z atrakcyjnych funkcji korzystania ze środowiska uruchomieniowego platformy WF.  

Śledzenie składa się z dwóch podstawowych składników: śledzenia uczestników i profilów śledzenia.  Profil śledzenia definiuje zdarzenia i dane, które mają być śledzone przez środowisko uruchomieniowe. Profile mogą zawierać trzy podstawowe typy zapytań:

  • ActivityStateQuery — służy do identyfikowania stanów aktywności (np. zamkniętych) i zmiennych lub argumentów w celu wyodrębnienia danych
  • WorkflowInstanceQuery — służy do identyfikowania zdarzeń przepływu pracy
  • CustomTrackingQuery — służy do identyfikowania jawnych wywołań śledzenia danych, zwykle w ramach działań niestandardowych

Na przykład rysunek 28 przedstawia tworzony plik TrackingProfile, który zawiera wartość ActivityStateQuery i workflowInstanceQuery.   Zwróć uwagę, że zapytania wskazują, kiedy należy zbierać informacje, a także jakie dane mają być zbierane.  W przypadku zapytania ActivityStateQuery dołączono listę zmiennych, które powinny mieć wyodrębnione wartości i dodane do śledzonych danych. 

Profil TrackingProfile = nowy plik TrackingProfile

{

    Name = "SimpleProfile",

    Zapytania = {

        new WorkflowInstanceQuery {

            Stany = { "*" }

        },

        new ActivityStateQuery {

            ActivityName = "WriteLine",

            States={ "*" },

            Zmienne = {"Text" }

        }

    }

};

Rysunek 28. Tworzenie profilu śledzenia

Uczestnik śledzenia to rozszerzenie, które można dodać do środowiska uruchomieniowego i jest odpowiedzialne za przetwarzanie rekordów śledzenia podczas ich emitowania.  Klasa podstawowa TrackingParticipant definiuje właściwość w celu udostępnienia elementu TrackingProfile i metody Track obsługującej śledzenie.  Rysunek 29 przedstawia ograniczonego uczestnika śledzenia niestandardowego, który zapisuje dane w konsoli.  Aby można było używać uczestnika śledzenia, należy zainicjować go za pomocą profilu śledzenia, a następnie dodać go do kolekcji rozszerzeń w wystąpieniu przepływu pracy. 

public class ConsoleTrackingParticipant: TrackingParticipant

{

   protected override void Track(TrackingRecord record, TimeSpan timeout)

   {

      ActivityStateRecord aRecord = rekord jako ActivityStateRecord;

      if (aRecord != null)

      {

         Console.WriteLine("{0} wprowadzono stan {1}",

            aRecord.Activity.Name, aRecord.State);

         foreach (var item in aRecord.Arguments)

         {

            Console.ForegroundColor = ConsoleColor.Cyan;

            Console.WriteLine("Zmienna:{0} ma wartość: {1}",

                przedmiot. Klucz, element. Wartość);

            Console.ResetColor();

         }

      }

   }

}

Rysunek 29. Śledzenie konsoli

Program WF jest dostarczany z elementem EtwTrackingParticipant (ETW = Enterprise Trace for Windows), który można dodać do środowiska uruchomieniowego, aby umożliwić śledzenie danych o wysokiej wydajności.  ETW to system śledzenia, który jest składnikiem natywnym w systemie Windows i jest używany przez wiele składników i usług w systemie operacyjnym, w tym sterowniki i inny kod poziomu jądra.  Dane zapisane w etW mogą być używane przy użyciu kodu niestandardowego lub narzędzi, takich jak nadchodzący program Windows Server AppFabric.  W przypadku użytkowników migrujących z platformy WF3 będzie to zmiana, ponieważ w ramach struktury nie będzie można wysyłać uczestników śledzenia SQL.  Jednak program Windows Server AppFabric będzie dostarczać użytkownikom etW, którzy będą zbierać dane ETW i przechowywać je w bazie danych SQL.  Podobnie możesz utworzyć konsumenta ETW i przechowywać dane w dowolnym preferowanym formacie lub napisać własnego uczestnika śledzenia, niezależnie od tego, co ma sens w twoim środowisku. 

Tworzenie działań niestandardowych

Biblioteka działań podstawowych zawiera bogatą paletę działań związanych z interakcją z usługami, obiektami i kolekcjami, ale nie zapewnia działań związanych z interakcją z podsystemami, takimi jak bazy danych, serwery poczty e-mail lub niestandardowe obiekty i systemy domeny.  Częścią rozpoczynania pracy z platformą WF4 będzie ustalenie, jakie podstawowe działania mogą być potrzebne lub potrzebne podczas tworzenia przepływów pracy.  Wielką rzeczą jest to, że podczas tworzenia podstawowych jednostek pracy można je połączyć w bardziej grubsze działania, których deweloperzy mogą używać w swoich przepływach pracy. 

Hierarchia klas działań

Chociaż działanie jest działaniem, nie jest prawdą, że wszystkie działania mają te same wymagania lub potrzeby.  Z tego powodu, a nie wszystkie działania pochodzące bezpośrednio z działania, istnieje hierarchia klas bazowych działań, pokazana na rysunku 30, którą można wybrać podczas tworzenia działania niestandardowego. 

Rysunek 30. Hierarchia klas działań

W przypadku większości działań niestandardowych możesz pochodzić z elementu AsyncCodeActivity, CodeActivity lub NativeActivity (albo jednego z wariantów ogólnych) albo deklaratywnie modelować działanie.  Na wysokim poziomie można opisać cztery klasy bazowe w następujący sposób:

  • Działanie — używane do modelowania działań przez tworzenie innych działań, zwykle definiowanych przy użyciu języka XAML.
  • CodeActivity — uproszczona klasa bazowa, gdy trzeba napisać kod, aby wykonać pracę.
  • AsyncCodeActivity — używana w przypadku asynchronicznego wykonywania działania.
  • NativeActivity — gdy działanie wymaga dostępu do wewnętrznych środowisk uruchomieniowych, na przykład w celu zaplanowannia innych działań lub utworzenia zakładek.

W poniższych sekcjach skompiluję kilka działań, aby zobaczyć, jak używać każdej z tych klas bazowych.  Ogólnie rzecz biorąc, jak myślisz o klasie bazowej, należy zacząć od klasy bazowej Activity i sprawdzić, czy możesz utworzyć działanie przy użyciu logiki deklaratywnej, jak pokazano w następnej sekcji.  Następnie funkcja CodeActivity udostępnia uproszczony model, jeśli określisz, że musisz napisać jakiś kod platformy .NET w celu wykonania zadania i AsyncCodeActivity, jeśli chcesz, aby działanie było wykonywane asynchronicznie.  Na koniec, jeśli piszesz działania przepływu sterowania, takie jak te znalezione w strukturze (np. While, Switch, If), musisz utworzyć z klasy bazowej NativeActivity, aby zarządzać działaniami podrzędnymi. 

Tworzenie działań przy użyciu projektanta działań

Podczas tworzenia nowego projektu biblioteki działań lub dodawania nowego elementu do projektu WF i wybrania szablonu Działania otrzymasz plik XAML z pustym elementem Działania.  W projektancie jest to powierzchnia projektowa, w której można utworzyć treść działania.  Aby rozpocząć pracę z działaniem, które będzie miało więcej niż jeden krok, zwykle pomaga przeciągnąć działanie Sekwencja jako treść, a następnie wypełnić je rzeczywistą logiką działania, ponieważ treść reprezentuje jedno działanie podrzędne. 

Działanie można traktować podobnie jak w przypadku metody w składniku z argumentami.  W samym działaniu można zdefiniować argumenty wraz z ich kierunkowością, aby zdefiniować interfejs działania.  Zmienne, które mają być używane w działaniu, muszą być zdefiniowane w działaniach składających się na treść, takich jak wcześniej wymieniona sekwencja główna.  Na przykład można utworzyć działanie NotifyManager, które komponuje dwa prostsze działania: GetManager i SendMail. 

Najpierw utwórz nowy projekt ActivityLibrary w programie Visual Studio 2010 i zmień nazwę pliku Activity1.xaml na NotifyManager.xaml.  Następnie przeciągnij działanie Sekwencja z przybornika i dodaj je do projektanta.  Po utworzeniu sekwencji można wypełnić ją działaniami podrzędnymi, jak pokazano na rysunku 31. (Należy pamiętać, że działania używane w tym przykładzie to działania niestandardowe w projekcie, do którego odwołuje się odwołanie, i które nie są dostępne w strukturze).

Rysunek 31. Powiadamianie menedżera działań

To działanie musi podjąć argumenty dla nazwy pracownika i treści komunikatu. Działanie GetManager wyszukuje menedżera pracownika i podaj wiadomość e-mail jako ciąg OutArgument<>.  Na koniec działanie SendMail wysyła wiadomość do menedżera.  Następnym krokiem jest zdefiniowanie argumentów działania przez rozwinięcie okna Argumenty w dolnej części projektanta i wprowadzenie informacji dla dwóch wymaganych argumentów wejściowych. 

Aby utworzyć te elementy, należy przekazać argumenty wejściowe określone w działaniu NotifyManager do poszczególnych działań podrzędnych.  W przypadku działania GetManager potrzebna jest nazwa pracownika, która jest argumentem w działaniu. Nazwę argumentu można użyć w oknie dialogowym właściwości argumentu employeeName w działaniu GetManager, jak pokazano na rysunku 31.  Działanie SendMail wymaga adresu e-mail menedżera i wiadomości.  W przypadku komunikatu możesz wprowadzić nazwę argumentu zawierającego komunikat.  Jednak dla adresu e-mail potrzebny jest sposób przekazania argumentu out z działania GetManager do argumentu in dla działania SendMail.  W tym celu potrzebna jest zmienna. 

Po wyróżnieniu działania Sekwencja można użyć okna Zmienne, aby zdefiniować zmienną o nazwie "mgrEmail".  Teraz możesz wprowadzić nazwę tej zmiennej zarówno dla argumentu ManagerEmail out w działaniu GetManager, jak i argument Do w działaniu SendMail.  Gdy działanie GetManager zostanie wykonane, dane wyjściowe będą przechowywane w tej zmiennej, a gdy działanie SendMail zostanie wykonane, odczytuje dane z tej zmiennej jako argument w argumencie . 

Właśnie opisane podejście to czysto deklaratywny model definiowania treści działania.  W niektórych okolicznościach możesz wolisz określić treść działania w kodzie.  Na przykład działanie może zawierać kolekcję właściwości, które z kolei prowadzą do zestawu działań podrzędnych; Zestaw nazwanych wartości, które napędzają tworzenie zestawu działań Przypisywanie, byłoby jednym z przypadków, w których preferowane byłoby użycie kodu.  W takich przypadkach można napisać klasę, która pochodzi z działania, i napisać kod we właściwości Implementacja, aby utworzyć działanie (i wszystkie elementy podrzędne) reprezentujące funkcjonalność działania.  Wynik końcowy jest taki sam w obu przypadkach: logika jest definiowana przez tworzenie innych działań, tylko mechanizm, za pomocą którego zdefiniowano treść, jest inny.  Rysunek 32 przedstawia to samo działanie NotifyManager zdefiniowane w kodzie. 

public, klasa NotifyManager: Działanie

{

    publiczny ciąg<InArgument> EmployeeName { get; set; }

    publiczny ciąg<<ciąg> Komunikat { get; set; }

    chronione zastąpienie implementacji działania<Func>

    {

        Pobierz

        {

            return () =>

            {

                Ciąg zmiennej<> mgrEmail =

                nowy ciąg<zmiennej> { Name = "mgrEmail" };

                Sekwencja s = nowa sekwencja

                {

                    Zmienne = { mgrEmail },

                    Działania = {

                        nowy GetManager {

                            EmployeeName =

                                nowy ciąg<VisualBasicValue>("EmployeeName"),

                                Result = mgrEmail,

                        },

                        nowa wiadomość SendMail {

                            ToAddress = mgrEmail,

                            MailBody = nowy ciąg<VisualBasicValue>("Message"),

                            From = "test@contoso.com",

                            Temat = "Automatyczna wiadomość e-mail"

                        }

                    }

                };

                zwraca s;

            };

        }

        ustaw { base. Implementacja = wartość; }

    }

}

Rysunek 32. Kompozycja działania przy użyciu kodu

Zwróć uwagę, że klasa bazowa to Activity, a właściwość Implementation to Func<Activity>, aby zwrócić pojedyncze działanie. Ten kod jest bardzo podobny do pokazanego wcześniej podczas tworzenia przepływów pracy i nie powinno to być zaskakujące, ponieważ celem w obu przypadkach jest utworzenie działania.  Ponadto w argumentach podejścia do kodu można ustawić zmienne lub użyć wyrażeń, aby połączyć jeden argument z innym, jak pokazano dla argumentów EmployeeName i Message, ponieważ są one używane w dwóch działaniach podrzędnych. 

Pisanie niestandardowych klas działań

Jeśli ustalisz, że logika działania nie może zostać wykonana przez utworzenie innych działań, możesz napisać działanie oparte na kodzie.  Podczas pisania działań w kodzie pochodzącym z odpowiedniej klasy zdefiniuj argumenty, a następnie przesłoń metodę Execute.  Metoda Execute jest wywoływana przez środowisko uruchomieniowe, gdy czas na wykonanie działania.

Aby skompilować działanie SendMail użyte w poprzednim przykładzie, najpierw muszę wybrać typ podstawowy.  Chociaż istnieje możliwość utworzenia funkcji działania SendMail przy użyciu klasy bazowej Activity i komponowania tryCatch<T> i invokeMethod działań, dla większości deweloperów bardziej naturalne będzie wybranie klas bazowych CodeActivity i NativeActivity oraz pisanie kodu dla logiki wykonywania.  Ponieważ to działanie nie musi tworzyć zakładek ani planować innych działań, mogę pochodzić z klasy bazowej CodeActivity.  Ponadto, ponieważ to działanie zwróci pojedynczy argument wyjściowy, powinien pochodzić z> CodeActivity<TResult, który udostępnia<TResult>.  Pierwszym krokiem jest zdefiniowanie kilku argumentów wejściowych dla właściwości wiadomości e-mail.  Następnie zastąpisz metodę Execute, aby zaimplementować funkcjonalność działania.  Rysunek 33 przedstawia ukończoną klasę działania. 

public class SendMail: CodeActivity<SmtpStatusCode>

{

    publiczny ciąg<inargument> Do { get; set; }

    publiczny ciąg<inargument> z { get; set; }

    publiczny ciąg<<ciągu> Temat { get; set; }

    publiczny ciąg<InArgument> Treść { get; set; }

    chronione przesłanianie polecenia SmtpStatusCode Execute(

    Kontekst CodeActivityContext)

    {

        próbować

        {

            Klient SmtpClient = nowy obiekt SmtpClient();

            klient. Send(

            From.Get(context), ToAddress.Get(context),

            Subject.Get(context), MailBody.Get(context));

        }

        catch (SmtpException smtpEx)

        {

            return smtpEx.StatusCode;

        }

        return SmtpStatusCode.Ok;

    }

}

Rysunek 33. Działanie niestandardowe SendMail

Istnieje kilka kwestii do zanotowania użycia argumentów.  Zadeklarowałem kilka standardowych właściwości platformy .NET przy użyciu typów InArgument<T>.  W ten sposób działanie oparte na kodzie definiuje argumenty wejściowe i wyjściowe.  Jednak w kodzie nie mogę po prostu odwołać się do tych właściwości, aby uzyskać wartość argumentu, muszę użyć argumentu jako rodzaju uchwytu, aby pobrać wartość przy użyciu podanego elementu ActivityContext; w tym przypadku kodActivityContext.  Metoda Get klasy argumentu służy do pobierania wartości i metody Set w celu przypisania wartości do argumentu. 

Gdy działanie zakończy metodę Execute, środowisko uruchomieniowe wykryje to i zakłada, że działanie jest wykonywane i zamyka je.  W przypadku pracy asynchronicznej lub długotrwałej istnieją sposoby zmiany tego zachowania, które zostały wyjaśnione w nadchodzącej sekcji, ale w tym przypadku po wysłaniu wiadomości e-mail praca zostanie ukończona. 

Teraz przyjrzyjmy się działaniu przy użyciu klasy bazowej NativeActivity w celu zarządzania działaniami podrzędnymi.  Skompiluję działanie iteratora, które wykonuje pewną aktywność podrzędną o określonej liczbie razy.  Najpierw potrzebuję argumentu do przechowywania liczby iteracji do wykonania, właściwości działania do wykonania i zmiennej do przechowywania bieżącej liczby iteracji. Metoda Execute wywołuje metodę BeginIteration, aby rozpocząć początkową iterację, a kolejne iteracji są wywoływane w taki sam sposób.  Zwróć uwagę na rysunek 34, że zmienne są manipulowane tak samo jak argumenty przy użyciu metody Get i Set. 

public class Iterator : NativeActivity

{

    publiczna treść działania { get; set; }

    publiczna<int int> RequestedIterations { get; set; }

    publiczna zmienna<int> CurrentIteration { get; set; }

    publiczny iterator()

    {

        CurrentIteration = nowa zmienna<int> { Default = 0 };

    }

    protected override void Execute(NativeActivityContext context)

    {

        BeginIteration(kontekst);

    }

    private void BeginIteration(NativeActivityContext context)

    {

        if (RequestedIterations.Get(context) > CurrentIteration.Get(context))

        {

            kontekst. ScheduleActivity(Treść,

            new CompletionCallback(ChildComplete),

            new FaultCallback(ChildFaulted));

        }

    }

}

Rysunek 34. Działanie natywne iteratora

Jeśli przyjrzysz się bliżej metodzie BeginIteration, zauważysz wywołanie metody ScheduleActivity.  W ten sposób działanie może wchodzić w interakcje ze środowiskiem uruchomieniowym, aby zaplanować inne działanie do wykonania i jest to spowodowane tym, że potrzebujesz tej funkcji pochodzącej z elementu NativeActivity.  Należy również pamiętać, że podczas wywoływania metody ScheduleActivity w obiekcie ActivityExecutionContext są udostępniane dwie metody wywołania zwrotnego.  Ponieważ nie wiesz, które działanie będzie używane jako treść lub jak długo potrwa ukończenie, a ponieważ WF jest silnie asynchronicznym środowiskiem programowania, wywołania zwrotne są używane do powiadamiania działania po zakończeniu działania podrzędnego, co umożliwia pisanie kodu w celu reagowania.

Drugie wywołanie zwrotne to FaultCallback, który zostanie wywołany, jeśli działanie podrzędne zgłosi wyjątek.  W tym wywołaniu zwrotnym otrzymasz wyjątek i masz możliwość wskazywania przez środowisko uruchomieniowe, że błąd został obsłużony, co uniemożliwia jego działanie i poza nim. Rysunek 35 przedstawia metody wywołania zwrotnego dla działania iteratora, w którym zaplanowano następną iterację, a funkcja FaultCallback obsługuje błąd, aby umożliwić wykonywanie w celu kontynuowania następnej iteracji.

private void ChildComplete(NativeActivityContext context,

Wystąpienie klasy ActivityInstance)

{

    CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);

    BeginIteration(kontekst);

}

private void ChildFaulted(NativeActivityFaultContext context, Exception ex,

Wystąpienie klasy ActivityInstance)

{

    CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);

    kontekst. HandleFault();

}

Rysunek 35. Wywołania zwrotne działań

Gdy działanie musi wykonać pracę, która może być długotrwała, najlepiej, aby praca mogła zostać przekazana innemu wątkowi, aby umożliwić wątkowi przepływu pracy kontynuowanie przetwarzania innych działań lub używanie ich do przetwarzania innych przepływów pracy.  Jednak po prostu uruchamianie wątków i zwracanie kontroli do środowiska uruchomieniowego może powodować problemy, ponieważ środowisko uruchomieniowe nie monitoruje utworzonych wątków.  Jeśli środowisko uruchomieniowe ustali, że przepływ pracy był bezczynny, przepływ pracy może zwolnić, usunąć metody wywołania zwrotnego lub środowisko uruchomieniowe mogłoby przyjąć, że działanie zostało wykonane i przejść do następnego działania w przepływie pracy.  Aby obsługiwać programowanie asynchroniczne w działaniu, pochodzisz z elementu AsyncCodeActivity lub AsyncCodeActivity<T>. 

Rysunek 36 przedstawia przykład działania asynchronicznego, które ładuje kanał informacyjny RSS.  Ten podpis metody execute różni się w przypadku działań asynchronicznych, w których zwraca wartość IAsyncResult.  Kod można napisać w metodzie execute, aby rozpocząć operację asynchroniczną.  Zastąpi metodę EndExecute, aby obsłużyć wywołanie zwrotne po zakończeniu operacji asynchronicznej i zwrócić wynik wykonania. 

public sealed class GetFeed : AsyncCodeActivity<SyndicationFeed>

{

    public InArgument<identyfikator URI> FeedUrl { get; set; }

    chronione zastąpienie IAsyncResult BeginExecute(

    Kontekst AsyncCodeActivityContext, wywołanie zwrotne AsyncCallback,

    stan obiektu)

    {

        var req = (HttpWebRequest)HttpWebRequest.Create(

        FeedUrl.Get(context));

        Req. Method = "GET";

        kontekst. UserState = req;

        return req. BeginGetResponse(new AsyncCallback(callback), state);

    }

    chronione zastąpienie SyndicationFeed EndExecute(

    Kontekst AsyncCodeActivityContext, wynik IAsyncResult)

    {

        HttpWebRequest req = context. UserState jako HttpWebRequest;

        WebResponse wr = req. EndGetResponse(wynik);

        SyndicationFeed localFeed = SyndicationFeed.Load(

        XmlReader.Create(wr. GetResponseStream()));

        return localFeed;

    }

}

Rysunek 36. Tworzenie działań asynchronicznych

Dodatkowe pojęcia dotyczące działań

Teraz, gdy znasz już podstawy tworzenia działań przy użyciu klas bazowych, argumentów i zmiennych oraz zarządzania wykonywaniem; Pokrótce omówię niektóre bardziej zaawansowane funkcje, których można używać w tworzeniu działań. 

Zakładki umożliwiają autorowi działania utworzenie punktu wznowienia w wykonywaniu przepływu pracy.  Po utworzeniu zakładki można ją wznowić, aby kontynuować przetwarzanie przepływu pracy od miejsca, w którym została przerwana.  Zakładki są zapisywane jako część stanu, więc w przeciwieństwie do działań asynchronicznych po utworzeniu zakładki nie jest to problem z utrwalanie i zwalnianie wystąpienia przepływu pracy.  Gdy host chce wznowić przepływ pracy, ładuje wystąpienie przepływu pracy i wywołuje metodę ResumeBookmark, aby wznowić wystąpienie, z którego zostało wyłączone.  Podczas wznawiania zakładek dane mogą być również przekazywane do działania.   Rysunek 37 przedstawia działanie ReadLine, które tworzy zakładkę w celu odbierania danych wejściowych i rejestruje metodę wywołania zwrotnego, która ma zostać wywołana po nadejściu danych.  Środowisko uruchomieniowe wie, kiedy działanie ma zaległe zakładki i nie zamknie działania, dopóki zakładka nie zostanie wznowiona.  Metodę ResumeBookmark można użyć w klasie WorkflowApplication, aby wysyłać dane do nazwanej zakładki i sygnalizować element BookmarkCallback.  

public class ReadLine: NativeActivity<ciąg>

{

    publiczny ciąg<outArgument> InputText { get; set; }

    protected override void Execute(NativeActivityContext context)

    {

        kontekst. CreateBookmark("ReadLine",

        new BookmarkCallback(BookmarkResumed));

    }

    private void BookmarkResumed(NativeActivityContext context,

        Zakładka bk, stan obiektu)

    {

        Result.Set(kontekst, stan);

    }

}

Rysunek 37. Tworzenie zakładki

Kolejną zaawansowaną funkcją dla autorów działań jest koncepcja działaniaAction.  ActivityAction to odpowiednik przepływu pracy klasy akcji w kodzie imperatywnego: opisujący wspólny delegat; ale dla niektórych pomysł szablonu może być łatwiejszy do zrozumienia.  Rozważ działanie> T forEach<, które umożliwia iterowanie zestawu danych i zaplanowanie działania podrzędnego dla każdego elementu danych.  Musisz w jakiś sposób zezwolić użytkownikowi działania na zdefiniowanie treści i mieć możliwość korzystania z elementu danych typu T. Zasadniczo potrzebne jest pewne działanie, które może zaakceptować element typu T i działać na nim, ActivityAction<T> służy do włączania tego scenariusza i zawiera odwołanie do argumentu i definicji działania do przetwarzania elementu.  Możesz użyć działania ActivityAction w niestandardowych działaniach, aby umożliwić konsumentom działania dodawanie własnych kroków w odpowiednich punktach.  Dzięki temu można tworzyć szablony przepływów pracy lub działań sortowania, w których użytkownik może wypełnić odpowiednie części, aby dostosować wykonywanie do ich użycia.  Można również użyć funkcji ActivityFunc<TResult> i powiązanych alternatyw, gdy delegowanie działania wymaga wywołania zwróci wartość.  

Na koniec działania można zweryfikować, aby upewnić się, że zostały prawidłowo skonfigurowane, sprawdzając poszczególne ustawienia, ograniczając dozwolone działania podrzędne itp. Aby często wymagać określonego argumentu, można dodać atrybut RequiredArgument do deklaracji właściwości w działaniu.  Aby uzyskać bardziej zaangażowaną walidację, w konstruktorze działania utwórz ograniczenie i dodaj je do kolekcji ograniczeń, które są udostępniane w klasie Activity.  Ograniczenie to działanie napisane w celu sprawdzenia działania docelowego i zapewnienia poprawności.  Rysunek 38 przedstawia konstruktor działania iteratora, który sprawdza, czy właściwość RequestedIterations jest ustawiona. Na koniec przesłonięcia metody CacheMetadata można wywołać metodę AddValidationError w parametrze ActivityMetdata, aby dodać błędy walidacji dla działania. 

var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };

var vctx = new DelegateInArgument<ValidationContext>();

Ograniczenie<> iteratora = nowe ograniczenie<iteratora>

{

    Treść = nowa funkcja ActivityAction<Iterator, ValidationContext>

    {

        Argument1 = act,

        Argument2 = vctx,

        Procedura obsługi = nowa asercjaValidation

        {

            Komunikat = "Iteracja musi być podana",

            PropertyName = "RequestedIterations",

            Assertion = new InArgument<bool>(

                e) => akt. Get(e). RequestedIterations != null)

        }

    }

};

baza. Ograniczenia.Add(cons);

Rysunek 38. Ograniczenia

Projektanci działań

Jedną z zalet platformy WF jest to, że pozwala programować logikę aplikacji deklaratywnie, ale większość osób nie chce pisać kodu XAML ręcznie, dlatego środowisko projektanta w programie WF jest tak ważne.  Podczas tworzenia działań niestandardowych prawdopodobnie zechcesz również utworzyć projektanta, aby udostępnić użytkownikom działania środowisko wyświetlania i interakcji wizualnych.  Projektant dla platformy WF jest oparty na programie Windows Presentation Foundation, co oznacza, że masz całą moc stylów, wyzwalaczy, powiązania danych i wszystkich innych wspaniałych narzędzi do tworzenia rozbudowanego interfejsu użytkownika dla projektanta.  Ponadto platforma WF udostępnia niektóre kontrolki użytkownika, których można użyć w projektancie, aby uprościć zadanie wyświetlania poszczególnych działań podrzędnych lub kolekcji działań.  Cztery podstawowe kontrolki to:

  • ActivityDesigner — główna kontrolka WPF używana w projektantach działań
  • WorkflowItemPresenter — służy do wyświetlania pojedynczego działania
  • WorkflowItemsPresenter — służy do wyświetlania kolekcji działań podrzędnych
  • ExpressionTextBox — służy do włączania edycji wyrażeń, takich jak argumenty

WF4 zawiera szablon projektu ActivityDesignerLibrary i szablon elementu ActivityDesigner, który może służyć do początkowego tworzenia artefaktów.  Po zainicjowaniu projektanta możesz rozpocząć korzystanie z powyższych elementów, aby dostosować wygląd i działanie działania, określając sposób wyświetlania danych i wizualnych reprezentacji działań podrzędnych.  W projektancie masz dostęp do elementu ModelItem, który jest abstrakcją rzeczywistego działania i uwypukania wszystkich właściwości działania. 

Kontrolka WorkflowItemPresenter może służyć do wyświetlania pojedynczego działania będącego częścią działania.  Aby na przykład zapewnić możliwość przeciągania działania do pokazanego wcześniej działania iteratora i wyświetlania działania zawartego w działaniu Iteractor, możesz użyć kodu XAML pokazanego na rysunku 39, powiązania elementu WorkflowItemPresenter z elementem ModelItem.Body.  Rysunek 40 przedstawia projektanta używanego na powierzchni projektowej przepływu pracy. 

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"

  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

  xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation">

  <Grid>

    <BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">

<sap:WorkflowItemPresenter MinHeight="50"

Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"

HintText="Dodaj treść tutaj" />

    </> obramowania

  </Grid>

</sap:ActivityDesigner>

Rysunek 39: WorkflowItemPresenter w projektancie działań

Rysunek 40. Projektant iteratora

WorkflowItemsPresenter udostępnia podobne funkcje do wyświetlania wielu elementów, takich jak elementy podrzędne w sekwencji. Szablon i orientację można zdefiniować pod kątem sposobu wyświetlania elementów, a także szablonu odstępu, aby dodać elementy wizualne między działaniami, takimi jak łączniki, strzałki lub coś bardziej interesującego.  Rysunek 41 przedstawia prostego projektanta dla niestandardowego działania sekwencji, które wyświetla działania podrzędne z linią poziomą między poszczególnymi elementami.

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation">

    <Grid>

      > StackPanel <

         <BorderBrush="Goldenrod" BorderThickness="3">

     <sap:WorkflowItemsPresenter HintText="Drop Activities Here"

  Items="{Binding Path=ModelItem.ChildActivities}">

     <sap:WorkflowItemsPresenter.SpacerTemplate>

  <DataTemplate>

    <Height="3" Width="40"

 Fill="MidnightBlue" Margin="5" />

                     </DataTemplate>

                  </sap:WorkflowItemsPresenter.SpacerTemplate>

                  <sap:WorkflowItemsPresenter.ItemsPanel>

                      <ItemsPanelTemplate>

                          <StackPanel Orientation="Vertical"/>

                      </ItemsPanelTemplate>

                   </sap:WorkflowItemsPresenter.ItemsPanel>

            </sap:WorkflowItemsPresenter>

        </> obramowania

      </StackPanel>

    </Grid>

</sap:ActivityDesigner>

Rysunek 41. Niestandardowy projektant sekwencji przy użyciu elementu WorkflowItemsPresenter

Rysunek 42. Projektant sekwencji

Na koniec kontrolka ExpressionTextBox umożliwia edytowanie argumentów działań w miejscu w projektancie oprócz siatki właściwości. W programie Visual Studio 2010 obejmuje to obsługę funkcji IntelliSense dla wprowadzonych wyrażeń. Rysunek 43 przedstawia projektanta utworzonego wcześniej działania GetManager, umożliwiając edycję argumentów EmployeeName i ManagerEmail w projektancie. Rzeczywisty projektant jest wyświetlany na rysunku 44. 

<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:sap="clr-namespace:System.Activities.Presentation;

assembly=System.Activities.Presentation"

    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;

assembly=System.Activities.Presentation"

xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;

assembly=System.Activities.Presentation">

    <sap:ActivityDesigner.Resources>

        <sapc:ArgumentToExpressionConverter

x:Key="ArgumentToExpressionConverter"

x:Uid="swdv:ArgumentToExpressionConverter_1" />

    </sap:ActivityDesigner.Resources>

    <Grid>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="50" />

             <ColumnDefinition Width="*" />

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

        </Grid.RowDefinitions>

        <TextBlock VerticalAlignment="Center"

 HorizontalAlignment="Center>" Employee:</TextBlock>

        <sapv:ExpressionTextBox Grid.Column="1"

              Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,

       Converter={StaticResource ArgumentToExpressionConverter},

       ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"

MinLines="1" MaxLines="1" MinWidth="50"

HintText="< Nazwisko pracownika>" />

        <TextBlock Grid.Row="1" VerticalAlignment="Center"

              Adres e-mail programu HorizontalAlignment="Center">Mgr:</TextBlock>

        <sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"

    UseLocationExpression="True"

           Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,

    Converter={StaticResource ArgumentToExpressionConverter},

    ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"

    MinLines="1" MaxLines="1" MinWidth="50" />

    </Grid>

</sap:ActivityDesigner>

Rysunek 43. Edytowanie argumentów w projektancie przy użyciu kontrolki ExpressionTextBox

Rysunek 44. Projektant działań GetManager

Przykładowi projektanci przedstawieni tutaj byli prosti, aby podkreślić konkretne funkcje, ale pełna moc platformy WPF jest do Dyspozycji, aby ułatwić konfigurowanie działań i bardziej naturalne wykorzystanie ich dla użytkowników.   Po utworzeniu projektanta istnieją dwa sposoby skojarzenia go z działaniem: zastosowanie atrybutu do klasy działania lub zaimplementowanie interfejsu IRegisterMetadata.  Aby zezwolić definicji działania na wybór projektanta, wystarczy zastosować atrybut DesignerAttribute do klasy działania i podać typ projektanta; działa dokładnie tak jak w WF3.  W przypadku bardziej luźno powiązanej implementacji można zaimplementować interfejs IRegisterMetadata i zdefiniować atrybuty, które chcesz zastosować do klasy i właściwości działania.  Rysunek 45 przedstawia przykładową implementację, aby zastosować projektantów do dwóch działań niestandardowych. Program Visual Studio odnajdzie implementację i wywoła ją automatycznie, podczas gdy host projektanta niestandardowego omówiony w następnej kolejności wywoła metodę jawnie.

metadane klasy publicznej: IRegisterMetadata

{

    rejestr pustki publicznej()

    {

        AttributeTableBuilder builder = new AttributeTableBuilder();

        budowniczy. AddCustomAttributes(typeof(SendMail), new Attribute[] {

            new DesignerAttribute(typeof(SendMailDesigner))});

        budowniczy. AddCustomAttributes(typeof(GetManager), nowy atrybut[]{

            new DesignerAttribute(typeof(GetManagerDesigner))});

        MetadataStore.AddAttributeTable(builder. CreateTable());

    }

}

Rysunek 45. Rejestrowanie projektantów działań

Ponowne hostowanie projektanta przepływu pracy

W przeszłości deweloperzy często chcieli zapewnić swoim użytkownikom możliwość tworzenia lub edytowania przepływów pracy.  W poprzednich wersjach programu Windows Workflow Foundation było to możliwe, ale nie było to proste zadanie.  Wraz z przejściem do WPF jest nowy projektant i ponowne hostowanie było głównym przypadkiem użycia dla zespołu deweloperów.  Projektant można hostować w niestandardowej aplikacji WPF, tak jak pokazano na rysunku 46 z kilkoma wierszami kodu XAML i kilkoma wierszami kodu. 

Rysunek 46. Ponowne hostowanie projektanta przepływu pracy

Kod XAML okna, Rysunek 47, używa siatki do układu trzech kolumn, a następnie umieszcza kontrolkę obramowania w każdej komórce.  W pierwszej komórce przybornik jest tworzony deklaratywnie, ale można go również utworzyć w kodzie.  Dla samego widoku projektanta i siatki właściwości istnieją dwa puste kontrolki obramowania w każdej z pozostałych komórek.  Te kontrolki są dodawane w kodzie. 

<Okno x:Class="WorkflowEditor.MainWindow"

        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:sys="clr-namespace:System; assembly=mscorlib"

 xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;

assembly=System.Activities.Presentation"

        Title="Edytor przepływu pracy" Height="500" Width="700" >

    <Window.Resources>

        <sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,

Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>

        <sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>

    </Window.Resources>

    <Grid x:Name="DesignerGrid">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="2*" />

            <ColumnDefinition Width="7*" />

            <ColumnDefinition Width="3*" />

        </Grid.ColumnDefinitions>

        < > obramowania

            <sapt:ToolboxControl>

                <sapt:ToolboxControl.Categories>

                    <sapt:ToolboxCategory CategoryName="Basic">

                        <sapt:ToolboxItemWrapper

  AssemblyName="{StaticResource AssemblyName}" >

                            <sapt:ToolboxItemWrapper.ToolName>

                                System.Activities.Statements.Sequence

                            </sapt:ToolboxItemWrapper.ToolName>

                        </sapt:ToolboxItemWrapper>

                        <sapt:ToolboxItemWrapper

AssemblyName="{StaticResource MyAssemblyName}">

                            <sapt:ToolboxItemWrapper.ToolName>

                                CustomActivities.SendMail

                             </sapt:ToolboxItemWrapper.ToolName>

                        </sapt:ToolboxItemWrapper>

                    </sapt:ToolboxCategory>

                </sapt:ToolboxControl.Categories>

            </sapt:ToolboxControl>

        </> obramowania

        <Border Name="DesignerBorder" Grid.Column="1"

BorderBrush="Salmon" BorderThickness="3" />

        <obramowanie x:Name="PropertyGridExpander" Grid.Column="2" />

    </Grid>

</Window>

Rysunek 47. Ponowne hostowanie XAML przez projektanta

Kod inicjowania projektanta i siatki właściwości jest wyświetlany na rysunku 48.  Pierwszym krokiem w metodzie OnInitialized jest zarejestrowanie projektantów dla wszystkich działań, które będą używane w projektancie przepływu pracy.  Klasa DesignerMetadata rejestruje skojarzonych projektantów dla działań, które są dostarczane ze strukturą, a klasa Metadata rejestruje projektantów dla moich działań niestandardowych.  Następnie utwórz klasę WorkflowDesigner i użyj właściwości View i PropertyInspectorView, aby uzyskać dostęp do obiektów UIElement wymaganych do renderowania.  Projektant jest inicjowany przy użyciu pustej sekwencji, ale można również dodać menu i załadować kod XAML przepływu pracy z różnych źródeł, w tym systemu plików lub bazy danych. 

public MainWindow()

{

    InitializeComponent();

}

protected override void OnInitialized(EventArgs e) {

    baza. OnInitialized(e);

    RegisterDesigners();

    PlaceDesignerAndPropGrid();

}

private void RegisterDesigners() {

    new DesignerMetadata(). Register();

    new CustomActivities.Presentation.Metadata(). Register();

}

private void PlaceDesignerAndPropGrid() {

    WorkflowDesigner designer = new WorkflowDesigner();

    projektant. Load(nowy System.Activities.Statements.Sequence());

    DesignerBorder.Child = projektant. Widok;

    PropertyGridExpander.Child = designer. PropertyInspectorView;

}

Rysunek 48. Ponowne hostowanie kodu projektanta

Za pomocą tego małego kodu i znaczników można edytować przepływ pracy, przeciągać i upuszczać działania z przybornika, konfigurować zmienne w przepływie pracy i manipulować argumentami działań.  Możesz również uzyskać tekst XAML, wywołując funkcję Flush w projektancie, a następnie odwołując się do właściwości Text elementu WorkflowDesigner.  Jest o wiele więcej, co można zrobić, a rozpoczęcie pracy jest znacznie łatwiejsze niż wcześniej. 

Usługi przepływu pracy

Jak wspomniano wcześniej, jednym z dużych obszarów koncentracji uwagi w tej wersji programu Windows Workflow Foundation jest ściślejsza integracja z programem Windows Communication Foundation.  W przykładzie w przewodniku działania pokazano, jak wywołać usługę z przepływu pracy, a w tej sekcji omówimy sposób uwidaczniania przepływu pracy jako usługi WCF. 

Aby rozpocząć, utwórz nowy projekt i wybierz projekt usługi przepływu pracy WCF. Po zakończeniu szablonu znajdziesz projekt z plikiem web.config i plikiem z rozszerzeniem XAMLX.  Plik XAMLX jest usługą deklaratywną lub usługą przepływu pracy, podobnie jak inne szablony WCF, zapewnia działającą, choć prostą implementację usługi, która odbiera żądanie i zwraca odpowiedź.  W tym przykładzie zmienię kontrakt, aby utworzyć operację, aby otrzymać raport wydatków i zwrócić wskaźnik, że raport został odebrany.  Aby rozpocząć, dodaj klasę ExpenseReport do projektu, taką jak pokazana na rysunku 49.

[DataContract]

public class ExpenseReport

{

    [DataMember]

    public DateTime FirstDateOfTravel { get; set; }

    [DataMember]

    publiczny podwójny sumAmount { get; set; }

    [DataMember]

    ciąg publiczny EmployeeName { get; set; }

}

Rysunek 49. Typ kontraktu raportu wydatków

Po powrocie do projektanta przepływu pracy zmień typ zmiennej "data" w zmiennych na typ ExpenseReport zdefiniowany po prostu (może być konieczne skompilowanie projektu typu, aby typ był wyświetlany w oknie dialogowym selektora typów). Zaktualizuj działanie Odbierz, klikając przycisk Zawartość i zmieniając typ w oknie dialogowym na typ ExpenseReport, aby był zgodny.  Zaktualizuj właściwości działania, aby zdefiniować nową wartość OperationName i ServiceContractName, jak pokazano na rysunku 50. 

Rysunek 50. Konfigurowanie lokalizacji odbierania

Teraz działanie SendReply może mieć wartość Wartość ustawioną na True, aby zwrócić wskaźnik paragonu.  Oczywiście w bardziej skomplikowanym przykładzie można użyć pełnej mocy przepływu pracy, aby zapisać informacje w bazie danych, przeprowadzić walidację raportu itp. przed ustaleniem odpowiedzi. Przechodzenie do usługi za pomocą aplikacji WCFTestClient pokazuje, jak konfiguracja działania definiuje uwidoczniony kontrakt usługi, jak pokazano na rysunku 51. 

Rysunek 51. Kontrakt wygenerowany na podstawie usługi przepływu pracy

Po utworzeniu usługi przepływu pracy musisz ją hostować tak samo jak w przypadku dowolnej usługi WCF.  W poprzednim przykładzie użyto najprostszej opcji hostingu: IIS.  Aby hostować usługę przepływu pracy we własnym procesie, należy użyć klasy WorkflowServiceHost znajdującej się w zestawie System.ServiceModel.Activities.  Należy pamiętać, że w zestawie System.WorkflowServices istnieje inna klasa o tej samej nazwie, która jest używana do hostowania usług przepływu pracy utworzonych przy użyciu działań WF3.  W najprostszym przypadku samodzielnego hostingu możesz użyć kodu, takiego jak pokazano na rysunku 52, aby hostować usługę i dodawać punkty końcowe. 

Host WorkflowServiceHost = nowy

    WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),

    nowy identyfikator URI("https://localhost:9897/Services/Expense"));

gospodarz. AddDefaultEndpoints();

gospodarz. Description.Behaviors.Add(

    new ServiceMetadataBehavior { HttpGetEnabled = true });

gospodarz. Open();

Console.WriteLine("Host jest otwarty");

Console.ReadLine();

Rysunek 52. Samoobsługowe hostowanie usługi przepływu pracy

Jeśli chcesz skorzystać z opcji hostingu zarządzanego w systemie Windows, możesz hostować usługę w usługach IIS, kopiując plik XAMLX do katalogu i określając wszelkie niezbędne informacje o konfiguracji zachowań, powiązań, punktów końcowych itp. w pliku web.config.  W rzeczywistości, jak pokazano wcześniej, podczas tworzenia nowego projektu w programie Visual Studio przy użyciu jednego z szablonów projektów usługi deklaratywnego przepływu pracy otrzymasz projekt z plikiem web.config i usługą przepływu pracy zdefiniowaną w języku XAMLX gotowym do wdrożenia. 

Jeśli używasz klasy WorkflowServiceHost do hostowania usługi przepływu pracy, nadal musisz mieć możliwość konfigurowania i kontrolowania wystąpień przepływu pracy.  Aby kontrolować usługę, istnieje nowy standardowy punkt końcowy o nazwie WorkflowControlEndpoint.  Klasa towarzysza WorkflowControlClient udostępnia wstępnie utworzony serwer proxy klienta .  Możesz uwidocznić punkt roboczy WorkFlowControlEndpoint w usłudze i użyć elementu WorkflowControlClient, aby utworzyć i uruchomić nowe wystąpienia przepływów pracy lub kontrolować uruchomione przepływy pracy.  Ten sam model służy do zarządzania usługami na komputerze lokalnym lub zarządzania usługami na maszynie zdalnej.  Rysunek 53 przedstawia zaktualizowany przykład uwidaczniania punktu końcowego sterowania przepływem pracy w usłudze. 

Host WorkflowServiceHost = nowy

    WorkflowServiceHost("ExpenseReportService.xamlx",

    nowy identyfikator URI("https://localhost:9897/Services/Expense"));

gospodarz. AddDefaultEndpoints();

WorkflowControlEndpoint wce = nowy WorkflowControlEndpoint(

    nowy NetNamedPipeBinding(),

    new EndpointAddress("net.pipe://localhost/Expense/WCE"));

gospodarz. AddServiceEndpoint(wce);

gospodarz. Open();

Rysunek 53. Punkt końcowy sterowania przepływem pracy

Ponieważ nie tworzysz bezpośrednio klasy WorkflowInstance, istnieją dwie opcje dodawania rozszerzeń do środowiska uruchomieniowego.  Po pierwsze, można dodać pewne rozszerzenia do elementu WorkflowServiceHost i prawidłowo zainicjować wystąpienia przepływu pracy.  Drugi używa konfiguracji.  System śledzenia można na przykład całkowicie zdefiniować w pliku konfiguracji aplikacji.   Należy zdefiniować uczestników śledzenia i profile śledzenia w pliku konfiguracji, a także za pomocą zachowania zastosować określonego uczestnika do usługi, jak pokazano na rysunku 54. 

<system.serviceModel>

    > śledzenia <

      uczestnicy <>

        <add name="EtwTrackingParticipant"

             type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

             profileName="HealthMonitoring_Tracking_Profile"/>

      </uczestnicy>

      > profilów <

        <trackingProfile name="HealthMonitoring_Tracking_Profile">

          działanie przepływu pracy <DefinitionId="*">

            <workflowInstanceQuery>

              <stany>

                <state name="Started"/>

                <state name="Completed"/>

              </states>

            </workflowInstanceQuery>

          </workflow>

        </trackingProfile>

      </profiles>

</tracking>

. . .

<zachowania>

      <serviceBehaviors>

        <nazwa zachowania="SampleTrackingSample.SampleWFBehavior">

          <etwTracking profileName=" HealthMonitoring_Tracking_Profile" />

        </behavior>

      </serviceBehaviors>

</behaviors>

. . .

Rysunek 54. Konfigurowanie śledzenia usług

Istnieje wiele nowych ulepszeń hostingu i konfiguracji dla usług WCF, które można wykorzystać w przepływach pracy, takich jak usługi ".svc-less" lub usługi, które nie wymagają rozszerzenia pliku, powiązań domyślnych i domyślnych punktów końcowych tylko po to, aby wymienić kilka.  Aby uzyskać więcej informacji na temat tych i innych funkcji w programie WCF4, zapoznaj się z dokumentem towarzyszącym w programie WCF znajdującym się w sekcji Dodatkowe zasoby. 

Oprócz ulepszeń hostingu program Windows Workflow Foundation korzysta z innych funkcji w programie WCF; niektóre bezpośrednio i inne pośrednio.  Na przykład korelacja komunikatów jest teraz kluczową funkcją w tworzeniu usług, która umożliwia powiązanie komunikatów z danym wystąpieniem przepływu pracy na podstawie danych w komunikacie, takim jak numer zamówienia lub identyfikator pracownika. Korelację można skonfigurować w różnych działaniach obsługi komunikatów zarówno dla żądania, jak i odpowiedzi, i obsługuje korelację na wielu wartościach w komunikacie.  Więcej szczegółów na temat tych nowych funkcji można znaleźć w dokumencie towarzyszącym w programie WCF4.   

Konkluzja

Nowa technologia Windows Workflow Foundation dostarczana z programem .NET Framework 4 stanowi główną poprawę wydajności i produktywności deweloperów oraz realizuje cel deklaratywnego tworzenia aplikacji dla logiki biznesowej.  Platforma udostępnia narzędzia do krótkich jednostek skomplikowanej pracy, które należy uprościć, oraz do tworzenia złożonych długotrwałych usług z skorelowanym komunikatem, stanem utrwalonego i rozbudowaną widocznością aplikacji za pośrednictwem śledzenia. 

Informacje o autorze

Matt jest członkiem personelu technicznego firmy Pluralsight, gdzie koncentruje się na technologiach połączonych systemów (WCF, WF, BizTalk, AppFabric i Platformie usług Platformy Azure). Matt jest również niezależnym konsultantem specjalizującym się w projektowaniu i tworzeniu aplikacji microsoft .NET. Jako pisarz Matt przyczynił się do kilku czasopism i magazynów, w tym MSDN Magazine, gdzie obecnie autorem zawartości przepływu pracy dla kolumny Foundations. Matt regularnie dzieli się swoją miłością do technologii, przemawiając na lokalnych, regionalnych i międzynarodowych konferencjach, takich jak Tech Ed. Firma Microsoft uznała Matta za MVP za wkład społeczności w technologię połączonych systemów. Skontaktuj się z Mattem za pośrednictwem swojego bloga: http://www.pluralsight.com/community/blogs/matt/default.aspx.

Dodatkowe zasoby