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.

Omówienie

Jak wiedzą deweloperzy oprogramowania, 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ą programowanie, 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, oczyszczanie niepotrzebnych obiektów i obsługę 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 odpowiadające 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 deklaratywnego modelu programowania dla wewnętrznych aplikacji, ponieważ mamy do interfejsu użytkownika w Windows Presentation Foundation (WPF). Program Windows Workflow Foundation (WF) udostępnia strukturę deklaratywną do tworzenia logiki aplikacji i usług oraz zapewnia deweloperom język wyższego poziomu do obsługi asynchronicznych, równoległych zadań i innych złożonych procesów 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 logiki i przepływu sterowania oraz środowisko uruchomieniowe do wykonywania wynikowej definicji aplikacji.  Krótko mówiąc, WF polega na używaniu języka wyższego poziomu do pisania aplikacji, a celem jest zwiększenie produktywności deweloperów, łatwiejsze zarządzanie aplikacjami i szybsze wprowadzanie zmian w implementacji.  Środowisko uruchomieniowe platformy WF wykonuje nie tylko przepływy pracy, ale także udostępnia usługi i funkcje ważne podczas pisania logiki aplikacji, takich jak trwałość stanu, tworzenie zakładek i ponowne tworzenie 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ść, zalecamy przeczytanie tematu "The Workflow Way" autorstwa Davida Chappella w sekcji Dodatkowe zasoby. 

Co nowego w programie WF4

W wersji 4 .NET Framework firmy Microsoft® 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 wydajność, 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ć programowi WF dalsze korzystanie z silnego podstawowego składnika, który 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 platformy zgodne z poprzednimi wersjami znajdują się w zestawach System.Workflow.*.  Zestawy System.Workflow.* są częścią .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 znalezionych w zestawach System.Activities.* i WF3, aby odwołać się do składników znalezionych w zestawach System.Workflow.*. 

Projektanci

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, a projektanci są oparte na Windows Presentation Foundation (WPF), wykorzystując w pełni bogate środowisko użytkownika, które można utworzyć za pomocą struktury deklaratywnego 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 osobom niebędącym deweloperami wyświetlania przepływów pracy i korzystania z tych przepływów pracy jest teraz znacznie łatwiejsze. 

Przepływ danych

W programie WF3 przepływ danych w przepływie pracy był nieprzezroczysły.  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 koncepcje i procesy myślowe, które wielu analityków i deweloperów przechodzi przez proces tworzenia rozwiązań lub projektowania procesów biznesowych.  W związku z tym warto zapewnić działanie, aby ułatwić modelowanie pojęć 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 logika przełącznika/przypadku. 

Model programowania

Model programowania WF został przebudowany, 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 pracyUruchomienie przepływu pracy, aby wywołać przepływ 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 programowania kodu, co upraszcza 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 włożyło się w zwiększenie 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 to główne obszary poprawy. 

Wprowadzenie z przepływem pracy

Najlepszym sposobem zrozumienia platformy WF jest rozpoczęcie korzystania z niej i zastosowanie pojęć.  Omówię kilka podstawowych pojęć dotyczących podstaw przepływu pracy, a następnie omówię tworzenie kilku prostych przepływów pracy, aby zilustrować, w jaki sposób 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 mogą być tworzone 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 definiowania stylu przepływu sterowania głównego dla przepływu pracy. Każde 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 przepływie sterowania głównego to Sekwencja i Schemat blokowy.  Chociaż dowolne działanie można wykonać jako przepływ pracy, te dwa zapewniają najbardziej typowe wzorce projektowe służące do definiowania logiki biznesowej.   Rysunek 2 przedstawia przykład modelu sekwencyjnego przepływu pracy definiującego przetwarzanie odebranego zamówienia, zapisanego, a następnie wysyłania powiadomień 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 powinno to obejmować coś podobnego do przedstawionego na rysunku 3, w którym działanie DoWhile służy 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 patrzący na 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 projektowanie przepływu pracy ściślej pasuje do myślenia i wymagań zgodnie z pierwotnymi definicjami. 

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.  W programie 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ą, najpierw określ 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 zbieranych danych źródła danych. 

Rysunek 5. Zmienne w zakresie działań

Argumenty — przekazywanie danych

