다음을 통해 공유


.NET 4의 WF(Windows Workflow Foundation) 개발자 소개

맷 밀너, Pluralsight

2009년 11월

릴리스로 업데이트됨: 2010년 4월

개요

소프트웨어 개발자가 알고 있듯이 애플리케이션 작성은 어려울 수 있으며, 프로세스를 간소화하고 해결하려는 비즈니스 과제에 집중할 수 있는 도구와 프레임워크를 지속적으로 찾고 있습니다.  어셈블러와 같은 컴퓨터 언어로 코드를 작성하는 것에서 C# 및 Visual Basic과 같은 상위 수준 언어로 전환하여 개발을 용이하게 하고, 메모리 관리와 같은 낮은 수준의 문제를 제거하고, 개발자로서의 생산성을 향상했습니다.  Microsoft 개발자의 경우 .NET으로 이동하면 CLR(공용 언어 런타임)이 메모리를 할당하고 불필요한 개체를 정리하며 포인터와 같은 하위 수준 구문을 처리할 수 있습니다. 

애플리케이션의 복잡성 대부분은 백그라운드에서 진행되는 논리 및 처리에 있습니다.  비동기 또는 병렬 실행과 같은 문제 및 일반적으로 사용자 요청 또는 서비스 요청에 응답하도록 작업을 조정하면 애플리케이션 개발자가 핸들, 콜백, 동기화 등의 하위 수준 코딩으로 빠르게 돌아갈 수 있습니다. 개발자는 WPF(Windows Presentation Foundation)의 사용자 인터페이스와 마찬가지로 애플리케이션 내부를 위한 선언적 프로그래밍 모델과 동일한 능력과 유연성이 필요합니다. WF(Windows Workflow Foundation)는 애플리케이션 및 서비스 논리를 빌드하기 위한 선언적 프레임워크를 제공하고 개발자에게 비동기, 병렬 작업 및 기타 복잡한 처리를 처리하기 위한 더 높은 수준의 언어를 제공합니다.

메모리 및 개체를 관리하는 런타임이 있으면 코드 작성의 중요한 비즈니스 측면에 더 집중할 수 있습니다.  마찬가지로 비동기 작업 조정의 복잡성을 관리할 수 있는 런타임이 있으면 개발자 생산성을 향상시키는 일련의 기능이 제공됩니다.  WF는 워크플로(비즈니스 논리), 논리 및 제어 흐름을 정의하는 데 도움이 되는 활동 및 결과 애플리케이션 정의를 실행하기 위한 런타임을 선언하기 위한 도구 집합입니다.  요컨대, WF는 개발자의 생산성을 높이고, 애플리케이션을 더 쉽게 관리하고, 구현하기 위해 더 빠르게 변경할 수 있도록 하기 위해 애플리케이션을 작성하기 위해 더 높은 수준의 언어를 사용하는 것입니다.  WF 런타임은 워크플로를 실행할 뿐만 아니라 상태 지속성, 비즈니스 논리의 책갈피 및 재개와 같은 애플리케이션 논리를 작성할 때 중요한 서비스 및 기능을 제공하며, 이 모든 것이 스레드 및 프로세스 민첩성으로 이어져 비즈니스 프로세스를 확장하고 확장할 수 있습니다. 

WF를 사용하여 애플리케이션을 빌드하는 방법에 대한 자세한 개념 정보를 보려면 추가 리소스 섹션에 있는 David Chappell의 "워크플로 방법"을 읽어보는 것이 좋습니다. 

WF4의 새로운 기능

Microsoft® .NET Framework 버전 4에서 Windows Workflow Foundation은 .NET 3.0 및 3.5의 일부로 제공된 이전 버전의 기술에서 상당한 변화를 도입했습니다.  실제로 팀은 프로그래밍 모델, 런타임 및 도구의 핵심을 다시 검토하고 성능과 생산성을 높이고 이전 버전을 사용한 고객 참여에서 얻은 중요한 피드백을 해결하기 위해 각각을 다시 설계했습니다.  WF를 채택하는 개발자에게 최상의 환경을 제공하고 WF가 애플리케이션에서 빌드할 수 있는 강력한 기본 구성 요소가 될 수 있도록 하기 위해 중요한 변경이 필요했습니다. 여기에 높은 수준의 변화를 소개하고, 논문을 통해 각 주제는 심층 처리를 얻을 것이다. 

계속하기 전에 이전 버전과의 호환성도 이 릴리스의 주요 목표였다는 것을 이해하는 것이 중요합니다.  새 프레임워크 구성 요소는 주로 System.Activities.* 어셈블리에 있으며 이전 버전과 호환되는 프레임워크 구성 요소는 System.Workflow.* 어셈블리에 있습니다.  System.Workflow.* 어셈블리는 .NET Framework 4의 일부이며 완전한 이전 버전과의 호환성을 제공하므로 워크플로 코드를 변경하지 않고 애플리케이션을 .NET 4로 마이그레이션할 수 있습니다. 이 문서에서는 WF4라는 이름을 사용하여 System.Activities.* 어셈블리 및 WF3에 있는 새 구성 요소를 참조하여 System.Workflow.* 어셈블리에 있는 구성 요소를 참조합니다. 

디자이너

가장 눈에 띄는 개선 영역 중 하나는 워크플로 디자이너입니다. 유용성과 성능은 VS 2010 릴리스 팀의 주요 목표였습니다.  이제 디자이너는 성능 저하 없이 훨씬 더 큰 워크플로를 사용할 수 있는 기능을 지원하며, 디자이너는 모두 WPF(Windows Presentation Foundation)를 기반으로 하며, 선언적 UI 프레임워크를 사용하여 빌드할 수 있는 풍부한 사용자 환경을 최대한 활용합니다.  활동 개발자는 XAML을 사용하여 시각적 디자인 환경에서 활동을 보고 사용자와 상호 작용하는 방식을 정의합니다.  또한 개발자가 아닌 사용자가 워크플로를 보고 상호 작용할 수 있도록 자체 애플리케이션에서 워크플로 디자이너를 다시 호스트하는 것이 훨씬 쉬워집니다. 

데이터 흐름

WF3에서는 워크플로의 데이터 흐름이 불투명했습니다.  WF4는 인수 및 변수를 사용하는 데이터 흐름 및 범위 지정을 위한 명확하고 간결한 모델을 제공합니다.  모든 개발자에게 친숙한 이러한 개념은 데이터 스토리지의 정의와 워크플로 및 활동 내/외부 데이터의 흐름을 모두 간소화합니다.  또한 데이터 흐름 모델은 지정된 활동의 예상 입력 및 출력을 더 명확하게 하고 데이터가 더 쉽게 관리되므로 런타임의 성능을 향상시킵니다. 

순서도

개발자가 순서도 모델을 사용하여 워크플로를 정의할 수 있도록 순서도라는 새 제어 흐름 작업이 추가되었습니다.  순서도는 솔루션을 만들거나 비즈니스 프로세스를 디자인할 때 많은 분석가와 개발자가 겪는 개념 및 사고 프로세스와 더 유사합니다.  따라서 이미 수행된 개념적 사고와 계획을 쉽게 모델링할 수 있도록 활동을 제공하는 것이 합리적이었습니다.  순서도를 사용하면 이전 단계로 돌아가서 단일 조건에 따라 논리를 분할하거나 Switch/Case 논리를 분할하는 등의 개념을 사용할 수 있습니다. 

프로그래밍 모델

WF 프로그래밍 모델은 더 간단하고 강력하도록 개선되었습니다.  활동은 프로그래밍 모델의 핵심 기본 형식이며 워크플로와 활동을 모두 나타냅니다.  또한 워크플로를 호출하기 위해 WorkflowRuntime을 더 이상 만들 필요가 없으며, 인스턴스를 만들어 실행하여 특정 환경을 설정하는 문제를 겪고 싶지 않은 단위 테스트 및 애플리케이션 시나리오를 간소화할 수 있습니다.  마지막으로, 워크플로 프로그래밍 모델은 코드 옆에 없으므로 워크플로 작성을 간소화하는 완전히 선언적인 활동 컴퍼지션이 됩니다.

WCF(Windows Communication Foundation) 통합

WF의 이점은 서비스를 만들고 서비스 상호 작용을 사용하거나 조정하는 데 가장 확실하게 적용됩니다.  많은 노력이 WCF와 WF 간의 통합을 향상시키는 데 들어갔습니다.  새로운 메시징 활동, 메시지 상관 관계 및 향상된 호스팅 지원과 완전한 선언적 서비스 정의가 주요 개선 영역입니다. 

워크플로 시작

WF를 이해하는 가장 좋은 방법은 WF를 사용하고 개념을 적용하는 것입니다.  워크플로의 기초에 대한 몇 가지 핵심 개념을 다룬 다음, 몇 가지 간단한 워크플로를 만들어 이러한 개념이 서로 어떻게 관련되는지 설명합니다. 

워크플로 구조

활동은 WF의 구성 요소이며 모든 활동은 궁극적으로 활동에서 파생됩니다.  용어 참고 - 활동은 WF의 작업 단위입니다. 활동을 더 큰 활동으로 함께 구성할 수 있습니다. 활동이 최상위 진입점으로 사용되는 경우 Main이 CLR 프로그램에 대한 최상위 진입점을 나타내는 다른 함수인 것처럼 "워크플로"라고 합니다.   예를 들어 그림 1은 코드에서 빌드되는 간단한 워크플로를 보여줍니다. 

시퀀스 s = 새 시퀀스

{

    활동 = {

        new WriteLine {Text = "Hello"},

        new Sequence {

            활동 =

            {

                new WriteLine {Text = "Workflow"},

                new WriteLine {Text = "World"}

            }

        }

    }

};

그림 1: 간단한 워크플로

그림 1에서 시퀀스 작업은 워크플로의 루트 제어 흐름 스타일을 정의하는 루트 활동으로 사용됩니다. 모든 활동을 루트 또는 워크플로로 사용하고 간단한 WriteLine에서도 실행할 수 있습니다.   시퀀스에서 다른 활동의 컬렉션을 사용하여 Activities 속성을 설정하여 워크플로 구조를 정의했습니다.  또한 자식 활동에는 전체 워크플로 정의를 구성하는 활동 트리를 만드는 자식 활동이 있을 수 있습니다. 

워크플로 템플릿 및 워크플로 디자이너

WF4는 많은 활동과 함께 제공됩니다. Visual Studio 2010에는 활동을 정의하기 위한 템플릿이 포함되어 있습니다. 루트 제어 흐름에 사용되는 가장 일반적인 두 가지 활동은 시퀀스 및 순서도입니다.  모든 활동을 워크플로로 실행할 수 있지만, 이 두 가지는 비즈니스 논리를 정의하기 위한 가장 일반적인 디자인 패턴을 제공합니다.   그림 2는 수신, 저장 및 다른 서비스로 전송되는 알림의 처리를 정의하는 순차 워크플로 모델의 예를 보여 줍니다.   

 

그림 2: 순차 워크플로 디자인

순서도 워크플로 유형은 워크플로의 이전 단계로 돌아갈 수 있고 비즈니스 논리를 정의하는 분석가 및 개발자가 수행한 개념적 디자인과 더 유사하기 때문에 기존 사용자의 일반적인 요청을 처리하기 위해 WF4에 도입되었습니다.  예를 들어 애플리케이션에 대한 사용자 입력과 관련된 시나리오를 고려해 보세요.  사용자가 제공한 데이터에 대한 응답으로 프로그램은 프로세스에서 계속 진행하거나 이전 단계로 돌아가 입력을 다시 요청해야 합니다. 순차 워크플로를 사용하면 일부 조건이 충족될 때까지 DoWhile 활동을 사용하여 처리를 계속하는 그림 3에 표시된 것과 비슷한 내용이 포함됩니다. 

그림 3: 의사 결정 분기 대한 순차적

그림 3의 디자인은 작동하지만 워크플로를 보는 개발자 또는 분석가로서 모델은 설명된 원래 논리 또는 요구 사항을 나타내지 않습니다.   그림 4의 순서도 워크플로는 그림 3에 사용된 순차적 모델과 유사한 기술적 결과를 제공하지만 워크플로 디자인은 원래 정의된 사고 및 요구 사항과 더 밀접하게 일치합니다. 

그림 4: 순서도 워크플로

워크플로의 데이터 흐름

워크플로에 대해 생각할 때 대부분의 사람들이 가장 먼저 생각하는 것은 비즈니스 프로세스 또는 애플리케이션의 흐름입니다.  그러나 흐름만큼이나 중요한 것은 프로세스를 구동하는 데이터와 워크플로를 실행하는 동안 수집 및 저장되는 정보입니다.  WF4에서는 데이터의 저장 및 관리가 디자인 고려의 주요 영역이었습니다. 

데이터와 관련하여 이해해야 할 세 가지 주요 개념인 변수, 인수 및 식이 있습니다.  각각에 대한 간단한 정의는 변수가 데이터를 저장하기 위한 것이고, 인수는 데이터를 전달하며, 식은 데이터를 조작하기 위한 것입니다. 

변수 - 데이터 저장

워크플로의 변수는 명령적 언어에서 사용하는 변수와 매우 유사합니다. 즉, 데이터를 저장할 명명된 위치를 설명하고 특정 범위 지정 규칙을 따릅니다.  변수를 만들려면 먼저 변수를 사용할 수 있어야 하는 범위를 결정합니다.  클래스 또는 메서드 수준에서 사용할 수 있는 코드에 변수가 있는 것처럼 워크플로 변수는 다른 범위에서 정의할 수 있습니다.  그림 5의 워크플로를 고려합니다.  이 예제에서는 워크플로의 루트 수준 또는 피드 수집 데이터 시퀀스 작업으로 정의된 범위에서 변수를 정의할 수 있습니다. 

그림 5: 활동 범위로 범위가 지정된 변수

인수 – 데이터 전달

인수는 활동에 정의되며 활동 내/외부 데이터 흐름을 정의합니다.  명령적 코드의 메서드에 인수를 사용하는 것처럼 활동에 대한 인수를 생각할 수 있습니다.  인수는 In, Out 또는 In/Out일 수 있으며 이름과 형식을 가질 수 있습니다.  워크플로를 빌드할 때 루트 작업에서 인수를 정의하여 데이터를 호출할 때 워크플로에 전달할 수 있도록 할 수 있습니다.  인수 창을 사용하여 변수를 수행하는 것과 마찬가지로 워크플로에서 인수를 정의합니다. 

워크플로에 활동을 추가할 때 활동에 대한 인수를 구성해야 하며, 이는 주로 범위 내 변수를 참조하거나 식을 사용하여 수행되며, 다음에 설명하겠습니다.  실제로 Argument 기본 클래스에는 인수 형식의 값을 반환하는 작업인 Expression 속성이 포함되어 있으므로 이러한 모든 옵션은 관련되어 있고 활동에 의존합니다. 

워크플로 디자이너를 사용하여 인수를 편집하는 경우 리터럴 값을 나타내는 식을 속성 표에 입력하거나 변수 이름을 사용하여 그림 6에 표시된 대로 범위 내 변수를 참조할 수 있습니다. 여기서 emailResult 및 emailAddress는 워크플로에 정의된 변수입니다. 

그림 6: 활동에 대한 인수 구성

식 - 데이터에 대한 작업

식은 워크플로에서 데이터에서 작동하는 데 사용할 수 있는 활동입니다.  활동을 사용하고 반환 값에 관심이 있는 위치에서 식을 사용할 수 있습니다. 즉, 인수를 설정하거나 While 또는 If 활동과 같은 활동에 대한 조건을 정의할 수 있습니다.  WF4의 대부분의 항목은 활동에서 파생되고 식은 다르지 않으며, 활동<TResult> 특정 형식의 값을 반환한다는 의미의 파생 항목입니다.  또한 WF4에는 Visual Basic 식뿐만 아니라 변수 및 인수를 참조하기 위한 몇 가지 일반적인 식이 포함되어 있습니다.  특수화된 식 계층으로 인해 인수를 정의하는 데 사용하는 식에는 코드, 리터럴 값 및 변수 참조가 포함될 수 있습니다.  표 1에서는 워크플로 디자이너를 사용하여 인수를 정의할 때 사용할 수 있는 식 형식의 작은 샘플링을 제공합니다.  코드에서 식을 만들 때 람다 식 사용을 비롯한 몇 가지 옵션이 더 있습니다. 

식 형식

"hello world"

리터럴 문자열 값

10

리터럴 Int32 값

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

명령적 메서드 호출

"hello " & "world"

Visual Basic 식

argInputString

인수 참조(인수 이름)

varResult

변수 참조(변수 이름)

"hello: " & argInputString

리터럴 및 인수/변수 혼합

표 1: 예제 식

첫 번째 워크플로 빌드

이제 활동 및 데이터 흐름에 대한 핵심 개념을 다루었으므로 이러한 개념을 사용하여 워크플로를 만들 수 있습니다.  WF의 진정한 가치 제안보다는 개념에 초점을 맞추는 간단한 hello world 워크플로로 시작하겠습니다.  시작하려면 Visual Studio 2010에서 새 단위 테스트 프로젝트를 만듭니다.  WF의 핵심을 사용하려면 System.Activities 어셈블리에 대한 참조를 추가하고 테스트 클래스 파일에 System.Activities, System.Activities.Statements 및 System.IO 대한 using 문을 추가합니다.  그런 다음 그림 7과 같이 테스트 메서드를 추가하여 기본 워크플로를 만들고 실행합니다. 

[TestMethod]

public void TestHelloWorldStatic()

{

    StringWriter 기록기 = new StringWriter();

    Console.SetOut(기록기);

    시퀀스 wf = 새 시퀀스

    {

        활동 = {

            new WriteLine {Text = "Hello"},

            new WriteLine {Text = "World"}

        }

    };

    WorkflowInvoker.Invoke(wf);

    Assert.IsTrue(String.Compare(

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

        작가. GetStringBuilder(). ToString()) == 0,

        "잘못된 문자열 작성");

}

그림 7: 코드 hello world 만들기

WriteLine 작업의 Text 속성은 inArgument<문자열> 이 예제에서는 해당 속성에 리터럴 값을 전달했습니다.

다음 단계는 변수를 사용하고 해당 변수를 활동 인수에 전달하도록 이 워크플로를 업데이트하는 것입니다.  그림 8에서는 변수를 사용하도록 업데이트된 새 테스트를 보여 줍니다.

[TestMethod]

public void TestHelloWorldVariables()

{

    StringWriter 기록기 = new StringWriter();

    Console.SetOut(기록기);

시퀀스 wf = 새 시퀀스

{

    변수 = {

        새 변수<문자열>{Default = "Hello", Name = "greeting"},

        새 변수<문자열> { Default = "Bill", Name = "name" } },

        활동 = {

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

            new WriteLine { Text = new VisualBasicValue<string>(

            "name + \"Gates\"")}

        }

    };

    WorkflowInvoker.Invoke(wf);

}

그림 8: 변수가 있는 코드 워크플로

이 경우 변수는 변수<문자열> 형식으로 정의되고 기본값이 지정됩니다.  변수는 시퀀스 작업 내에서 선언된 다음 두 활동의 Text 인수에서 참조됩니다.  또는 식을 사용하여 VisualBasicValue<TResult> 클래스가 식을 나타내는 문자열을 전달하는 데 사용되는 그림 9에 표시된 대로 변수를 초기화할 수 있습니다.  첫 번째 경우 식은 변수의 이름을 참조하고, 두 번째 경우에는 변수가 리터럴 값과 연결됩니다.  텍스트 식에 사용되는 구문은 C#에서 코드를 작성하는 경우에도 Visual Basic입니다.

활동 = {

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

        TextWriter = 기록기 },

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

        TextWriter = 기록기 }

}

그림 9: 식을 사용하여 인수 정의

코드 예제는 중요한 사항을 설명하고 일반적으로 개발자가 편안하게 느끼는 데 도움이 되지만 대부분의 사람들은 디자이너를 사용하여 워크플로를 만듭니다.  디자이너에서 활동을 디자인 화면으로 끌어와 업데이트되었지만 친숙한 속성 그리드를 사용하여 인수를 설정합니다.  또한 워크플로 디자이너는 아래쪽에 확장 가능한 영역을 포함하여 워크플로 및 변수에 대한 인수를 편집합니다.   디자이너에서 비슷한 워크플로를 만들려면 솔루션에 새 프로젝트를 추가하고 활동 라이브러리 템플릿을 선택한 다음 새 활동을 추가합니다. 

워크플로 디자이너가 처음 표시되면 활동이 포함되지 않습니다.  활동을 정의하는 첫 번째 단계는 루트 활동을 선택하는 것입니다.  이 예제에서는 도구 상자의 순서도 범주에서 끌어서 디자이너에 순서도 작업을 추가합니다.  순서도 디자이너에서 도구 상자에서 두 개의 WriteLine 활동을 끌어서 한 작업을 순서도에 추가합니다.  이제 순서도에서 따라야 할 경로를 알 수 있도록 활동을 함께 연결해야 합니다.  먼저 순서도 맨 위에 있는 녹색 "시작" 원 위로 마우스를 가져가서 잡기 핸들을 표시한 다음, 첫 번째 WriteLine으로 끌어서 작업 맨 위에 나타나는 끌기 핸들에 놓습니다.  첫 번째 WriteLine을 두 번째 WriteLine에 연결하려면 동일한 작업을 수행합니다.  디자인 화면은 그림 10과 같습니다. 

그림 10: Hello 순서도 레이아웃

구조가 배치되면 일부 변수를 구성해야 합니다.  디자인 화면을 클릭한 다음 디자이너 아래쪽에 있는 변수 단추를 클릭하면 순서도에 대한 변수 컬렉션을 편집할 수 있습니다.  "greeting" 및 "name"이라는 두 개의 변수를 나열하고 기본값을 각각 "Hello" 및 "Bill"로 설정합니다. 값을 식으로 설정할 때 따옴표를 포함해야 하므로 리터럴 문자열을 따옴표로 묶어야 합니다.  그림 11에서는 두 개의 변수와 해당 기본값으로 구성된 변수 창을 보여 줍니다. 

그림 11: 워크플로 디자이너에 정의된 변수

WriteLine 작업에서 이러한 변수를 사용하려면 첫 번째 작업의 Text 인수에 대한 속성 표에 "greeting"(따옴표 없이)을 입력하고 두 번째 WriteLine의 Text 인수에 "name"(다시 따옴표 없이)을 입력합니다.   런타임에 Text 인수가 평가되면 변수의 값이 확인되고 WriteLine 작업에서 사용됩니다.

테스트 프로젝트에서 워크플로가 포함된 프로젝트에 대한 참조를 추가합니다.  그런 다음 그림 12에 표시된 것과 같은 테스트 메서드를 추가하여 이 워크플로를 호출하고 출력을 테스트할 수 있습니다.  그림 12에서는 만든 클래스의 인스턴스를 인스턴스화하여 워크플로가 생성되고 있음을 확인할 수 있습니다.  이 경우 워크플로는 디자이너에서 정의되고 작업에서 파생된 클래스로 컴파일되어 호출될 수 있습니다. 

[TestMethod]

public void TestHelloFlowChart()

{

    StringWriter tWriter = new StringWriter();

    Console.SetOut(tWriter);

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

    WorkflowInvoker.Invoke(wf);

    생략된 어설션

}

그림 12: 순서도 테스트

지금까지 워크플로에서 변수를 사용하고 이를 사용하여 WriteLine 작업의 인수에 값을 제공했습니다.  워크플로를 호출할 때 워크플로에 데이터를 전달하고 워크플로가 완료될 때 출력을 받을 수 있도록 정의된 인수를 포함할 수도 있습니다.  이전 예제에서 순서도를 업데이트하려면 "name" 변수를 제거하고(해당 변수를 선택하고 Delete 키를 누릅니다) 대신 문자열 형식의 "name" 인수를 만듭니다.  인수 단추를 사용하여 인수 편집기를 보는 것을 제외하고는 거의 동일한 방식으로 이 작업을 수행합니다.  인수에도 방향이 있을 수 있으며 런타임 시 값이 작업으로 전달되므로 기본값을 제공할 필요가 없습니다.  변수에 대해 했던 것과 동일한 인수 이름을 사용하므로 WriteLine 작업 텍스트 인수는 유효한 상태로 유지됩니다.  이제 런타임에 해당 인수는 워크플로에서 "name" 인수의 값을 평가하고 확인하고 해당 값을 사용합니다.  Out 방향과 이름이 "fullGreeting"인 문자열 형식의 추가 인수를 추가합니다. 호출 코드로 반환됩니다. 

그림 13: 워크플로 대한 인수 정의

순서도에서 할당 작업을 추가하고 마지막 WriteLine 작업에 연결합니다.  To 인수에 "fullGreeting"(따옴표 없음)을 입력하고 Value 인수에 "greeting & name"(따옴표 없음)을 입력합니다. 그러면 greeting 변수와 name 인수의 연결이 fullGreeting 출력 인수에 할당됩니다. 

이제 단위 테스트에서 워크플로를 호출할 때 인수를 제공하도록 코드를 업데이트합니다.  인수는 사전<문자열, 개체> 워크플로에 전달됩니다. 여기서 키는 인수의 이름입니다.  그림 14와 같이 워크플로를 호출하는 호출을 변경하여 이 작업을 수행할 수 있습니다. 출력 인수는 인수 이름에 키가 지정된 Dictionary<문자열, 개체> 컬렉션에도 포함됩니다. 

IDictionary<문자열, 개체> 결과 = WorkflowInvoker.Invoke(wf,

    새 사전<문자열, 개체> {

        {"name", "Bill" } }

    );

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

그림 14: 워크플로에 인수 전달