Argumenty są definiowane na działaniach i definiują przepływ danych do i z działania.  Argumenty do 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 w działaniu głównym, które umożliwia przekazywanie danych do przepływu pracy po wywołaniu.  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ę w następnej kolejności.  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żywać nazw zmiennych do odwoływania 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 obsługi 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żywać wyrażeń w argumentach ustawiania lub definiować warunki działań, takich jak działania While lub If.  Należy pamiętać, że większość elementów w programie WF4 pochodzi z działania i wyrażeń nie różni się, są one pochodną TResult> działania<, 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 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, po omówieniu podstawowych pojęć związanych z przepływem działań i danych, mogę utworzyć przepływ pracy przy użyciu tych pojęć.  Zaczniem 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 programu 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(

        "Hello\r\nWorld\r\n",

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

        "Nieprawidłowy ciąg zapisany");

}

Rysunek 7. Tworzenie środowiska hello 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 do 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"},

        nowy ciąg> zmiennej<{ Default = "Bill", Name = "name" } },

        Działania = {

            new WriteLine { Text = new VisualBasicValue<string>("greeting"),

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

            "name + \"Gates\"")}

        }

    };

    WorkflowInvoker.Invoke(wf);

}

Rysunek 8. Przepływ pracy kodu ze zmiennymi

W tym przypadku zmienne są definiowane jako ciąg> zmiennej<i podane wartości domyślnej.  Zmienne są deklarowane w działaniu Sekwencja, a następnie przywoływane z argumentu Text dwóch działań.  Alternatywnie można użyć wyrażeń do zainicjowania zmiennych, jak pokazano na rysunku 9, gdzie klasa TResult> VisualBasicValue<jest używana 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<string>("greeting"),

        TextWriter = writer },

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

        TextWriter = writer }

}

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

Chociaż przykłady kodu pomagają zilustrować ważne punkty 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 rozwijane regiony u dołu, aby edytować argumenty przepływu pracy i zmiennych.   Aby utworzyć podobny przepływ pracy w projektancie, dodaj nowy projekt do rozwiązania, wybierając szablon Biblioteki 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 schematu blokowego 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 razem, aby schemat blokowy znał ścieżkę do naśladowania.  W tym celu najpierw umieść kursor na zielonym okręgu "start" u góry schematu blokowego, aby wyświetlić uchwyty chwytania, a następnie kliknij i przeciągnij jeden do pierwszej linii zapisu i upuść go na uchwytie przeciągania, który pojawia się w górnej części działania.  Wykonaj to samo, aby połączyć pierwszą linię zapisu z drugą linią 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, aby ciągi literałów były cytowane.  Rysunek 11 przedstawia okno zmiennych skonfigurowane przy użyciu dwóch zmiennych i ich wartości domyślnych. 

Rysunek 11. Zmienne zdefiniowane w projektancie przepływu pracy

Aby użyć tych zmiennych w działaniach WriteLine, wprowadź "greeting" (bez cudzysłowów) w siatce właściwości 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ą, jak pokazano 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 do klasy pochodzącej z działania, która następnie może zostać wywołana. 

[TestMethod]

public void TestHelloFlowChart()

{

    StringWriter tWriter = new StringWriter();

    Console.SetOut(tWriter);

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

    WorkflowInvoker.Invoke(wf);

    Potwierdzenia pominięte

}

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" (zaznacz ją i naciśnij klawisz Delete), a zamiast tego utwórz argument typu "name" typu string.  Można to zrobić w taki sam sposób, z wyjątkiem używania 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, jak 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 do kodu wywołującego. 

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

W schemacie blokowym dodaj działanie Assign i połącz je z ostatnim działaniem WriteLine.  W przypadku argumentu Do wprowadź wartość "fullGreeting" (bez cudzysłowów) i jako argument Wartość wprowadź "nazwa powitania & " (bez cudzysłowów). Spowoduje to przypisanie łączenia zmiennej powitania z argumentem name do argumentu fullGreeting output. 

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żna 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<, kolekcji obiektów> kluczy w nazwie argumentu. 

Ciąg IDictionary<, object> results = WorkflowInvoker.Invoke(wf,

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

        {"name", "Bill" } }

    );

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

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

Teraz, gdy znasz już podstawy łączenia działań, zmiennych i argumentów, poprowadzę Cię przez przewodnik po działaniach zawartych w strukturze, aby umożliwić bardziej interesujące przepływy pracy skoncentrowane na logice biznesowej zamiast koncepcji 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 deweloperów 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 and While, do zarządzania przepływem sterowania potrzebne są również te same możliwości podczas definiowania logiki w deklaratywnej przepływie pracy.  Te możliwości są dostępne w formie podstawowej biblioteki działań dostarczanej ze strukturą.  W tej sekcji przedstawię krótki przewodnik po działaniach dostarczanych ze strukturą, aby dać Ci pojęcie funkcjonalności dostarczonej z pudełka.

Działania pierwotne i działania kolekcji

Podczas przechodzenia do deklaratywnego modelu programowania można łatwo 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ływać polecenia lub zarządzać kolekcją elementów, istnieje zestaw działań zaprojektowanych specjalnie z tymi zadaniami. 

Działanie Opis

Przypisanie

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

Opóźnienie

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 Conditions(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. 

Działanie Opis

Sequence

Do wykonywania działań w serii

While/DoWhile

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

ForEach<T>

Iteruje po wyliczonej kolekcji 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 dostęp wpisany do pojedynczego elementu napędzającego iterację w postaci nazwanego argumentu.

Jeśli użytkownik

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

Przełącznik<T>

Oblicza wyrażenie i planuje działanie podrzędne za pomocą pasującego klucza. 

Równoległy

Planuje wszystkie działania podrzędne jednocześnie, ale także udostępnia 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 możliwej do wyliczenia i wykonuje działanie podrzędne raz dla każdego elementu w kolekcji, planując jednocześnie wszystkie wystąpienia. Podobnie jak w przypadku elementu ForEach<T>, to działanie zapewnia dostęp do bieżącego elementu danych w postaci nazwanego argumentu.

Pick

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żdy 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 elementu ParallelForEach<T> do użycia listy adresów URL i asynchroniczne pobieranie kanału informacyjnego RSS pod określonym adresem.  Po zwróceniu kanału informacyjnego element T> forEach<jest używany do iteracji nad elementami kanału informacyjnego i przetwarzania ich. 

Zwróć uwagę, ż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 wystąpień języka T> visualBasicValue<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 wymienione w sekcji dotyczącej tworzenia działań niestandardowych.  Te akcje używają funkcji DelegateInArgument i DelegateOutArgument w taki sam sposób, jak w przypadku działań 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 opóźnienia zostanie wysłane przypomnienie, a po zakończeniu działania ManagerResponse flaga zostanie ustawiona na przerwanie pętli DoWhile. 

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

W przykładzie na rysunku 16 przedstawiono również sposób użycia zakładek w przepływach pracy.  Zakładka jest tworzona przez działanie, aby oznaczyć miejsce w przepływie pracy, dzięki czemu przetwarzanie może być wznowione od tego momentu w późniejszym czasie.  Działanie Opóźnienie ma zakładkę, która jest wznawiana po upływie określonego czasu.  Działanie ManagerResponse to działanie niestandardowe, które czeka na dane wejściowe i wznawia przepływ pracy po nadejściu danych.  Działania związane z obsługą 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ć utrwalone 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 programu WF3 działanie międzyoperacyjności 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ędzyoperacyjnego 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 pozwala na 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 .NET Framework 4 Profil klienta.  Zmień strukturę docelową we właściwościach projektu na .NET Framework 4, a działanie będzie wyświetlane w przyborniku.

Rysunek 17. Konfiguracja działania międzyoperacyjnego

Schemat blokowy

Podczas projektowania przepływów pracy schematu blokowego istnieje kilka konstrukcji, których można użyć do zarządzania przepływem wykonywania w schemacie blokowym.  Same konstrukcje te zapewniają proste kroki, proste punkty decyzyjne na podstawie jednego warunku lub instrukcji switch.  Realną moc schematu blokowego jest możliwość połą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 następujących konstrukcji, ale do wykonania, musi być połączony w schemacie blokowym. 

FlowDecision

Zapewnia logikę rozgałęziania na podstawie warunku.

FlowSwitch<T>

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

Flowstep

Reprezentuje krok w schemacie blokowym z możliwością połączenia 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ą określone 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 bezpośrednio w środku. 

Działania dotyczące komunikatów

Jednym z głównych czynnikó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 w celu modelowania operacji obsługi komunikatów, takich jak wysyłanie i odbieranie komunikatów.  Istnieje w rzeczywistości kilka różnych działań zawartych w strukturze obsługi komunikatów, z których każda ma nieco inną funkcjonalność i przeznaczenie. 

Działanie Opis

Wysyłanie/odbieranie

Jedno ze sposobów wysyłania lub odbierania wiadomości.  Te same działania składają się na interakcje 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ę uchwytu współużytkowanego przez kilka działań obsługi komunikatów. 

TransactedReceiveScope

Umożliwia włączenie 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 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]

    List<HotelSearchResult> SearchHotels(

        HotelSearchRequest requestDetails);

}

Rysunek 18. Kontrakt usługi