활동, 변수 및 인수를 통합하는 방법의 기본 사항을 확인했으므로 낮은 수준의 개념 대신 비즈니스 논리에 초점을 맞춘 더 흥미로운 워크플로를 사용할 수 있도록 프레임워크에 포함된 활동을 둘러보겠습니다.

워크플로 활동 팔레트 둘러보기

모든 프로그래밍 언어를 사용하면 애플리케이션 논리를 정의하기 위한 핵심 구문이 있어야 합니다.  WF와 같은 더 높은 수준의 개발 프레임워크를 사용하는 경우 이러한 기대는 변경되지 않습니다.   If/Else, Switch 및 While와 같은 .NET 언어의 문이 있는 것처럼 제어 흐름을 관리하려면 선언적 워크플로에서 논리를 정의할 때도 동일한 기능이 필요합니다.  이러한 기능은 프레임워크와 함께 제공되는 기본 활동 라이브러리의 형태로 제공됩니다.  이 섹션에서는 프레임워크와 함께 제공되는 활동에 대한 빠른 둘러보기를 제공하여 기본 제공 기능에 대한 아이디어를 제공합니다.

활동 기본 형식 및 컬렉션 활동

선언적 프로그래밍 모델로 이동하는 경우 코드를 작성할 때 두 번째 특성인 일반적인 개체 조작 작업을 수행하는 방법을 쉽게 궁금해할 수 있습니다.  개체를 사용하여 작업하고 속성을 설정하거나, 명령을 호출하거나, 항목 컬렉션을 관리해야 하는 작업의 경우 이러한 작업을 염두에 두고 특별히 설계된 활동 집합이 있습니다. 

활동 묘사

할당하다

위치에 값을 할당하여 변수를 설정할 수 있습니다.

지연

지정된 시간 동안 실행 경로를 지연합니다.

InvokeMethod

필요에 따라 T의 반환 형식을 사용하여 .NET 개체 또는 .NET 형식의 정적 메서드에서 메서드를 호출합니다.

WriteLine

지정된 텍스트를 텍스트 작성기에 씁니다. 기본값은 Console.Out입니다.

AddToCollection<T>

형식화된 컬렉션에 항목을 추가합니다.

RemoveFromCollection<T>

형식화된 컬렉션에서 항목을 제거합니다.

ClearCollection<T>

컬렉션에서 모든 항목을 제거합니다.

ExistsInCollection<T>

지정된 항목이 컬렉션에 있는지 여부를 나타내는 부울 값을 반환합니다. 

 

흐름 작업 제어

비즈니스 논리 또는 비즈니스 프로세스를 정의할 때 실행 흐름을 제어하는 것이 중요합니다. 제어 흐름 작업에는 순서대로 단계를 실행해야 할 때 공통 컨테이너를 제공하는 Sequence와 If 및 Switch 작업과 같은 일반적인 분기 논리와 같은 기본 사항이 포함됩니다.  제어 흐름 작업에는 데이터(ForEach) 및 조건(While)을 기반으로 하는 루핑 논리도 포함됩니다.  복잡한 프로그래밍을 간소화하는 데 가장 중요한 것은 병렬 작업이며, 동시에 여러 비동기 작업을 수행할 수 있습니다. 

활동 묘사

순서

계열에서 작업 실행

While/DoWhile

조건(식)이 true인 동안 자식 작업을 실행합니다.

ForEach<T>

열거 가능한 컬렉션을 반복하고 컬렉션의 각 항목에 대해 자식 작업을 한 번 실행하여 다음 반복을 시작하기 전에 자식이 완료되기를 기다립니다. 명명된 인수의 형태로 반복을 구동하는 개별 항목에 대한 형식화된 액세스를 제공합니다.

조건(식)의 결과에 따라 두 자식 작업 중 하나를 실행합니다.

<T> 전환

식을 평가하고 일치하는 키를 사용하여 자식 작업을 예약합니다. 

평행의

모든 자식 활동을 한 번에 예약하지만 특정 조건이 충족되는 경우 활동이 미해결 자식 활동을 취소할 수 있도록 하는 완료 조건도 제공합니다. 

ParallelForEach<T>

열거 가능한 컬렉션을 반복하고 컬렉션의 각 항목에 대해 자식 작업을 한 번 실행하여 모든 인스턴스를 동시에 예약합니다. ForEach<T>마찬가지로 이 작업은 명명된 인수의 형태로 현재 데이터 항목에 대한 액세스를 제공합니다.

고르다

모든 자식 PickBranch 활동을 예약하고 첫 번째 트리거를 제외한 모든 작업을 취소합니다.  PickBranch 작업에는 트리거와 작업이 모두 있습니다. 각각은 활동입니다.  트리거 작업이 완료되면 Pick은 다른 모든 자식 활동을 취소합니다. 

 아래의 두 예제는 이러한 활동을 함께 구성하는 방법을 설명하기 위해 사용 중인 이러한 활동 중 몇 가지를 보여 줍니다.  첫 번째 예제인 그림 15에는 ParallelForEach<T> 사용하여 URL 목록을 사용하고 지정된 주소에서 RSS 피드를 비동기적으로 가져오는 것이 포함됩니다.  피드가 반환되면 ForEach<T> 사용하여 피드 항목을 반복하고 처리합니다. 

코드는 System.ServiceModel.Syndication 형식에 대한 참조를 사용하여 VisualBasicSettings 인스턴스를 선언하고 정의합니다.  그런 다음 이 설정 개체는 해당 네임스페이스의 변수 형식을 참조하는 VisualBasicValue<T> 인스턴스를 선언하여 해당 식에 대한 형식 확인을 사용하도록 설정할 때 사용됩니다.  또한 ParallelForEach 작업의 본문은 사용자 지정 활동을 만드는 섹션에서 언급한 ActivityAction을 사용합니다.  이러한 작업은 활동에서 InArgument 및 OutArgument를 사용하는 것과 거의 동일한 방식으로 DelegateInArgument 및 DelegateOutArgument를 사용합니다. 

그림 15: 흐름 작업 제어

두 번째 예제인 그림 16은 시간 제한이 있는 응답을 기다리는 일반적인 패턴입니다.  예를 들어 관리자가 요청을 승인할 때까지 대기하고 응답이 할당된 시간에 도착하지 않은 경우 미리 알림을 보냅니다.  DoWhile 활동은 응답을 기다리는 반복을 발생시키고, 선택 작업은 트리거와 동시에 ManagerResponse 작업과 지연 작업을 모두 실행하는 데 사용됩니다.  지연이 먼저 완료되면 미리 알림이 전송되고 ManagerResponse 작업이 먼저 완료되면 플래그가 DoWhile 루프에서 중단되도록 설정됩니다. 

그림 16: 선택 및 DoWhile 활동

그림 16의 예제에서는 워크플로에서 책갈피를 사용하는 방법도 보여 줍니다.  책갈피는 워크플로의 위치를 표시하는 활동에 의해 만들어지므로 나중에 해당 지점에서 처리를 다시 시작할 수 있습니다.  지연 활동에는 특정 시간이 경과한 후 다시 시작되는 책갈피가 있습니다.  ManagerResponse 작업은 입력을 대기하고 데이터가 도착하면 워크플로를 다시 시작하는 사용자 지정 작업입니다.  곧 설명된 메시징 활동은 책갈피 실행을 위한 주요 활동입니다.  워크플로가 작업을 적극적으로 처리하지 않는 경우 책갈피가 다시 시작될 때까지 대기하는 경우 유휴 상태로 간주되며 지속형 저장소에 유지할 수 있습니다.  책갈피는 사용자 지정 활동 만들기에 대한 섹션에서 자세히 설명합니다. 

이주

WF3을 사용하는 개발자의 경우 Interop 작업은 기존 자산을 다시 사용하는 데 중요한 역할을 할 수 있습니다. System.Workflow.Runtime 어셈블리에 있는 작업은 기존 작업 형식을 래핑하고 활동의 속성을 WF4 모델의 인수로 표시합니다.  속성은 인수이므로 식을 사용하여 값을 정의할 수 있습니다.  그림 17은 WF3 활동을 호출하는 Interop 작업의 구성을 보여줍니다.  입력 인수는 워크플로 정의 내에서 범위 내 변수에 대한 참조를 사용하여 정의됩니다.    WF3에서 빌드된 활동에는 인수 대신 속성이 있으므로 각 속성에 해당 입력 및 출력 인수가 제공되므로 활동으로 보내는 데이터와 활동이 실행된 후 검색할 것으로 예상되는 데이터를 구분할 수 있습니다. 새 WF4 프로젝트에서는 대상 프레임워크가 .NET Framework 4 클라이언트 프로필로 설정되어 있으므로 도구 상자에서 이 작업을 찾을 수 없습니다.  프로젝트 속성의 대상 프레임워크를 .NET Framework 4로 변경하면 작업이 도구 상자에 표시됩니다.

그림 17: Interop 작업 구성

순서도

순서도 워크플로를 디자인할 때 순서도 내에서 실행 흐름을 관리하는 데 사용할 수 있는 몇 가지 구문이 있습니다.  이러한 구문 자체는 간단한 단계, 단일 조건에 따른 간단한 의사 결정 지점 또는 switch 문을 제공합니다.  순서도의 진정한 힘은 이러한 노드 형식을 원하는 흐름에 연결하는 기능입니다.

생성/작업 묘사

순서도

일련의 흐름 단계에 대한 컨테이너, 각 흐름 단계는 모든 작업 또는 다음 구문 중 하나일 수 있지만 실행하려면 순서도 내에서 연결해야 합니다. 

FlowDecision

조건에 따라 분기 논리를 제공합니다.

FlowSwitch<T>

식 값에 따라 여러 분기를 사용하도록 설정합니다. 

FlowStep

다른 단계에 연결할 수 있는 순서도의 단계를 나타냅니다. 이 형식은 디자이너에서 암시적으로 추가되므로 도구 상자에 표시되지 않습니다.  이 활동은 워크플로의 다른 활동을 래핑하고 워크플로의 다음 단계에 대한 탐색 의미 체계를 제공합니다. 

순서도 모델에 대한 특정 활동이 있지만 워크플로 내에서 다른 모든 활동을 사용할 수 있다는 점에 유의해야 합니다.  마찬가지로 순서도 작업을 다른 활동에 추가하여 해당 워크플로 내에서 순서도의 실행 및 디자인 의미 체계를 제공할 수 있습니다.  즉, 여러 작업이 있는 시퀀스와 중간에 순서도를 사용할 수 있습니다. 

메시징 활동

WF4의 주요 중 하나는 WF와 WCF 간의 긴밀한 통합입니다.  워크플로 측면에서는 메시지 보내기 및 받기와 같은 메시징 작업을 모델링하는 활동을 의미합니다.  실제로 메시징을 위한 프레임워크에는 각각 약간 다른 기능과 목적이 있는 여러 가지 활동이 포함되어 있습니다. 

활동 묘사

보내기/받기

메시지를 보내거나 받는 단방향 메시징 활동입니다.  이러한 동일한 활동은 요청/응답 스타일 상호 작용으로 구성됩니다. 

ReceiveAndSendReply

메시지를 받고 회신을 다시 보내는 서비스 작업을 모델화합니다. 

SendAndReceiveReply

서비스 작업을 호출하고 응답을 받습니다. 

InitializeCorrelation

메시지에서 값을 추출하는 대신 워크플로 논리의 일부로 명시적으로 상관 관계 값을 초기화할 수 있습니다. 

CorrelationScope

여러 메시징 활동에서 공유하는 핸들의 구성을 간소화하는 작업을 수신하고 보낼 수 있는 상관 관계 핸들에 액세스할 수 있는 실행 범위를 정의합니다. 

TransactedReceiveScope

수신 작업을 사용하여 WCF 작업으로 흐르는 동일한 트랜잭션에 워크플로 논리를 포함할 수 있도록 합니다.

워크플로 내에서 서비스 작업을 호출하려면 워크플로 프로젝트에 서비스 참조를 추가하는 친숙한 단계를 따릅니다.  그러면 Visual Studio의 프로젝트 시스템은 서비스에서 메타데이터를 사용하고 계약에 있는 각 서비스 작업에 대한 사용자 지정 작업을 만듭니다.  이를 C# 또는 Visual Basic 프로젝트에서 만들 WCF 프록시에 해당하는 워크플로라고 생각할 수 있습니다.   예를 들어 그림 18에 표시된 서비스 계약을 사용하여 호텔 예약을 검색하는 기존 서비스를 사용합니다. 서비스 참조를 추가하면 그림 19에 표시된 사용자 지정 작업이 생성됩니다. 

[ServiceContract]

공용 인터페이스 IHotelService

{

    [OperationContract]

    List<HotelSearchResult> SearchHotels(

        HotelSearchRequest requestDetails);

}

그림 18: 서비스 계약

그림 19: 사용자 지정 WCF 활동

WCF 서비스로 노출되는 워크플로 빌드에 대한 자세한 내용은 이 문서의 뒷부분에 있는 Workflow Services 섹션에서 확인할 수 있습니다. 

트랜잭션 및 오류 처리