Rysunek 19. Niestandardowe działanie 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ć zachowaniem spójnego stanu w aplikacji.  W przypadku przepływu pracy zakresy zarządzania obsługą wyjątków i transakcji 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, WF4 obejmuje również działania, które umożliwiają modelowanie długotrwałej koordynacji rozproszonej poprzez rekompensatę i potwierdzenie. 

Działanie Opis

CancellationScope

Służy do zezwalania deweloperowi przepływu pracy na reagowanie, jeśli treść pracy zostanie anulowana. 

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 wyjątków typowych. 

Throw

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

Rethrow

Służy do ponownego wymuś wyjątku, zazwyczaj tego, który został złapany przy użyciu działania TryCatch. 

CompensableActivity

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

Compensate

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

Confirm

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

Działanie TryCatch zapewnia znany sposób na zakres zestawu pracy w celu przechwycenia wszelkich wyjątków, które mogą wystąpić, a działanie Catch<T> udostępnia 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 typowy 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 przechwycenia.   Jeśli zajdzie taka potrzeba, działanie Rethrow może służyć do ponownego wyrzucenia wyjątku lub działania Throw w celu zgłoszenia nowego wyjątku. 

Rysunek 20. Działanie TryCatch

Transakcje pomagają zapewnić spójność w 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ć transakcji niepodzielnej w ramach zasobów, możesz użyć rekompensaty.  Rekompensata umożliwia zdefiniowanie zestawu pracy, który, jeśli zostanie ukończony, może mieć zestaw działań w celu zrekompensowania wprowadzonych zmian.  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, aby wycofać wcześniejszą wiadomość. 

Rysunek 21. Działanie z możliwością współkompensowania

Tworzenie i wykonywanie przepływów pracy

Podobnie jak w przypadku dowolnego języka programowania, istnieją dwie podstawowe rzeczy, 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ływanie się do działań i typów utworzonych przy użyciu platformy .NET.  Większość deweloperów prawdopodobnie będzie używać projektanta przepływu pracy do tworzenia przepływów pracy, co spowoduje deklaratywną definicję przepływu pracy XAML.  Ponieważ język XAML to tylko kod XML, jednak każde narzędzie może służyć 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:Sekwencja>

    <p:WriteLine>[greeting &amp; " from workflow"]</p:WriteLine>

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

  </p:Sekwencja>

</p:Działanie>

Rysunek 22. Przepływ pracy zdefiniowany w języku 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 programu WF3 teraz można utworzyć przepływ pracy w kodzie i łatwo serializować go w języku XAML; zapewnia większą elastyczność modelowania definicji przepływów 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 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łaniu. 

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 uzyskania działania, które można wykonać: utwórz je w kodzie lub odczytaj w pliku XAML i deserializuj zawartość do działania.  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ę load.  Wystarczy przekazać obiekt Stream lub XamlReader i odzyskać 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łownik 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. 

Matematyka aktywnościF;

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"], "Incorrect result returned");

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 do środowiska uruchomieniowego na potrzeby wykonywania.  

Korzystanie z 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, co umożliwia łatwiejsze korzystanie ze wszystkich zalet platformy WF bez konieczności wykonywania wielu zadań 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 testowego składnika. 

Inna typowa klasa hostingu to 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óra może 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, bezczynny i zwolniony.  Po wykonaniu przepływu pracy przy użyciu funkcji WorkflowApplication możesz użyć kodu podobnego do kodu pokazanego 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", e.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 aplikacji przepływu pracy

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 platformy WF, ponieważ WF3, jest to, że jest wystarczająco lekki, aby być hostowanym 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ętrznie ze środowiska uruchomieniowego.  W tym miejscu wchodzą w grę 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ń, o których pamięta ś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 w razie potrzeby.  Rozszerzenie trwałości wysyłki wraz z platformą obejmuje obsługę platformy Microsoft SQL Server, ale rozszerzenia mogą być zapisywane w celu obsługi innych systemów baz danych lub magazynów trwałych. 

Trwałość

Aby użyć rozszerzenia trwałości, należy najpierw skonfigurować bazę danych do przechowywania stanu, co można zrobić przy użyciu skryptów SQL znajdujących się w folderze %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<language> (np. c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). Po utworzeniu bazy danych należy wykonać 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 w 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. InstanceOwner;

Aplikacji. 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 Persist 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.  Pozwala to na kontrolę autora przepływu pracy, gdy ważne jest zapisanie bieżącego stanu przepływu pracy.  Druga opcja umożliwia aplikacji hostingowej utrwalanie stanu przepływu pracy po wystąpieniu różnych zdarzeń 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 reagować na zdarzenie, aby wskazać, czy wystąpienie powinno być utrwalone, rozładowane lub nie.  Rysunek 26 przedstawia rejestrację aplikacji hosta 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

Po bezczynnym i trwałym przepływie pracy środowisko uruchomieniowe programu WF może zwolnić go z pamięci, zwalniając zasoby dla innych przepływów pracy wymagających przetwarzania.  Możesz zwolnić wystąpienie przepływu pracy, zwracając odpowiednią wyliczenie z akcji PersistableIdle.  Po rozładowaniu przepływu pracy host będzie chciał załadować go z powrotem z magazynu trwałości, aby go wznowić.  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. 

Aplikacja WorkflowApplication = nowa aplikacja WorkflowApplication(activity);

Aplikacji. InstanceStore = instanceStore;

Aplikacji. Load(id);

Aplikacji. ResumeBookmark(readLineBookmark, input);

Rysunek 27. Wznowienie utrwalonego przepływu pracy

Jedna ze zmian w programie WF4 polega na tym, jak stan przepływów pracy jest utrwalany i opiera się w dużym stopniu na nowych technikach zarządzania danymi argumentów i zmiennych.  Zamiast serializować całe drzewo działań i utrzymywać stan przez cały okres 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 jest również przekazywane. 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 mniejszy rozmiar pamięci. 

Śledzenie

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 dla zarządzania przepływami pracy i uzyskiwania wglądu w proces biznesowy.  Możliwość śledzenia tego, co dzieje się w aplikacji, jest jedną z atrakcyjnych funkcji korzystania ze środowiska uruchomieniowego 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 element ActivityStateQuery i WorkflowInstanceQuery.   Zwróć uwagę, że zapytania wskazują zarówno czas zbierania informacji, jak i dane do zebrania.  W przypadku elementu ActivityStateQuery zawierałem listę zmiennych, które powinny zawierać ich wartość wyodrębnione 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 emisji.  Klasa bazowa 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 przy użyciu profilu śledzenia, a następnie dodać go do kolekcji rozszerzeń w wystąpieniu przepływu pracy. 

public class ConsoleTrackingParticipant: TrackingParticipant

{

   chronione przesłonięcia void Track(Rekord TrackingRecord, limit czasu przedziału czasu)

   {

      ActivityStateRecord aRecord = rekord jako ActivityStateRecord;

      if (aRecord != null)

      {

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

            aRecord.Activity.Name, aRecord.State);

         foreach (element var w aRecord.Arguments)

         {

            Console.ForegroundColor = ConsoleColor.Cyan;

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

                Element. Klucz, element. Wartość);

            Console.ResetColor();

         }

      }

   }

}

Rysunek 29. Uczestnik śledzenia 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 programie 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ż nie będzie wysyłki uczestników śledzenia SQL w ramach platformy.  Jednak system 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 większe znaczenie dla twojego środowiska. 

Tworzenie działań niestandardowych

Biblioteka działań podstawowych zawiera bogatą paletę działań do interakcji 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.  Wspaniałą 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 będziesz pochodzić z funkcji AsyncCodeActivity, CodeActivity lub NativeActivity (albo jednego z wariantów ogólnych) albo modelować działanie deklaratywne.  Na wysokim poziomie cztery klasy bazowe można opisać w następujący sposób:

  • Działanie — używane do modelowania działań przez tworzenie innych działań, zwykle zdefiniowanych przy użyciu języka XAML.
  • CodeActivity — uproszczona klasa bazowa, gdy trzeba napisać kod, aby wykonać pracę.
  • AsyncCodeActivity — używana podczas wykonywania pewnych działań asynchronicznie.
  • NativeActivity — gdy działanie wymaga dostępu do wewnętrznych elementów środowiska uruchomieniowego, na przykład w celu planowania innych działań lub tworzenia 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 tym, która klasa bazowa ma być używana, należy zacząć od klasy bazowej Działania 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), konieczne będzie wyprowadzenie z klasy bazowej NativeActivity w celu zarządzania 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 ramach działania, 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 tworzy dwie 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 Sequence 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 o działaniu menedżera

To działanie musi przyjmować 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, musisz mieć możliwość przekazania argumentów wejściowych określonych 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żna wprowadzić nazwę argumentu zawierającego komunikat.  Jednak w przypadku adresu e-mail potrzebny jest sposób przekazywania 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ć tę nazwę 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 po wykonaniu działania SendMail odczytuje dane z tej zmiennej jako argument in. 