신뢰할 수 있는 시스템을 작성하는 것은 어려울 수 있으며, 애플리케이션에서 일관된 상태를 유지하기 위해 오류를 처리하고 관리하는 작업을 잘 수행해야 합니다.  워크플로의 경우 예외 처리 및 트랜잭션을 관리하기 위한 범위는 ActivityAction 또는 Activity 형식의 속성이 있는 활동을 사용하여 모델링되어 개발자가 오류 처리 논리를 모델링할 수 있도록 합니다.  이러한 일반적인 일관성 패턴 외에도 WF4에는 보상 및 확인을 통해 장기 실행 분산 조정을 모델링할 수 있는 활동도 포함되어 있습니다. 

활동 묘사

CancellationScope

작업 본문이 취소될 경우 워크플로 개발자가 반응할 수 있도록 하는 데 사용됩니다. 

TransactionScope

트랜잭션에서 범위의 모든 자식 작업을 실행하여 코드에서 트랜잭션 범위를 사용하는 것과 유사한 의미 체계를 사용하도록 설정합니다. 

TryCatch/catch<T>

예외 처리를 모델링하고 형식화된 예외를 catch하는 데 사용됩니다. 

던지다

활동에서 예외를 throw하는 데 사용할 수 있습니다.  

다시 throw

일반적으로 TryCatch 작업을 사용하여 catch된 예외를 다시 throw하는 데 사용됩니다. 

CompensableActivity

성공 후 해당 작업을 보정해야 할 수 있는 자식 활동을 실행하기 위한 논리를 정의합니다.  보정 논리, 확인 논리 및 취소 처리에 대한 자리 표시자를 제공합니다.

물다

보정 가능한 작업에 대한 보정 처리 논리를 호출합니다.

확인하다

보상 가능한 활동에 대한 확인 논리를 호출합니다. 

TryCatch 작업은 발생할 수 있는 모든 예외를 catch하기 위해 작업 집합의 범위를 지정하는 친숙한 방법을 제공하며 Catch<T> 작업은 예외 처리 논리에 대한 컨테이너를 제공합니다.  이 작업이 그림 20에서 사용되는 방법의 예를 볼 수 있습니다. 각 형식화된 예외 블록은 Catch<T> 작업으로 정의되어 각 catch의 왼쪽에 있는 명명된 인수를 통해 예외에 대한 액세스를 제공합니다.   필요한 경우 Rethrow 작업을 사용하여 catch된 예외를 다시 throw하거나 Throw 작업을 사용하여 새 예외를 throw할 수 있습니다. 

그림 20: TryCatch 작업

트랜잭션은 수명이 짧은 작업의 일관성을 보장하는 데 도움이 되며 TransactionScope 작업은 TransactionScope 클래스를 사용하여 .NET 코드에서 가져올 수 있는 것과 동일한 종류의 범위 지정을 제공합니다. 

마지막으로 일관성을 유지해야 하지만 리소스 전체에서 원자성 트랜잭션을 사용할 수 없는 경우 보정을 사용할 수 있습니다.  보정을 사용하면 작업 집합을 정의할 수 있으며, 작업이 완료되면 변경 내용을 보정하기 위한 활동 집합을 가질 수 있습니다.  간단한 예로, 전자 메일이 전송되는 그림 21의 보상 가능 활동을 고려해 보세요.  작업이 완료된 후 예외가 발생하면 보정 논리를 호출하여 시스템을 일관된 상태로 되돌릴 수 있습니다. 이 경우 이전 메시지를 취소하는 후속 전자 메일이 있습니다. 

그림 21: 보상 가능한 활동

워크플로 만들기 및 실행

프로그래밍 언어와 마찬가지로 워크플로를 정의하고 실행하는 두 가지 기본 작업이 있습니다.  두 경우 모두 WF는 유연성과 제어를 제공하는 몇 가지 옵션을 제공합니다. 

워크플로 디자인 옵션

워크플로를 디자인하거나 정의할 때 코드 또는 XAML의 두 가지 주요 옵션이 있습니다. XAML은 진정한 선언적 환경을 제공하며, .NET을 사용하여 빌드된 활동 및 형식을 참조하여 워크플로의 전체 정의를 XML 태그에 정의할 수 있도록 합니다.  대부분의 개발자는 워크플로 디자이너를 사용하여 선언적 XAML 워크플로 정의를 생성하는 워크플로를 빌드할 가능성이 높습니다.  그러나 XAML은 XML일 뿐이므로 모든 도구를 사용하여 만들 수 있습니다. 이는 애플리케이션을 빌드하기 위한 강력한 모델인 이유 중 하나입니다.  예를 들어 그림 22에 표시된 XAML은 간단한 텍스트 편집기에서 만들어졌으며 컴파일하거나 직접 사용하여 정의되는 워크플로의 인스턴스를 실행할 수 있습니다. 

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

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

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

  </x:Members>

  <p:Sequence>

    <p:WriteLine>[greeting & "워크플로에서"]</p:WriteLine>

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

  </p:Sequence>

</p:Activity>

그림 22: XAML 정의된 워크플로

이 동일한 XAML은 디자이너에 의해 생성되며 사용자 지정 도구로 생성될 수 있으므로 관리하기가 훨씬 쉽습니다.  또한 앞에서 설명한 것처럼 코드를 사용하여 워크플로를 빌드할 수도 있습니다.  여기에는 프레임워크의 다양한 활동 및 사용자 지정 활동을 사용하여 활동 계층 구조를 만드는 작업이 포함됩니다.  코드에 완전히 정의된 워크플로의 몇 가지 예가 이미 있습니다.  이제 WF3과 달리 코드에서 워크플로를 만들고 XAML로 쉽게 직렬화할 수 있습니다. 워크플로 정의를 모델링하고 관리하는 데 더 많은 유연성을 제공합니다. 

보여 주듯이 워크플로를 실행하기 위해 필요한 것은 작업뿐이며 코드에서 빌드된 인스턴스 또는 XAML에서 만든 인스턴스일 수 있습니다.  각 모델링 기술의 최종 결과는 활동에서 파생되는 클래스 또는 역직렬화되거나 활동으로 컴파일될 수 있는 XML 표현입니다. 

워크플로를 실행하기 위한 옵션

워크플로를 실행하려면 워크플로를 정의하는 작업이 필요합니다.  실행할 수 있는 작업을 가져오는 일반적인 방법에는 코드로 만들거나 XAML 파일에서 읽은 다음 콘텐츠를 활동으로 역직렬화하는 두 가지 방법이 있습니다.  첫 번째 옵션은 간단하며 이미 몇 가지 예를 보여 줍니다.  XAML 파일을 로드하려면 정적 Load 메서드를 제공하는 ActivityXamlServices 클래스를 사용해야 합니다.  Stream 또는 XamlReader 개체를 전달하기만 하면 XAML에 표시된 활동을 다시 가져옵니다.  

작업이 있으면 작업을 실행하는 가장 간단한 방법은 앞서 단위 테스트에서 했던 것처럼 WorkflowInvoker 클래스를 사용하는 것입니다.  이 클래스의 Invoke 메서드에는 Activity 형식의 매개 변수가 있으며 IDictionary<문자열, 개체>반환합니다.  워크플로에 인수를 전달해야 하는 경우 먼저 워크플로에서 인수를 정의한 다음 작업과 함께 값을 Invoke 메서드에 이름/값 쌍의 사전으로 전달합니다.  마찬가지로 워크플로에 정의된 Out 또는 In/Out 인수는 메서드를 실행한 결과로 반환됩니다.  그림 23에서는 XAML 파일에서 워크플로를 로드하고, 인수를 워크플로에 전달하고, 결과 출력 인수를 검색하는 예제를 제공합니다. 

활동 mathWF;

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

{

    mathWF = ActivityXamlServices.Load(mathXaml);

}