Opisane właśnie podejście jest czysto deklaratywnym modelem 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 napędzają zestaw 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), aby reprezentować 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 class NotifyManager : Działanie

{

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

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

    chronione zastępowanie implementacji działania> func<

    {

        get

        {

            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"

                        }

                    }

                };

                return s;

            };

        }

        set { base. Implementacja = wartość; }

    }

}

Rysunek 32. Kompozycja działań przy użyciu kodu

Zwróć uwagę, że klasa bazowa to Activity, a właściwość Implementation jest działaniem> func<, 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 utworzyć 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 Działania i utworzenia działań TryCatch<T> i InvokeMethod, dla większości deweloperów bardziej naturalne będzie wybranie między klasami podstawowymi CodeActivity i NativeActivity oraz pisaniem kodu 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, powinno pochodzić z klasy CodeActivity<TResult>, która zapewnia wynik TResult outArgument<>.  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> InArgument<Subject { get; set; }

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

    chronione zastąpienia SmtpStatusCode Execute(

    Kontekst CodeActivityContext)

    {

        try

        {

            Klient SmtpClient = nowy klient SmtpClient();

            Klienta. Send(

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

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

        }

        catch (SmtpException smtpExex)

        {

            return smtpEx.StatusCode;

        }

        return SmtpStatusCode.Ok;

    }

}

Rysunek 33. Działanie niestandardowe usługi SendMail

Istnieje kilka kwestii, które należy wziąć pod uwagę przy użyciu 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 swoje 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 uchwytu, aby pobrać wartość przy użyciu podanego elementu ActivityContext; w tym przypadku codeActivityContext.  Metoda Get klasy argumentów służy do pobierania wartości i metody Set w celu przypisania wartości do argumentu. 

Po zakończeniu działania metoda 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 do zarządzania działaniami podrzędnymi.  Skompiluję działanie iteratora, które wykonuje niektóre działania podrzędne 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 uruchomić iterację początkową, a kolejne iteracji są wywoływane w taki sam sposób.  Zwróć uwagę na rysunek 34, że zmienne są manipulowane w taki sam sposób, jak argumenty przy użyciu metody Get i Set. 

public class Iterator : NativeActivity

{

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

    public InArgument<int> RequestedIterations { get; set; }

    public Variable<int> CurrentIteration { get; set; }

    publiczny iterator()

    {

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

    }

    chroniony przesłoń void Execute(NativeActivityContext context)

    {

        BeginIteration(context);

    }

    private void BeginIteration(NativeActivityContext context)