var outputs = WorkflowInvoker.Invoke(mathWF,

    새 사전<문자열, 개체> {

    { "operand1", 5 },

    { "operand2", 10 },

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

Assert.AreEqual<int>(15, (int)outputs["result"], "잘못된 결과 반환");

그림 23: 인아웃 인수를 사용하여 "느슨한 XAML" 워크플로 호출

이 예제에서는 활동이 XAML 파일에서 로드되며 인수를 수락하고 반환할 수 있습니다.  워크플로는 쉽게 Visual Studio에서 디자이너를 사용하여 개발되었지만 사용자 지정 디자이너에서 개발되어 어디에나 저장될 수 있습니다.  실행을 위해 런타임에 전달되기 전에 데이터베이스, SharePoint 라이브러리 또는 다른 저장소에서 XAML을 로드할 수 있습니다.  

WorkflowInvoker를 사용하면 수명이 짧은 워크플로를 실행하는 가장 간단한 메커니즘을 제공합니다.  기본적으로 워크플로는 애플리케이션에서 메서드 호출처럼 작동하므로 WF 자체를 호스트하기 위해 많은 작업을 수행하지 않고도 WF의 모든 이점을 더 쉽게 활용할 수 있습니다.  이는 테스트 중인 구성 요소를 연습하는 데 필요한 테스트 설정을 줄여 활동 및 워크플로를 단위 테스트할 때 특히 유용합니다. 

또 다른 일반적인 호스팅 클래스는 런타임에서 실행되는 워크플로에 안전한 핸들을 제공하고 장기 실행 워크플로를 보다 쉽게 관리할 수 있게 해주는 WorkflowApplication입니다.  WorkflowApplication을 사용하면 WorkflowInvoker와 동일한 방식으로 워크플로에 인수를 전달할 수 있지만 Run 메서드를 사용하여 실제로 워크플로 실행을 시작할 수 있습니다.  이 시점에서 워크플로는 다른 스레드에서 실행되기 시작하고 컨트롤은 호출 코드로 돌아갑니다. 

이제 워크플로가 비동기적으로 실행되므로 호스팅 코드에서 워크플로가 완료되는 시기 또는 예외를 throw하는 경우 등을 알고 싶을 수 있습니다. 이러한 유형의 알림에 대해 WorkflowApplication 클래스에는 실행 중단, 처리되지 않은 예외, 완료됨, 유휴 및 언로드를 포함하여 워크플로 실행의 특정 조건에 대응하는 코드를 추가하는 이벤트처럼 사용할 수 있는 Action<T> 형식의 속성 집합이 있습니다.  WorkflowApplication을 사용하여 워크플로를 실행하는 경우 이벤트를 처리하는 작업을 사용하여 그림 24에 표시된 것과 유사한 코드를 사용할 수 있습니다. 

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

wf. Completed = delegate(WorkflowApplicationCompletedEventArgs e)

{

    Console.WriteLine("워크플로 {0} 완료", 예: InstanceId);

};

wf. Aborted = delegate(WorkflowApplicationAbortedEventArgs e)

{

    Console.WriteLine(예: Reason);

};

wf. OnUnhandledException =

  delegate(WorkflowApplicationUnhandledExceptionEventArgs e)

  {

      Console.WriteLine(예: UnhandledException.ToString());

      return UnhandledExceptionAction.Terminate;

  };

wf. Run();

그림 24: WorkflowApplication 작업

이 예제에서는 간단한 콘솔 애플리케이션인 호스트 코드의 목표는 워크플로가 완료되거나 오류가 발생하는 경우 콘솔을 통해 사용자에게 알리는 것입니다.  실제 시스템에서 호스트는 이러한 이벤트에 관심을 가지며 관리자에게 오류에 대한 정보를 제공하거나 인스턴스를 더 잘 관리하기 위해 다르게 반응할 수 있습니다.

워크플로 확장

WF3 이후 WF의 핵심 기능 중 하나는 모든 .NET 애플리케이션 도메인에서 호스트할 수 있을 만큼 가볍다는 것입니다.  런타임은 다른 도메인에서 실행될 수 있으며 사용자 지정된 실행 의미 체계가 필요할 수 있으므로 런타임 동작의 다양한 측면을 런타임에서 외부화해야 합니다.  워크플로 확장이 시작되는 곳입니다.  워크플로 확장을 사용하면 개발자가 호스트 코드를 작성하는 경우 사용자 지정 코드로 확장하여 런타임에 동작을 추가할 수 있습니다. 

WF 런타임에서 인식하는 두 가지 확장 유형은 지속성 및 추적 확장입니다. 지속성 확장은 워크플로의 상태를 지속성 저장소에 저장하고 필요할 때 해당 상태를 검색하는 핵심 기능을 제공합니다.  프레임워크와 함께 제공되는 지속성 확장에는 Microsoft SQL Server에 대한 지원이 포함되지만 다른 데이터베이스 시스템 또는 지속성 저장소를 지원하도록 확장을 작성할 수 있습니다. 

고집

지속성 확장을 사용하려면 먼저 상태를 유지하도록 데이터베이스를 설정해야 합니다. 이 스크립트는 %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<언어>(예: c:\windows\microsoft.net\framework\v4.0.30319\sql\en\)에 있는 SQL 스크립트를 사용하여 수행할 수 있습니다. 데이터베이스를 만든 후 두 스크립트를 실행하여 데이터베이스 내에서 구조(SqlWorkflowInstanceStoreSchema.sql)와 저장 프로시저(SqlWorkflowInstanceStoreLogic.sql)를 모두 만듭니다. 

데이터베이스가 설정되면 WorkflowApplication 클래스와 함께 SqlWorkflowInstanceStore를 사용합니다.  먼저 저장소를 만들고 구성한 다음 그림 25와 같이 WorkflowApplication에 제공합니다.

WorkflowApplication 애플리케이션 = new WorkflowApplication(activity);

InstanceStore instanceStore = new SqlWorkflowInstanceStore(

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

InstanceView 뷰 = instanceStore.Execute(

    instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),

    TimeSpan.FromSeconds(30));

instanceStore.DefaultInstanceOwner = view. InstanceOwner;

신청. InstanceStore = instanceStore;

그림 25: SQL 지속성 공급자 추가

워크플로를 유지할 수 있는 방법에는 두 가지가 있습니다.  첫 번째는 워크플로에서 지속 활동을 직접 사용하는 것입니다.  이 작업을 실행하면 워크플로 상태가 데이터베이스에 유지되어 워크플로의 현재 상태가 저장됩니다.  이렇게 하면 워크플로 작성자가 워크플로의 현재 상태를 저장하는 것이 중요한 시기를 제어할 수 있습니다.  두 번째 옵션을 사용하면 워크플로 인스턴스에서 다양한 이벤트가 발생할 때 호스팅 애플리케이션이 워크플로 상태를 유지할 수 있습니다. 워크플로가 유휴 상태일 가능성이 높습니다.  WorkflowApplication에서 PersistableIdle 작업을 등록하면 호스트 코드가 이벤트에 응답하여 인스턴스를 유지, 언로드 또는 둘 다 유지해야 하는지 여부를 나타낼 수 있습니다.  그림 26은 워크플로가 유휴 상태일 때 알림을 받고 유지되도록 등록하는 호스트 애플리케이션을 보여줍니다. 

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

그림 26: 워크플로가 유휴 상태일 때 워크플로 언로드

워크플로가 유휴 상태이고 지속되면 WF 런타임은 메모리에서 언로드하여 처리가 필요한 다른 워크플로에 대한 리소스를 확보할 수 있습니다.  PersistableIdle 작업에서 적절한 열거형을 반환하여 워크플로 인스턴스를 언로드하도록 선택할 수 있습니다.  워크플로가 언로드되면 호스트가 지속성 저장소에서 다시 로드하여 다시 시작하려고 합니다.  이렇게 하려면 인스턴스 저장소 및 인스턴스 식별자를 사용하여 워크플로 인스턴스를 로드해야 합니다. 그러면 인스턴스 저장소에서 상태를 로드하라는 메시지가 표시됩니다.  상태가 로드되면 WorkflowApplication의 Run 메서드를 사용하거나 그림 27과 같이 책갈피를 다시 설정할 수 있습니다.  책갈피에 대한 자세한 내용은 사용자 지정 활동 섹션을 참조하세요. 

WorkflowApplication 애플리케이션 = new WorkflowApplication(activity);

신청. InstanceStore = instanceStore;

신청. Load(id);

신청. ResumeBookmark(readLineBookmark, input);

그림 27: 지속형 워크플로 다시

WF4의 변경 사항 중 하나는 워크플로 상태가 유지되고 인수 및 변수의 새로운 데이터 관리 기술에 크게 의존하는 방식입니다.  전체 활동 트리를 직렬화하고 워크플로의 수명 동안 상태를 유지 관리하는 대신 책갈피 정보와 같은 일부 런타임 데이터와 함께 범위 변수 및 인수 값에서만 유지됩니다.  그림 27에서는 지속형 인스턴스가 다시 로드될 때 인스턴스 저장소를 사용하는 것 외에도 워크플로를 정의하는 작업도 전달됩니다. 기본적으로 데이터베이스의 상태는 제공된 활동에 적용됩니다.  이 기술을 사용하면 버전 관리의 유연성이 훨씬 향상되고 상태가 메모리 공간이 작기 때문에 성능이 향상됩니다. 

추적

지속성을 사용하면 호스트가 장기 실행 프로세스를 지원하고, 호스트 간에 인스턴스를 부하 분산하고, 다른 내결함성 동작을 지원할 수 있습니다.  그러나 워크플로 인스턴스가 완료되면 데이터베이스의 상태가 더 이상 유용하지 않으므로 삭제되는 경우가 많습니다.  프로덕션 환경에서는 워크플로를 관리하고 비즈니스 프로세스에 대한 인사이트를 얻는 데 중요합니다.  애플리케이션에서 발생하는 작업을 추적할 수 있다는 것은 WF 런타임을 사용하는 강력한 기능 중 하나입니다.  

추적은 참가자 추적 및 추적 프로필의 두 가지 주요 구성 요소로 구성됩니다.  추적 프로필은 런타임에서 추적할 이벤트 및 데이터를 정의합니다. 프로필에는 세 가지 기본 유형의 쿼리가 포함될 수 있습니다.

  • ActivityStateQuery - 작업 상태(예: 닫힘) 및 데이터 추출을 위한 변수 또는 인수를 식별하는 데 사용됩니다.
  • WorkflowInstanceQuery – 워크플로 이벤트를 식별하는 데 사용됨
  • CustomTrackingQuery – 일반적으로 사용자 지정 작업 내에서 데이터를 추적하기 위한 명시적 호출을 식별하는 데 사용됩니다.

예를 들어 그림 28에서는 ActivityStateQuery 및 WorkflowInstanceQuery를 포함하는 TrackingProfile을 만듭니다.   쿼리는 정보를 수집할 시기와 수집할 데이터를 모두 나타냅니다.  ActivityStateQuery의 경우 해당 값을 추출하고 추적된 데이터에 추가해야 하는 변수 목록을 포함했습니다. 

TrackingProfile 프로필 = 새 TrackingProfile

{

    Name = "SimpleProfile",

    쿼리 = {

        new WorkflowInstanceQuery {

            States = { "*" }

        },

        new ActivityStateQuery {

            ActivityName = "WriteLine",

            States={ "*" },

            변수 = {"Text" }

        }

    }

};

그림 28: 추적 프로필 만들기

추적 참가자는 런타임에 추가할 수 있는 확장이며, 추적 레코드가 내보내질 때 처리를 담당합니다.  TrackingParticipant 기본 클래스는 TrackingProfile 및 추적을 처리하는 Track 메서드를 제공하는 속성을 정의합니다.  그림 29는 콘솔에 데이터를 쓰는 제한된 사용자 지정 추적 참가자를 보여 줍니다.  추적 참가자를 사용하려면 추적 프로필을 사용하여 초기화한 다음 워크플로 인스턴스의 확장 컬렉션에 추가해야 합니다. 

public 클래스 ConsoleTrackingParticipant: TrackingParticipant

{

   protected override void Track(TrackingRecord record, TimeSpan timeout)

   {

      ActivityStateRecord aRecord = record as ActivityStateRecord;

      if(aRecord != null)

      {

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

            aRecord.Activity.Name, aRecord.State);

         foreach(aRecord.Arguments의 var 항목)

         {

            Console.ForegroundColor = ConsoleColor.Cyan;

            Console.WriteLine("Variable:{0} 값은 {1}",

                항목. 키, 항목입니다. 값);

            Console.ResetColor();

         }

      }

   }

}

그림 29: 콘솔 추적 참가자

WF는 데이터의 고성능 추적을 사용하도록 런타임에 추가할 수 있는 EtwTrackingParticipant(ETW = Windows용 엔터프라이즈 추적)와 함께 제공됩니다.  ETW는 Windows의 네이티브 구성 요소이며 드라이버 및 기타 커널 수준 코드를 포함하여 OS의 많은 구성 요소 및 서비스에서 사용되는 추적 시스템입니다.  ETW에 기록된 데이터는 사용자 지정 코드를 사용하거나 예정된 Windows Server AppFabric과 같은 도구를 사용하여 사용할 수 있습니다.  WF3에서 마이그레이션하는 사용자의 경우 프레임워크의 일부로 SQL 추적 참가자 배송이 없기 때문에 변경됩니다.  그러나 Windows Server AppFabric은 ETW 데이터를 수집하고 SQL 데이터베이스에 저장하는 ETW 소비자와 함께 제공합니다.  마찬가지로 ETW 소비자를 빌드하고 원하는 형식으로 데이터를 저장하거나 사용자 환경에 더 적합한 자체 추적 참가자를 작성할 수 있습니다. 

사용자 지정 활동 만들기

기본 작업 라이브러리에는 서비스, 개체 및 컬렉션과 상호 작용하기 위한 다양한 활동 팔레트가 포함되어 있지만 데이터베이스, 전자 메일 서버 또는 사용자 지정 도메인 개체 및 시스템과 같은 하위 시스템과 상호 작용하기 위한 활동은 제공하지 않습니다.  WF4를 시작하는 과정의 일부는 워크플로를 빌드할 때 어떤 핵심 활동이 필요하거나 원하는지 파악하는 것입니다.  중요한 점은 핵심 작업 단위를 빌드할 때 개발자가 워크플로에서 사용할 수 있는 더 거친 작업으로 결합할 수 있다는 것입니다. 

활동 클래스 계층 구조

활동은 활동이지만 모든 활동에 동일한 요구 사항이나 요구 사항이 있는 것은 아닙니다.  이러한 이유로 활동에서 직접 파생되는 모든 활동이 아니라, 사용자 지정 활동을 빌드할 때 선택할 수 있는 작업 기본 클래스의 계층 구조가 그림 30에 나와 있습니다. 

그림 30: 활동 클래스 계층 구조

대부분의 사용자 지정 활동의 경우 AsyncCodeActivity, CodeActivity 또는 NativeActivity(또는 제네릭 변형 중 하나)에서 파생되거나 활동을 선언적으로 모델링합니다.  상위 수준에서는 다음과 같이 4개의 기본 클래스를 설명할 수 있습니다.

  • 활동 - 일반적으로 XAML을 사용하여 정의된 다른 활동을 작성하여 활동을 모델링하는 데 사용됩니다.
  • CodeActivity – 작업을 완료하기 위해 일부 코드를 작성해야 하는 경우 간소화된 기본 클래스입니다.
  • AsyncCodeActivity – 활동이 비동기적으로 일부 작업을 수행할 때 사용됩니다.
  • NativeActivity – 예를 들어 다른 활동을 예약하거나 책갈피를 만들기 위해 작업에 런타임 내부 액세스 권한이 필요한 경우입니다.

다음 섹션에서는 이러한 각 기본 클래스를 사용하는 방법을 확인하기 위해 몇 가지 활동을 빌드합니다.  일반적으로 사용할 기본 클래스에 대해 생각할 때는 작업 기본 클래스로 시작하고 다음 섹션과 같이 선언적 논리를 사용하여 활동을 빌드할 수 있는지 확인해야 합니다.  다음으로, 작업을 수행하기 위해 일부 .NET 코드를 작성해야 하는 경우 CodeActivity는 간소화된 모델을 제공하고, 활동을 비동기적으로 실행하려면 AsyncCodeActivity를 제공합니다.  마지막으로 프레임워크에 있는 것과 같은 제어 흐름 작업(예: Switch, If)을 작성하는 경우 자식 활동을 관리하려면 NativeActivity 기본 클래스에서 파생해야 합니다. 

활동 디자이너를 사용하여 활동 작성

새 활동 라이브러리 프로젝트를 만들거나 WF 프로젝트에 새 항목을 추가하고 활동 템플릿을 선택하면 빈 활동 요소가 있는 XAML 파일이 표시됩니다.  디자이너에서는 활동의 본문을 만들 수 있는 디자인 화면으로 표시됩니다.  둘 이상의 단계를 포함하는 활동을 시작하려면 일반적으로 시퀀스 활동을 본문으로 끌어 본문으로 끌어 본문이 단일 자식 활동을 나타내기 때문에 실제 활동 논리로 채우는 데 도움이 됩니다. 

인수가 있는 구성 요소의 메서드와 마찬가지로 작업을 생각할 수 있습니다.  활동 자체에서 해당 방향성과 함께 인수를 정의하여 활동의 인터페이스를 정의할 수 있습니다.  활동 내에서 사용하려는 변수는 앞에서 언급한 루트 시퀀스처럼 본문을 구성하는 활동에 정의되어야 합니다.  예를 들어 GetManager 및 SendMail이라는 두 가지 간단한 작업을 구성하는 NotifyManager 작업을 빌드할 수 있습니다. 

먼저 Visual Studio 2010에서 새 ActivityLibrary 프로젝트를 만들고 Activity1.xaml 파일의 이름을 NotifyManager.xaml로 바꿉니다.  그런 다음 도구 상자에서 시퀀스 작업을 끌어 디자이너에 추가합니다.  시퀀스가 배치되면 그림 31과 같이 자식 활동으로 채울 수 있습니다. (이 예제에 사용된 활동은 참조된 프로젝트의 사용자 지정 활동이며 프레임워크에서는 사용할 수 없습니다.)

그림 31: 관리자 활동 알림

이 작업은 직원 이름 및 메시지 본문에 대한 인수를 가져와야 합니다. GetManager 작업은 직원의 관리자를 조회하고 메일을 OutArgument<문자열>제공합니다.  마지막으로 SendMail 활동은 관리자에게 메시지를 보냅니다.  다음 단계는 디자이너 맨 아래에 있는 인수 창을 확장하고 두 개의 필수 입력 인수에 대한 정보를 입력하여 작업에 대한 인수를 정의하는 것입니다. 

이러한 항목을 작성하려면 NotifyManager 활동에 지정된 입력 인수를 개별 자식 활동에 전달할 수 있어야 합니다.  GetManager 작업의 경우 활동에 대한 인수인 직원 이름이 필요합니다. 그림 31과 같이 GetManager 작업의 employeeName 인수에 대한 속성 대화 상자에서 인수 이름을 사용할 수 있습니다.  SendMail 활동에는 관리자의 이메일 주소와 메시지가 필요합니다.  메시지의 경우 메시지가 포함된 인수의 이름을 입력할 수 있습니다.  그러나 전자 메일 주소의 경우 GetManager 작업의 out 인수를 SendMail 활동의 in 인수로 전달하는 방법이 필요합니다.  이를 위해 변수가 필요합니다. 

시퀀스 작업을 강조 표시한 후 변수 창을 사용하여 "mgrEmail"이라는 변수를 정의할 수 있습니다.  이제 GetManager 작업의 ManagerEmail out 인수와 SendMail 작업의 To in 인수에 대한 변수 이름을 입력할 수 있습니다.  GetManager 작업이 실행되면 출력 데이터가 해당 변수에 저장되고 SendMail 작업이 실행되면 해당 변수의 데이터를 in 인수로 읽습니다. 

방금 설명한 접근 방식은 활동 본문을 정의하기 위한 순수한 선언적 모델입니다.  경우에 따라 코드에서 활동의 본문을 지정하는 것이 좋습니다.  예를 들어 활동에는 자식 활동 집합을 구동하는 속성 컬렉션이 포함될 수 있습니다. 할당 작업 집합을 만드는 데 사용되는 명명된 값 집합은 코드를 사용하는 것이 바람직한 경우 중 하나입니다.  이러한 경우 활동에서 파생되는 클래스를 작성하고 구현 속성에 코드를 작성하여 활동의 기능을 나타내는 활동(및 모든 자식 요소)을 만들 수 있습니다.  최종 결과는 두 경우 모두 동일합니다. 논리는 다른 활동을 구성하여 정의되며 본문이 정의된 메커니즘만 다릅니다.  그림 32는 코드에서 정의되는 것과 동일한 NotifyManager 활동을 보여 있습니다. 

public 클래스 NotifyManager: Activity

{

    public InArgument<string> EmployeeName { get; set; }

    public InArgument<string> Message { get; set; }

    protected override Func<Activity> Implementation

    {

        가져오기

        {

            return () =>

            {

                변수<문자열> mgrEmail =

                새 변수<문자열> { Name = "mgrEmail" };

                시퀀스 s = 새 시퀀스

                {

                    변수 = { mgrEmail },

                    활동 = {

                        new GetManager {

                            EmployeeName =

                                새 VisualBasicValue<문자열>("EmployeeName"),

                                결과 = mgrEmail,

                        },

                        new SendMail {

                            ToAddress = mgrEmail,

                            MailBody = 새 VisualBasicValue<문자열>("메시지"),

                            From = "test@contoso.com",

                            제목 = "자동화된 전자 메일"

                        }

                    }

                };

                return s;

            };

        }

        set { base. 구현 = 값; }

    }

}

그림 32: 코드 사용하여 활동 컴퍼지션

기본 클래스는 Activity이고 Implementation 속성은 단일 활동을 반환하는 Func<작업>. 이 코드는 워크플로를 만들 때 이전에 보여 준 코드와 매우 유사하며 두 경우 모두 활동을 만드는 것이 목표이므로 놀라운 일이 아닙니다.  또한 코드 접근 방식 인수를 변수로 설정하거나 식을 사용하여 EmployeeName 및 Message 인수에 대해 표시된 것처럼 두 자식 작업에 사용되는 인수를 다른 인수에 연결할 수 있습니다. 

사용자 지정 작업 클래스 작성

다른 활동을 작성하여 활동 논리를 수행할 수 없는 경우 코드 기반 작업을 작성할 수 있습니다.  코드에서 활동을 작성할 때 적절한 클래스에서 파생된 인수를 정의한 다음 Execute 메서드를 재정의합니다.  Execute 메서드는 작업이 작업을 수행할 때 런타임에 의해 호출됩니다.

이전 예제에서 사용된 SendMail 활동을 빌드하려면 먼저 기본 형식을 선택해야 합니다.  활동 기본 클래스를 사용하여 SendMail 활동의 기능을 만들고 TryCatch<T> 및 InvokeMethod 작업을 작성할 수 있지만 대부분의 개발자는 CodeActivity 및 NativeActivity 기본 클래스 중에서 선택하고 실행 논리에 대한 코드를 작성하는 것이 더 자연스러워집니다.  이 작업은 책갈피를 만들거나 다른 활동을 예약할 필요가 없으므로 CodeActivity 기본 클래스에서 파생할 수 있습니다.  또한 이 작업은 단일 출력 인수를 반환하므로 OutArgument<TResult>제공하는 CodeActivity<TResult> 파생되어야 합니다.  첫 번째 단계는 이메일 속성에 대한 몇 가지 입력 인수를 정의하는 것입니다.  그런 다음 Execute 메서드를 재정의하여 활동의 기능을 구현합니다.  그림 33은 완료된 활동 클래스를 보여줍니다. 

public 클래스 SendMail : CodeActivity<SmtpStatusCode>

{

    public InArgument<문자열> { get; set; }

    public InArgument<문자열> { get; set; }

    public InArgument<string> Subject { get; set; }

    public InArgument<문자열> 본문 { get; set; }

    protected override SmtpStatusCode Execute(

    CodeActivityContext 컨텍스트)

    {

        노력하다

        {

            SmtpClient 클라이언트 = 새 SmtpClient();

            클라이언트. Send(

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

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

        }

        catch(SmtpException smtpEx)

        {

            return smtpEx.StatusCode;

        }

        return SmtpStatusCode.Ok;

    }

}

그림 33: SendMail 사용자 지정 작업

인수 사용에 대해 유의해야 할 몇 가지 사항이 있습니다.  InArgument<T>형식을 사용하여 여러 표준 .NET 속성을 선언했습니다.  코드 기반 작업이 입력 및 출력 인수를 정의하는 방법입니다.  그러나 코드 내에서 이러한 속성을 참조하여 인수 값을 가져올 수는 없습니다. 제공된 ActivityContext를 사용하여 값을 검색하려면 인수를 일종의 핸들로 사용해야 합니다. 이 경우 CodeActivityContext입니다.  인수 클래스의 Get 메서드를 사용하여 값을 검색하고 Set 메서드를 사용하여 인수에 값을 할당합니다. 

작업이 Execute 메서드를 완료하면 런타임은 이를 감지하고 작업이 완료된 것으로 가정하고 닫습니다.  비동기 또는 장기 실행 작업의 경우 향후 섹션에서 설명하는 이 동작을 변경하는 방법이 있지만, 이 경우 전자 메일이 전송되면 작업이 완료됩니다. 

이제 NativeActivity 기본 클래스를 사용하여 자식 활동을 관리하는 작업을 살펴보겠습니다.  지정된 횟수만큼 일부 자식 활동을 실행하는 반복기 작업을 빌드합니다.  먼저 수행할 반복 횟수, 실행할 작업의 속성 및 현재 반복 수를 저장할 변수를 보유하는 인수가 필요합니다. Execute 메서드는 BeginIteration 메서드를 호출하여 초기 반복을 시작하고 후속 반복은 동일한 방식으로 호출됩니다.  그림 34에서 변수는 Get 및 Set 메서드를 사용하는 인수와 동일한 방식으로 조작됩니다. 

public 클래스 반복기: NativeActivity

{

    public Activity Body { get; set; }

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

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

    public Iterator()

    {

        CurrentIteration = 새 변수<int> { Default = 0 };

    }

    protected override void Execute(NativeActivityContext context)

    {

        BeginIteration(context);

    }

    private void BeginIteration(NativeActivityContext 컨텍스트)

    {

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

        {

            문맥. ScheduleActivity(본문,

            new CompletionCallback(ChildComplete),

            new FaultCallback(ChildFaulted));

        }

    }

}

그림 34: 반복기 네이티브 작업

BeginIteration 메서드를 자세히 살펴보면 ScheduleActivity 메서드 호출을 확인할 수 있습니다.  이는 작업이 런타임과 상호 작용하여 실행을 위해 다른 활동을 예약하는 방법이며 NativeActivity에서 파생되는 이 기능이 필요하기 때문입니다.  또한 ActivityExecutionContext에서 ScheduleActivity 메서드를 호출할 때 두 개의 콜백 메서드가 제공됩니다.  본문으로 사용될 활동이나 완료하는 데 걸리는 시간을 알 수 없으며 WF가 비동기 프로그래밍 환경이기 때문에 콜백은 자식 작업이 완료될 때 활동을 알리는 데 사용되므로 응답할 코드를 작성할 수 있습니다.

두 번째 콜백은 자식 작업이 예외를 throw하는 경우 호출되는 FaultCallback입니다.  이 콜백에서는 예외를 수신하고 ActivityFaultContext를 통해 런타임에 오류가 처리되었음을 나타내는 기능을 갖습니다. 이 기능은 작업에서 버블링되지 않도록 합니다. 그림 35는 반복기 작업에 대한 콜백 메서드를 보여 줍니다. 여기서 둘 다 다음 반복을 예약하고 FaultCallback은 오류를 처리하여 실행을 다음 반복으로 계속할 수 있도록 합니다.

private void ChildComplete(NativeActivityContext 컨텍스트,

ActivityInstance 인스턴스)

{

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

    BeginIteration(context);

}

private void ChildFaulted(NativeActivityFaultContext 컨텍스트, Exception ex,

ActivityInstance 인스턴스)

{

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

    문맥. HandleFault();

}

그림 35: 활동 콜백

작업이 장기 실행될 수 있는 작업을 수행해야 하는 경우 워크플로 스레드를 사용하여 다른 활동을 계속 처리하거나 다른 워크플로를 처리하는 데 사용할 수 있도록 작업을 다른 스레드로 전달하는 것이 이상적입니다.  그러나 단순히 스레드를 시작하고 런타임에 컨트롤을 반환하면 런타임에서 만드는 스레드를 모니터링하지 않으므로 문제가 발생할 수 있습니다.  런타임에서 워크플로가 유휴 상태인 것으로 확인되면 워크플로가 언로드하거나 콜백 메서드를 삭제하거나 런타임에서 작업이 완료되었다고 가정하고 워크플로의 다음 작업으로 이동할 수 있습니다.  활동에서 비동기 프로그래밍을 지원하려면 AsyncCodeActivity 또는 AsyncCodeActivity<T>파생합니다. 

그림 36에서는 RSS 피드를 로드하는 비동기 작업의 예를 보여 줍니다.  execute 메서드의 이 서명은 IAsyncResult를 반환한다는 측면에서 비동기 작업에 대해 다릅니다.  실행 메서드에 코드를 작성하여 비동기 작업을 시작합니다.  비동기 작업이 완료되면 콜백을 처리하고 실행 결과를 반환하도록 EndExecute 메서드를 재정의합니다. 

public sealed 클래스 GetFeed: AsyncCodeActivity<SyndicationFeed>

{

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

    protected override IAsyncResult BeginExecute(

    AsyncCodeActivityContext 컨텍스트, AsyncCallback 콜백,

    개체 상태)

    {

        var req = (HttpWebRequest)HttpWebRequest.Create(

        FeedUrl.Get(context));

        req. 메서드 = "GET";

        문맥. UserState = req;

        req를 반환합니다. BeginGetResponse(new AsyncCallback(콜백), 상태);

    }

    protected override SyndicationFeed EndExecute(

    AsyncCodeActivityContext 컨텍스트, IAsyncResult 결과)

    {

        HttpWebRequest req = context. UserState as HttpWebRequest;

        WebResponse wr = req. EndGetResponse(result);

        SyndicationFeed localFeed = SyndicationFeed.Load(

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

        return localFeed;

    }

}

그림 36: 비동기 작업 만들기

추가 활동 개념

이제 기본 클래스, 인수 및 변수를 사용하여 활동을 만들고 실행을 관리하는 기본 사항을 확인했습니다. 활동 개발에 사용할 수 있는 몇 가지 고급 기능에 대해 간략하게 설명하겠습니다. 

책갈피를 사용하면 활동 작성자가 워크플로 실행에서 다시 시작 지점을 만들 수 있습니다.  책갈피가 만들어지면 다시 시작되어 중단된 위치에서 워크플로 처리를 계속할 수 있습니다.  책갈피는 상태의 일부로 저장되므로 비동기 활동과 달리 책갈피를 만든 후에는 워크플로 인스턴스를 유지 및 언로드하는 것은 문제가 되지 않습니다.  호스트가 워크플로를 다시 시작하려는 경우 워크플로 인스턴스를 로드하고 ResumeBookmark 메서드를 호출하여 중단된 위치에서 인스턴스를 다시 시작합니다.  책갈피를 다시 시작하면 데이터도 활동으로 전달될 수 있습니다.   그림 37은 입력을 받을 책갈피를 만들고 데이터가 도착할 때 호출할 콜백 메서드를 등록하는 ReadLine 작업을 보여 줍니다.  런타임은 활동에 미해결 책갈피가 있는 시기를 알고 있으며 책갈피가 다시 시작될 때까지 활동을 닫지 않습니다.  ResumeBookmark 메서드는 WorkflowApplication 클래스에서 사용하여 명명된 책갈피로 데이터를 보내고 BookmarkCallback에 신호를 보낼 수 있습니다.  

public 클래스 ReadLine: NativeActivity<문자열>

{

    public OutArgument<string> InputText { get; set; }

    protected override void Execute(NativeActivityContext context)

    {

        문맥. CreateBookmark("ReadLine",

        new BookmarkCallback(BookmarkResumed));

    }

    private void BookmarkResumed(NativeActivityContext 컨텍스트,

        책갈피 bk, 개체 상태)

    {

        Result.Set(context, state);

    }

}

그림 37: 책갈피 만들기

활동 작성자를 위한 또 다른 강력한 기능은 ActivityAction의 개념입니다.  ActivityAction은 명령적 코드에서 Action 클래스와 동일한 워크플로입니다. 공통 대리자를 설명합니다. 그러나 일부의 경우 템플릿의 개념을 이해하기가 더 쉬울 수 있습니다.  데이터 집합을 반복하고 각 데이터 항목에 대한 자식 활동을 예약할 수 있는 ForEach<T> 작업을 고려합니다.  활동의 소비자가 본문을 정의하고 T 형식의 데이터 항목을 사용할 수 있도록 허용하는 방법이 필요합니다. 기본적으로 T 형식의 항목을 수락하고 이에 대해 작업할 수 있는 몇 가지 작업이 필요하며, ActivityAction<T> 이 시나리오를 사용하도록 설정하고 항목을 처리하는 활동의 인수 및 정의에 대한 참조를 제공합니다.  사용자 지정 활동에서 ActivityAction을 사용하여 활동의 소비자가 적절한 지점에서 자신의 단계를 추가할 수 있도록 할 수 있습니다.  이를 통해 소비자가 관련 부분을 입력하여 실행을 사용자 지정할 수 있는 일종의 워크플로 또는 활동 템플릿을 만들 수 있습니다.  또한 ActivityFunc<TResult> 사용할 수 있으며, 활동을 호출해야 하는 대리자가 값을 반환할 때 관련 대안을 사용할 수 있습니다.  

마지막으로 개별 설정을 확인하고 허용 가능한 자식 활동을 제한하여 작업이 제대로 구성되었는지 확인할 수 있습니다. 특정 인수를 필요로 하는 일반적인 경우 작업의 속성 선언에 RequiredArgument 특성을 추가할 수 있습니다.  더 많은 관련 유효성 검사를 위해 작업의 생성자에서 제약 조건을 만들고 활동 클래스에 표시되는 제약 조건 컬렉션에 추가합니다.  제약 조건은 대상 활동을 검사하고 유효성을 확인하기 위해 작성된 활동입니다.  그림 38은 RequestedIterations 속성이 설정되어 있는지 확인하는 반복기 작업의 생성자를 보여 줍니다. 마지막으로 CacheMetadata 메서드를 재정의하여 ActivityMetdata 매개 변수에서 AddValidationError 메서드를 호출하여 활동에 대한 유효성 검사 오류를 추가할 수 있습니다. 

var act = 새 DelegateInArgument<반복기> { Name = "constraintArg" };

var vctx = 새 DelegateInArgument<ValidationContext>();

제약 조건<반복기> 단점 = 새 제약 조건<반복기>

{

    본문 = 새 ActivityAction<반복기, ValidationContext>

    {

        Argument1 = act,

        Argument2 = vctx,

        처리기 = 새 AssertValidation

        {

            메시지 = "반복을 제공해야 합니다.",

            PropertyName = "RequestedIterations",

            Assertion = new InArgument<bool>(

                (e) => 동작입니다. Get(e). RequestedIterations != null)

        }

    }

};

기지. Constraints.Add(cons);

그림 38: 제약 조건

활동 디자이너

WF의 이점 중 하나는 애플리케이션 논리를 선언적으로 프로그래밍할 수 있지만 대부분의 사람들은 XAML을 직접 작성하지 않기 때문에 WF의 디자이너 환경이 매우 중요하다는 것입니다.  사용자 지정 활동을 빌드할 때 활동 소비자에게 표시 및 시각적 상호 작용 환경을 제공하는 디자이너를 만들려고 할 수도 있습니다.  WF용 디자이너는 Windows Presentation Foundation을 기반으로 합니다. 즉, 디자이너를 위한 풍부한 UI를 빌드하기 위한 스타일 지정, 트리거, 데이터 바인딩 및 기타 모든 유용한 도구가 있습니다.  또한 WF는 디자이너에서 개별 자식 활동 또는 활동 컬렉션을 표시하는 작업을 간소화하는 데 사용할 수 있는 몇 가지 사용자 컨트롤을 제공합니다.  네 가지 기본 컨트롤은 다음과 같습니다.

  • ActivityDesigner – 활동 디자이너에 사용되는 루트 WPF 컨트롤
  • WorkflowItemPresenter – 단일 활동을 표시하는 데 사용됨
  • WorkflowItemsPresenter – 자식 활동 컬렉션을 표시하는 데 사용됨
  • ExpressionTextBox - 인수와 같은 식을 대신 편집할 수 있도록 설정하는 데 사용됩니다.

WF4에는 처음에 아티팩트를 만드는 데 사용할 수 있는 ActivityDesignerLibrary 프로젝트 템플릿 및 ActivityDesigner 항목 템플릿이 포함되어 있습니다.  디자이너가 초기화되면 위의 요소를 사용하여 자식 활동의 데이터 및 시각적 표현을 표시하는 방법을 배치하여 활동의 모양과 느낌을 사용자 지정할 수 있습니다.  디자이너 내에서 실제 활동에 대한 추상화이고 활동의 모든 속성을 표시하는 ModelItem에 액세스할 수 있습니다. 

WorkflowItemPresenter 컨트롤을 사용하여 활동의 일부인 단일 활동을 표시할 수 있습니다.  예를 들어 이전에 표시된 반복기 작업으로 활동을 끌어와 반복기 활동에 포함된 활동을 표시하는 기능을 제공하려면 그림 39에 표시된 XAML을 사용하여 WorkflowItemPresenter를 ModelItem.Body에 바인딩할 수 있습니다.  그림 40에서는 워크플로 디자인 화면에서 사용 중인 디자이너를 보여 줍니다. 

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

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

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

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

assembly=System.Activities.Presentation"

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

assembly=System.Activities.Presentation">

  <Grid>

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

<sap:WorkflowItemPresenter MinHeight="50"

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

HintText="여기에 본문 추가" />

    </Border>

  /Grid><

/sap:ActivityDesigner><

그림 39: 활동 디자이너의 WorkflowItemPresenter

그림 40: 반복기 디자이너

WorkflowItemsPresenter는 자식과 같은 여러 항목을 시퀀스로 표시하는 유사한 기능을 제공합니다. 항목을 표시하는 방법에 대한 템플릿 및 방향을 정의할 수 있으며, 커넥터, 화살표 또는 더 흥미로운 작업 간에 시각적 요소를 추가하는 스페이서 템플릿을 정의할 수 있습니다.  그림 41에서는 자식 활동을 각각 가로줄로 표시하는 사용자 지정 시퀀스 작업에 대한 간단한 디자이너를 보여 줍니다.

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

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

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

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

assembly=System.Activities.Presentation"

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

assembly=System.Activities.Presentation">

    <Grid>

      stackPanel><

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

     <sap:WorkflowItemsPresenter HintText="Drop Activities here"

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

     sap:WorkflowItemsPresenter.SpacerTemplate><

 dataTemplate><

    <사각형 높이="3" Width="40"

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

                     </DataTemplate>

                  </sap:WorkflowItemsPresenter.SpacerTemplate>

                  sap:WorkflowItemsPresenter.ItemsPanel><

                      ItemsPanelTemplate><

                          <StackPanel Orientation="Vertical"/>

                      </ItemsPanelTemplate>

                   </sap:WorkflowItemsPresenter.ItemsPanel>

            </sap:WorkflowItemsPresenter>

        </Border>

      </StackPanel>

    /Grid><

/sap:ActivityDesigner><

그림 41: WorkflowItemsPresenter 사용하는 사용자 지정 시퀀스 디자이너

그림 42: 시퀀스 디자이너

마지막으로 ExpressionTextBox 컨트롤을 사용하면 속성 표 외에도 디자이너 내에서 활동 인수를 현재 위치에서 편집할 수 있습니다. Visual Studio 2010에서는 입력되는 식에 대한 intellisense 지원이 포함됩니다. 그림 43에서는 이전에 만든 GetManager 활동의 디자이너를 보여 줍니다. 디자이너 내에서 EmployeeName 및 ManagerEmail 인수를 편집할 수 있습니다. 실제 디자이너는 그림 44에 나와 있습니다. 

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

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

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

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

assembly=System.Activities.Presentation"

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

assembly=System.Activities.Presentation"

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

assembly=System.Activities.Presentation">

    sap:ActivityDesigner.Resources><

        <sapc:ArgumentToExpressionConverter

x:Key="ArgumentToExpressionConverter"

x:Uid="swdv:ArgumentToExpressionConverter_1" />

    /sap:ActivityDesigner.Resources><

    <Grid>

        grid.ColumnDefinitions><

            <ColumnDefinition Width="50" />

             <ColumnDefinition Width="*" />

        /Grid.ColumnDefinitions><

        grid.RowDefinitions><

            <RowDefinition />

            <RowDefinition />

            <RowDefinition />

        /Grid.RowDefinitions><

        <TextBlock VerticalAlignment="Center"

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

        <sapv:ExpressionTextBox Grid.Column="1"

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

       Converter={StaticResource ArgumentToExpressionConverter},

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

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

HintText="< 직원 이름>" />

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

    Converter={StaticResource ArgumentToExpressionConverter},

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

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

    /Grid><

/sap:ActivityDesigner><

그림 43: ExpressionTextBox를 사용하여 디자이너 인수 편집

그림 44: GetManager 활동 디자이너

여기에 제시된 예제 디자이너는 특정 기능을 강조하기는 간단했지만 WPF의 모든 기능을 사용하여 활동을 더 쉽게 구성하고 소비자에게 더 자연스럽게 사용할 수 있습니다.   디자이너를 만든 후에는 활동 클래스에 특성을 적용하거나 IRegisterMetadata 인터페이스를 구현하는 두 가지 방법으로 디자이너를 작업과 연결할 수 있습니다.  활동 정의가 디자이너 선택을 구동하도록 하려면 활동 클래스에 DesignerAttribute를 적용하고 디자이너에 대한 형식을 제공하면 됩니다. 이는 WF3에서와 동일하게 작동합니다.  좀 더 느슨하게 결합된 구현의 경우 IRegisterMetadata 인터페이스를 구현하고 작업 클래스 및 속성에 적용하려는 특성을 정의할 수 있습니다.  그림 45에서는 두 개의 사용자 지정 활동에 디자이너를 적용하는 예제 구현을 보여 줍니다. Visual Studio는 구현을 검색하고 자동으로 호출하는 반면, 다음에 설명된 사용자 지정 디자이너 호스트는 메서드를 명시적으로 호출합니다.

public 클래스 메타데이터: IRegisterMetadata

{

    public void Register()

    {

        AttributeTableBuilder builder = new AttributeTableBuilder();

        건설자. AddCustomAttributes(typeof(SendMail), new Attribute[] {

            new DesignerAttribute(typeof(SendMailDesigner))});

        건설자. AddCustomAttributes(typeof(GetManager), new Attribute[]{

            new DesignerAttribute(typeof(GetManagerDesigner))});

        MetadataStore.AddAttributeTable(builder. CreateTable());

    }

}

그림 45: 활동 디자이너 등록

워크플로 디자이너 다시 호스트

과거에 개발자는 워크플로를 만들거나 편집할 수 있는 기능을 사용자에게 제공하고자 했습니다.  이전 버전의 Windows Workflow Foundation에서는 이것이 가능했지만 간단한 작업은 아니었습니다.  WPF로의 이전과 함께 새로운 디자이너가 제공되며 다시 호스트는 개발 팀의 주요 사용 사례였습니다.  그림 46에 표시된 것과 같은 사용자 지정 WPF 애플리케이션에서 몇 줄의 XAML 및 몇 줄의 코드를 사용하여 디자이너를 호스트할 수 있습니다. 

그림 46: 다시 호스트된 워크플로 디자이너

창의 XAML 그림 47은 그리드를 사용하여 세 개의 열을 레이아웃한 다음 각 셀에 테두리 컨트롤을 배치합니다.  첫 번째 셀에서 도구 상자는 선언적으로 만들어지지만 코드에서도 만들 수 있습니다.  디자이너 뷰 자체와 속성 표의 경우 나머지 셀 각각에 두 개의 빈 테두리 컨트롤이 있습니다.  이러한 컨트롤은 코드에 추가됩니다. 

<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="Workflow Editor" 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><

        <테두리>

            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>

        </Border>

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

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

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

    /Grid><

</Window>

그림 47: 디자이너 다시 호스팅 XAML

디자이너 및 속성 그리드를 초기화하는 코드는 그림 48에 나와 있습니다.  OnInitialized 메서드의 첫 번째 단계는 워크플로 디자이너에서 사용할 모든 활동에 대한 디자이너를 등록하는 것입니다.  DesignerMetadata 클래스는 프레임워크와 함께 제공되는 활동에 연결된 디자이너를 등록하고 메타데이터 클래스는 내 사용자 지정 활동에 대한 디자이너를 등록합니다.  다음으로 WorkflowDesigner 클래스를 만들고 View 및 PropertyInspectorView 속성을 사용하여 렌더링에 필요한 UIElement 개체에 액세스합니다.  디자이너는 빈 시퀀스로 초기화되지만 메뉴를 추가하고 파일 시스템 또는 데이터베이스를 비롯한 다양한 원본에서 워크플로 XAML을 로드할 수도 있습니다. 

public MainWindow()

{

    InitializeComponent();

}

protected override void OnInitialized(EventArgs e) {

    기지. OnInitialized(e);

    RegisterDesigners();

    PlaceDesignerAndPropGrid();

}

private void RegisterDesigners() {

    new DesignerMetadata(). Register();

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

}

private void PlaceDesignerAndPropGrid() {

    WorkflowDesigner designer = new WorkflowDesigner();

    디자이너. Load(new System.Activities.Statements.Sequence());

    DesignerBorder.Child = designer. 보기;

    PropertyGridExpander.Child = designer. PropertyInspectorView;

}

그림 48: 디자이너 다시 호스팅 코드

이 약간의 코드와 태그를 사용하면 워크플로를 편집하고, 도구 상자에서 활동을 끌어서 놓고, 워크플로에서 변수를 구성하고, 활동에 대한 인수를 조작할 수 있습니다.  디자이너에서 Flush를 호출한 다음 WorkflowDesigner의 Text 속성을 참조하여 XAML의 텍스트를 가져올 수도 있습니다.  할 수 있는 일이 훨씬 많고, 시작하기가 이전보다 훨씬 쉽습니다. 

Workflow Services

앞에서 설명한 것처럼 이 Windows Workflow Foundation 릴리스의 주요 영역 중 하나는 Windows Communication Foundation과의 긴밀한 통합입니다.  작업 연습의 예제에서는 워크플로에서 서비스를 호출하는 방법을 보여 주었고, 이 섹션에서는 워크플로를 WCF 서비스로 노출하는 방법을 설명합니다. 

시작하려면 새 프로젝트를 만들고 WCF 워크플로 서비스 프로젝트를 선택합니다. 템플릿이 완료되면 web.config 파일과 XAMLX 확장이 있는 파일이 있는 프로젝트를 찾을 수 있습니다.  XAMLX 파일은 선언적 서비스 또는 워크플로 서비스이며 다른 WCF 템플릿과 마찬가지로 요청을 수신하고 응답을 반환하는 간단한 서비스 구현을 제공합니다.  이 예제에서는 계약을 변경하여 경비 보고서를 받고 보고서를 받은 지표를 반환하는 작업을 만듭니다.  시작하려면 그림 49와 같이 ExpenseReport 클래스를 프로젝트에 추가합니다.

[DataContract]

public 클래스 ExpenseReport

{

    [DataMember]

    public DateTime FirstDateOfTravel { get; set; }

    [DataMember]

    public double TotalAmount { get; set; }

    [DataMember]

    public string EmployeeName { get; set; }

}

그림 49: 지출 보고서 계약 유형

워크플로 디자이너로 돌아가서 변수의 "data" 변수 형식을 방금 정의된 ExpenseReport 형식으로 변경합니다(형식 선택 대화 상자에 표시할 형식에 대한 프로젝트를 빌드해야 할 수 있음). 콘텐츠 단추를 클릭하고 대화 상자의 형식을 일치시킬 ExpenseReport 형식으로 변경하여 수신 활동을 업데이트합니다.  그림 50과 같이 작업 속성을 업데이트하여 새 OperationName 및 ServiceContractName을 정의합니다. 

그림 50: 수신 위치 구성

이제 SendReply 작업은 수신 표시기를 반환하기 위해 값을 True로 설정할 수 있습니다.  더 복잡한 샘플에서는 응답을 결정하기 전에 워크플로의 모든 기능을 사용하여 데이터베이스에 정보를 저장하고, 보고서의 유효성을 검사하는 등의 작업을 수행할 수 있습니다. WCFTestClient 애플리케이션을 사용하여 서비스로 이동하여 작업 구성이 그림 51과 같이 노출된 서비스 계약을 정의하는 방법을 보여 줍니다. 

그림 51: 워크플로 서비스 생성된 계약

워크플로 서비스를 만든 후에는 WCF 서비스와 마찬가지로 워크플로 서비스를 호스트해야 합니다.  이전 예제에서는 호스팅에 가장 간단한 옵션인 IIS를 사용했습니다.  사용자 고유의 프로세스에서 워크플로 서비스를 호스트하려면 System.ServiceModel.Activities 어셈블리에 있는 WorkflowServiceHost 클래스를 사용하려고 합니다.  System.WorkflowServices 어셈블리에는 WF3 활동을 사용하여 빌드된 워크플로 서비스를 호스팅하는 데 사용되는 동일한 이름의 다른 클래스가 있습니다.  가장 간단한 자체 호스팅의 경우 그림 52와 같은 코드를 사용하여 서비스를 호스트하고 엔드포인트를 추가할 수 있습니다. 

WorkflowServiceHost 호스트 = new

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

    new Uri("https://localhost:9897/Services/Expense"));

호스트. AddDefaultEndpoints();

호스트. Description.Behaviors.Add(

    new ServiceMetadataBehavior { HttpGetEnabled = true });

호스트. Open();

Console.WriteLine("Host is open");

Console.ReadLine();

그림 52: 워크플로 서비스 자체 호스팅

Windows에서 관리되는 호스팅 옵션을 활용하려면 XAMLX 파일을 디렉터리에 복사하고 web.config 파일에서 동작, 바인딩, 엔드포인트 등에 필요한 구성 정보를 지정하여 IIS에서 서비스를 호스트할 수 있습니다.  실제로 앞에서 본 것처럼 선언적 워크플로 서비스 프로젝트 템플릿 중 하나를 사용하여 Visual Studio에서 새 프로젝트를 만들 때 배포할 준비가 된 XAMLX에 정의된 web.config 파일과 워크플로 서비스가 있는 프로젝트를 가져옵니다. 

WorkflowServiceHost 클래스를 사용하여 워크플로 서비스를 호스트하는 경우에도 워크플로 인스턴스를 구성하고 제어할 수 있어야 합니다.  서비스를 제어하기 위해 WorkflowControlEndpoint라는 새 표준 엔드포인트가 있습니다.  도우미 클래스인 WorkflowControlClient는 미리 빌드된 클라이언트 프록시를 제공합니다.  서비스에 WorkFlowControlEndpoint를 노출하고 WorkflowControlClient를 사용하여 워크플로의 새 인스턴스를 만들고 실행하거나 실행 중인 워크플로를 제어할 수 있습니다.  이 동일한 모델을 사용하여 로컬 컴퓨터에서 서비스를 관리하거나 원격 컴퓨터에서 서비스를 관리하는 데 사용할 수 있습니다.  그림 53에서는 서비스에서 워크플로 제어 엔드포인트를 노출하는 업데이트된 예제를 보여 줍니다. 

WorkflowServiceHost 호스트 = new

    WorkflowServiceHost("ExpenseReportService.xamlx",

    new Uri("https://localhost:9897/Services/Expense"));

호스트. AddDefaultEndpoints();

WorkflowControlEndpoint wce = new WorkflowControlEndpoint(

    new NetNamedPipeBinding(),

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

호스트. AddServiceEndpoint(wce);

호스트. Open();

그림 53: 워크플로 제어 엔드포인트

WorkflowInstance를 직접 만들지 않으므로 런타임에 확장을 추가하는 두 가지 옵션이 있습니다.  첫 번째는 WorkflowServiceHost에 일부 확장을 추가할 수 있으며 워크플로 인스턴스를 사용하여 올바르게 초기화된다는 것입니다.  두 번째는 구성을 사용하는 것입니다.  예를 들어 추적 시스템은 애플리케이션 구성 파일에서 완전히 정의할 수 있습니다.   구성 파일에서 추적 참가자 및 추적 프로필을 정의하고 동작을 사용하여 그림 54와 같이 특정 참가자를 서비스에 적용합니다. 

system.serviceModel><

    <추적>

      참가자><

        <add name="EtwTrackingParticipant"

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

             profileName="HealthMonitoring_Tracking_Profile"/>

      </참가자>

      프로필><

        <trackingProfile name="HealthMonitoring_Tracking_Profile">

          <워크플로 activityDefinitionId="*">

            <workflowInstanceQuery>

              <상태>

                <state name="Started"/>

                <state name="Completed"/>

              </states>

            </workflowInstanceQuery>

          </workflow>

        </trackingProfile>

      </profiles>

</tracking>

. . .

<동작>

      <serviceBehaviors>

        <동작 이름="SampleTrackingSample.SampleWFBehavior">

          <etwTracking profileName=" HealthMonitoring_Tracking_Profile" />

        </behavior>

      </serviceBehaviors>

</behaviors>

. . .

그림 54: 서비스 추적 구성

".svc-less" 서비스 또는 파일 확장명, 기본 바인딩 및 기본 엔드포인트가 필요하지 않은 서비스와 같이 워크플로에서 활용할 수 있는 WCF 서비스의 호스팅 및 구성에 대한 새로운 개선 사항이 많이 있습니다.  WCF4의 이러한 기능 및 기타 기능에 대한 자세한 내용은 추가 리소스 섹션에 있는 WCF의 도우미 문서를 참조하세요. 

Windows Workflow Foundation은 호스팅 개선 사항 외에도 WCF의 다른 기능을 활용합니다. 일부는 직접 및 다른 사람을 간접적으로.  예를 들어 메시지 상관 관계는 이제 주문 번호 또는 직원 ID와 같은 메시지의 데이터를 기반으로 메시지를 지정된 워크플로 인스턴스에 연결할 수 있는 서비스 빌드의 주요 기능입니다. 요청 및 응답 모두에 대한 다양한 메시징 활동에 상관 관계를 구성할 수 있으며 메시지의 여러 값에 대한 상관 관계를 지원합니다.  이러한 새로운 기능에 대한 자세한 내용은 WCF4의 컴패니언 백서에서 확인할 수 있습니다.   

결론

.NET Framework 4와 함께 제공되는 새로운 Windows Workflow Foundation 기술은 성능 및 개발자 생산성이 크게 향상되었으며 비즈니스 논리에 대한 선언적 애플리케이션 개발의 목표를 실현합니다.  프레임워크는 간단한 복잡한 작업 단위를 간소화하고 추적을 통해 상관 관계 메시지, 지속형 상태 및 풍부한 애플리케이션 가시성을 사용하여 복잡한 장기 실행 서비스를 빌드하기 위한 도구를 제공합니다. 

작성자 정보

Matt는 Pluralsight의 기술 직원으로 연결된 시스템 기술(WCF, WF, BizTalk, AppFabric 및 Azure Services Platform)에 중점을 둡니다. Matt는 Microsoft .NET 애플리케이션 디자인 및 개발을 전문으로 하는 독립 컨설턴트이기도 합니다. 작가인 Matt는 MSDN Magazine을 비롯한 여러 저널과 잡지에 기고했으며 현재 Foundations 칼럼의 워크플로 콘텐츠를 작성하고 있습니다. Matt는 Tech Ed와 같은 지역, 지역 및 국제 컨퍼런스에서 연설함으로써 기술에 대한 그의 사랑을 정기적으로 공유합니다. Microsoft는 연결된 시스템 기술에 대한 커뮤니티 기여로 Matt를 MVP로 인정했습니다. 그의 블로그를 통해 Matt에게 연락하십시오: http://www.pluralsight.com/community/blogs/matt/default.aspx.

추가 리소스