    {

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

        {

            Kontekście. 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 funkcji NativeActivity.  Należy również pamiętać, że podczas wywoływania metody ScheduleActivity w obiekcie ActivityExecutionContext dostępne są 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 o zakończeniu działania podrzędnego, co pozwala na pisanie kodu, aby zareagować.

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 otrzymujesz wyjątek i masz możliwość, za pośrednictwem elementu ActivityFaultContext, aby wskazać środowisku uruchomieniowemu, że błąd został obsłużony, co uniemożliwia jego działanie. Rysunek 35 przedstawia metody wywołania zwrotnego dla działania iteratora, gdzie harmonogram następnej iteracji i 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);

    Kontekście. 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 tworzonych wątków.  Jeśli środowisko uruchomieniowe ustaliło, że przepływ pracy był bezczynny, przepływ pracy może zwolnić, usunąć metody wywołania zwrotnego lub założyć, ż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ąpij metodę EndExecute, aby obsłużyć wywołanie zwrotne po zakończeniu operacji asynchronicznej i zwrócić wynik wykonania. 

public sealed, klasa GetFeed: AsyncCodeActivity<SyndicationFeed>

{

    public InArgument<Uri> FeedUrl { get; set; }

    chronione zastąpienie IAsyncResult BeginExecute(

    Kontekst AsyncCodeActivityContext, wywołanie zwrotne AsyncCallback,

    stan obiektu)

    {

        var req = (HttpWebRequest)HttpWebRequest.Create(

        FeedUrl.Get(kontekst));

        Req. Metoda = "GET";

        Kontekście. UserState = req;

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

    }

    protected override 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, z którego 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, aby wystąpienie przepływu pracy było utrwalane i zwalniane.  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 przerwane.  Podczas wznawiania zakładek dane mogą być również przekazywane do działania.   Rysunek 37 przedstawia działanie ReadLine, które tworzy zakładkę do 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 do momentu wznowienia zakładki.  W klasie WorkflowApplication można użyć metody ResumeBookmark, aby wysyłać dane do nazwanej zakładki i sygnalizować element BookmarkCallback.  

public, klasa ReadLine: ciąg NativeActivity<>

{

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

    protected override void Execute(NativeActivityContext context)

    {

        Kontekście. 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 activityAction.  ActivityAction jest odpowiednikiem przepływu pracy klasy akcji w kodzie imperatywnym: opisując wspólny delegat; ale dla niektórych pomysł szablonu może być łatwiejszy do zrozumienia.  Rozważ działanie ForEach<T> , które umożliwia iterowanie zestawu danych i planowanie działania podrzędnego dla każdego elementu danych.  Musisz w jakiś sposób zezwolić użytkownikowi działania na zdefiniowanie treści i móc korzystać z elementu danych typu T. Zasadniczo potrzebujesz pewnego działania, które może zaakceptować element typu T i wykonać na nim działania, funkcja ActivityAction<T> jest używana do włączenia tego scenariusza i zawiera odwołanie do argumentu i definicji działania do przetwarzania elementu.  Możesz użyć działania ActivityAction w działaniach niestandardowych, aby umożliwić użytkownikom 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 użytku.  Możesz również użyć funkcji ActivityFunc<TResult> i powiązanych alternatyw, gdy delegat działania wymaga wywołania zwróci wartość.  

Na koniec działania można zweryfikować, aby upewnić się, że są 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 pojawiły się w klasie Activity.  Ograniczenie jest działaniem zapisanym 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łaniając metodę 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>();

Constraint<Iterator cons = new Constraint<Iterator>>

{

    Body = new ActivityAction<Iterator, ValidationContext>

    {

        Argument1 = act,

        Argument2 = vctx,

        Procedura obsługi = nowa assertValidation

        {

            Message = "Iteracja musi być podana",

            PropertyName = "RequestedIterations",

            Assertion = new InArgument<bool>(

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

        }

    }

};

Podstawowej. Constraints.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ć XAML ręcznie, dlatego doświadczenie projektanta w programie WF jest tak ważne.  Podczas tworzenia działań niestandardowych prawdopodobnie zechcesz również utworzyć projektanta, aby zapewnić użytkownikom działania środowisko interakcji z wyświetlaniem i wizualizacją.  Projektant WF jest oparty na Windows Presentation Foundation co oznacza, że masz wszystkie możliwości stylów, wyzwalaczy, powiązania danych i wszystkich innych wspaniałych narzędzi do tworzenia rozbudowanego interfejsu użytkownika dla projektanta.  Ponadto WF udostępnia pewne 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 w celu dostosowania wyglądu i działania działania przez określenie sposobu wyświetlania danych i wizualnych reprezentacji działań podrzędnych.  W projektancie masz dostęp do elementu ModelItem, który jest abstrakcją nad rzeczywistym działaniem i uwypukli wszystkie 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 na pokazane wcześniej działanie iteratora i wyświetlania działania zawartego w działaniu Iteractor, możesz użyć kodu XAML pokazanego na rysunku 39, powiązanie elementu WorkflowItemPresenter z 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">

  <Siatka>

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

<sap:WorkflowItemPresenter MinHeight="50"

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

HintText="Dodaj treść tutaj" />

    </Granicy>

  </Siatki>

</sap:ActivityDesigner>

Rysunek 39. WorkflowItemPresenter w projektancie działań

Rysunek 40. Projektant iteratora

WorkflowItemsPresenter zapewnia podobną funkcjonalność 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 spacerowego, 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 poziomą linią między poszczególnymi.

<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">

    <Siatka>

      <StackPanel>

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

     <sap:WorkflowItemsPresenter HintText="Drop Activities Here"

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

     <sap:WorkflowItemsPresenter.SpacerTemplate>

 <Datatemplate>

    <Heightle 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>

        </Granicy>

      </Stackpanel>

    </Siatki>

</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>

    <Siatka>

        <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,

       Konwerter={StaticResource ArgumentToExpressionConverter},

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

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

HintText="&lt; Nazwa&pracownika gt;" />

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

              HorizontalAlignment="Center">Mgr Email:</TextBlock>

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

    UseLocationExpression="True"

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

    Konwerter={StaticResource ArgumentToExpressionConverter},

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

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

    </Siatki>

</sap:ActivityDesigner>

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

Rysunek 44. Projektant działań GetManager

Przykładowi projektanci przedstawili tutaj proste wyróżnienie konkretnych funkcji, ale pełną moc WPF można łatwo skonfigurować i bardziej naturalne do użycia dla użytkowników.   Po utworzeniu projektanta istnieją dwa sposoby skojarzenia go z działaniem: stosowanie atrybutu do klasy działania lub implementowanie 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 to dokładnie tak jak w WF3.  W przypadku bardziej luźno powiązanej implementacji można zaimplementować interfejs IRegisterMetadata i zdefiniować atrybuty, które mają być stosowane do klasy i właściwości działania.  Rysunek 45 przedstawia przykładową implementację do zastosowania projektantów dla dwóch działań niestandardowych. Program Visual Studio wykryje implementację i wywoła ją automatycznie, podczas gdy host projektanta niestandardowego omówiony w następnej kolejności wywoła metodę jawnie.

public class Metadata : IRegisterMetadata

{

    rejestr pustki publicznej()

    {

        AttributeTableBuilder builder = new AttributeTableBuilder();

        Program report builder. AddCustomAttributes(typeof(SendMail), new Attribute[] {

            new DesignerAttribute(typeof(SendMailDesigner))});

        Program report builder. AddCustomAttributes(typeof(GetManager), nowy atrybut[]{

            new DesignerAttribute(typeof(GetManagerDesigner))});

        MetadataStore.AddAttributeTable(builder. CreateTable());

    }

}

Rysunek 45. Rejestrowanie projektantów pod kątem 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 dla 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. 

<Window 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>

        <Obramowanie>

            <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>

        </Granicy>

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

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

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

    </Siatki>

</Okno>

Rysunek 47. Projektant ponowne hostowanie kodu XAML

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 z pustą sekwencją, 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) {

    Podstawowej. 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 = designer. Widok;

    PropertyGridExpander.Child = designer. PropertyInspectorView;

}

Rysunek 48. Projektant ponowne hostowanie kodu

Dzięki temu trochę kodu i adiustacji 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ń.  Tekst XAML można również uzyskać, 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 zainteresowania 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 tworzenia 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ę w celu otrzymania raportu wydatków i zwrócenia wskaźnika, ż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]

    public double TotalAmount { 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 (może być konieczne skompilowanie projektu dla typu, aby 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ść Value ustawioną na True, aby zwrócić wskaźnik potwierdzenia.  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 określeniem 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 samoobsługowego 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"));

Hosta. AddDefaultEndpoints();

Hosta. Description.Behaviors.Add(

    new ServiceMetadataBehavior { HttpGetEnabled = true });

Hosta. 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 konfiguracyjne dotyczące 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 XAMLX, gotowym do wdrożenia. 

W przypadku hostowania usługi przepływu pracy przy użyciu klasy WorkflowServiceHost 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ć element WorkFlowControlEndpoint w usłudze i użyć elementu WorkflowControlClient do utworzenia i uruchomienia nowych wystąpień przepływów pracy lub kontrolowania uruchomionych przepływów 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"));

Hosta. AddDefaultEndpoints();

WorkflowControlEndpoint wce = new WorkflowControlEndpoint(

    nowy NetNamedPipeBinding(),

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

Hosta. AddServiceEndpoint(wce);

Hosta. Open();

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

Ponieważ nie tworzysz wystąpienia przepływu pracy bezpośrednio, istnieją dwie opcje dodawania rozszerzeń do środowiska uruchomieniowego.  Pierwszym z nich jest to, że można dodać pewne rozszerzenia do elementu WorkflowServiceHost, a następnie zainicjować je poprawnie przy użyciu wystąpień przepływu pracy.  Drugi używa konfiguracji.  Na przykład system śledzenia można 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>

      <Uczestników>

        <add name="EtwTrackingParticipant"

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

             profileName="HealthMonitoring_Tracking_Profile"/>

      </Uczestników>

      <Profile>

        <trackingProfile name="HealthMonitoring_Tracking_Profile">

          <activity activityDefinitionId="*">

            <workflowInstanceQuery>

              <Państw>

                <state name="Started"/>

                <state name="Completed"/>

              </Państw>

            </workflowInstanceQuery>

          </Przepływu pracy>

        </Trackingprofile>

      </Profile>

</Śledzenia>

. . .

<Zachowania>

      <Servicebehaviors>

        <behavior name="SampleTrackingSample.SampleWFBehavior">

          <etwTracking profileName=" HealthMonitoring_Tracking_Profile" />

        </Zachowanie>

      </Servicebehaviors>

</Zachowania>

. . .

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, domyślnych powiązań 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 artykule 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ć dla różnych działań 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.   

Podsumowanie

Nowa technologia Windows Workflow Foundation dostarczana z systemem .NET Framework 4 stanowi ważną 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 Azure Services Platform). 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