방법: 장기 실행 워크플로 만들기 및 실행
Windows WF(Workflow Foundation)의 핵심 기능 중 하나는 유휴 워크플로를 데이터베이스에 유지하고 언로드하는 런타임 기능입니다. 방법: 워크플로 실행의 단계에서는 콘솔 애플리케이션을 사용한 워크플로 호스팅의 기본 사항을 보여 주었습니다. 예제에서는 워크플로 시작 방법, 워크플로 수명 주기 처리기 및 책갈피 다시 시작 방법을 보여 줍니다. 워크플로 지속성을 효과적으로 보여 주기 위해서는 여러 워크플로 인스턴스의 시작 및 다시 시작을 지원하는 좀 더 복잡한 워크플로 호스트가 필요합니다. 자습서의 이 단계에서는 여러 워크플로 인스턴스의 시작 및 다시 시작과 워크플로 지속성을 지원하는 Windows Form 호스트 애플리케이션을 만드는 방법을 보여 주고, 이후 자습서 단계에서 설명하는 추적 및 버전 관리 등의 고급 기능에 대한 기본 사항을 제공합니다.
참고 항목
이 자습서 단계와 후속 단계에서는 방법: 워크플로 만들기의 세 가지 워크플로 형식을 모두 사용합니다.
지속성 데이터베이스를 만들려면
SQL Server Management Studio를 열고 로컬 서버(예: .\SQLEXPRESS)에 연결합니다. 로컬 서버에서 데이터베이스 노드를 마우스 오른쪽 단추로 클릭하고 새 데이터베이스를 선택합니다. 새 데이터베이스의 이름을 WF45GettingStartedTutorial로 지정한 다음 다른 값은 모두 그대로 두고 확인을 선택합니다.
참고 항목
데이터베이스를 만들기 전에 로컬 서버에 대한 데이터베이스 만들기 권한이 있는지 확인합니다.
파일 메뉴에서 열기, 파일을 차례로 선택합니다. 다음 폴더로 이동합니다. C:\Windows\Microsoft.NET\Framework\v4.0.30319\sql\en
다음 두 개의 파일을 선택하고 열기를 클릭합니다.
SqlWorkflowInstanceStoreLogic.sql
SqlWorkflowInstanceStoreSchema.sql
창 메뉴에서 SqlWorkflowInstanceStoreSchema.sql을 선택합니다. 사용 가능한 데이터베이스 드롭다운에서 WF45GettingStartedTutorial이 선택되어 있는지 확인하고 쿼리 메뉴에서 실행을 선택합니다.
창 메뉴에서 SqlWorkflowInstanceStoreLogic.sql을 선택합니다. 사용 가능한 데이터베이스 드롭다운에서 WF45GettingStartedTutorial이 선택되어 있는지 확인하고 쿼리 메뉴에서 실행을 선택합니다.
Warning
앞의 두 단계는 올바른 순서대로 수행해야 합니다. 쿼리를 순서에 맞지 않게 실행하면 오류가 발생하고 지속성 데이터베이스가 올바르게 구성되지 않습니다.
DurableInstancing 어셈블리에 대한 참조를 추가하려면
솔루션 탐색기에서 NumberGuessWorkflowHost를 마우스 오른쪽 단추로 클릭하고 참조 추가를 선택합니다.
참조 추가 목록에서 어셈블리를 선택하고 어셈블리 검색 상자에
DurableInstancing
을 입력합니다. 그러면 어셈블리가 필터링되므로 원하는 참조를 손쉽게 선택할 수 있습니다.검색 결과 목록에서 System.Activities.DurableInstancing 및 System.Runtime.DurableInstancing 옆의 확인란을 선택하고 확인을 클릭합니다.
워크플로 호스트 폼을 만들려면
솔루션 탐색기에서 NumberGuessWorkflowHost를 마우스 오른쪽 단추로 클릭하고 추가, 새 항목을 차례로 선택합니다.
설치된 템플릿 목록에서 Windows Form을 선택하고 이름 상자에
WorkflowHostForm
을 입력한 다음 추가를 클릭합니다.폼에 다음 속성을 구성합니다.
속성 값 FormBorderStyle FixedSingle MaximizeBox False 크기 400, 420 폼에 다음 컨트롤을 지정된 순서대로 추가하고 지시된 대로 속성을 구성합니다.
제어 속성: 값 버튼 이름: NewGame
위치: 13, 13
크기: 75, 23
텍스트: 새로운 게임레이블 위치: 94, 18
텍스트: 1부터 다음 숫자까지 숫자를 맞춰보세요.ComboBox 이름: NumberRange
DropDownStyle: DropDownList
항목: 10, 100, 1000
위치: 228, 12
크기: 143, 21레이블 위치: 13, 43
텍스트: 워크플로 형식ComboBox 이름: WorkflowType
DropDownStyle: DropDownList
항목: StateMachineNumberGuessWorkflow, FlowchartNumberGuessWorkflow, SequentialNumberGuessWorkflow
위치: 94, 40
크기: 277, 21레이블 이름: WorkflowVersion
위치: 13, 362
텍스트: 워크플로 버전GroupBox 위치: 13, 67
크기: 358, 287
텍스트: 게임참고 항목
다음 컨트롤을 추가할 때 GroupBox에 넣습니다.
제어 속성: 값 레이블 위치: 7, 20
텍스트: 워크플로 인스턴스 IDComboBox 이름: InstanceId
DropDownStyle: DropDownList
위치: 121, 17
크기: 227, 21레이블 위치: 7, 47
텍스트: GuessTextBox 이름: Guess
위치: 50, 44
크기: 65, 20버튼 이름: EnterGuess
위치: 121, 42
크기: 75, 23
텍스트: 추측 입력버튼 이름: QuitGame
위치: 274, 42
크기: 75, 23
텍스트: 종료TextBox 이름: 워크플로 상태
위치: 10, 73
여러 줄: True
Readonly = True
스크롤바: 세로
Size: 338, 208폼의 AcceptButton 속성을 EnterGuess로 설정합니다.
다음 예제에서는 완성된 폼을 보여 줍니다.
폼의 속성 및 도우미 메서드를 추가하려면
이 단원의 단계에서는 숫자 추측 워크플로의 실행 및 다시 시작을 지원하도록 폼 UI를 구성하는 속성 및 도우미 메서드를 폼 클래스에 추가합니다.
솔루션 탐색기에서 WorkflowHostForm을 마우스 오른쪽 단추로 클릭하고 코드 보기를 선택합니다.
다음
using
(또는Imports
) 문을 파일의 맨 위에 다른using
(또는Imports
) 문과 함께 추가합니다.Imports System.Activities Imports System.Activities.DurableInstancing Imports System.Data.SqlClient Imports System.IO Imports System.Windows.Forms
using System.Activities; using System.Activities.DurableInstancing; using System.Data.SqlClient; using System.IO; using System.Windows.Forms;
WorkflowHostForm 클래스에 다음 멤버 선언을 추가합니다.
Important
사용 가능한 가장 안전한 인증 흐름을 사용하는 것이 권장됩니다. Azure SQL에 연결하려는 경우, 권장되는 인증 방법은 Azure 리소스에 대한 관리 ID입니다.
Const connectionString = "Server=.\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI" Dim store As SqlWorkflowInstanceStore Dim workflowStarting As Boolean
const string connectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI"; SqlWorkflowInstanceStore store; bool workflowStarting;
참고 항목
연결 문자열이 다른 경우 해당 데이터베이스를 참조하도록
connectionString
을 업데이트하세요.WorkflowInstanceId
클래스에WorkflowFormHost
속성을 추가합니다.Public ReadOnly Property WorkflowInstanceId() As Guid Get If InstanceId.SelectedIndex = -1 Then Return Guid.Empty Else Return New Guid(InstanceId.SelectedItem.ToString()) End If End Get End Property
public Guid WorkflowInstanceId { get { return InstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)InstanceId.SelectedItem; } }
InstanceId
콤보 상자에 지속형 워크플로 인스턴스 ID의 목록이 표시되고WorkflowInstanceId
속성이 현재 선택된 워크플로를 반환합니다.폼의
Load
이벤트에 대한 처리기를 추가합니다. 처리기를 추가하려면 폼의 디자인 뷰로 전환하고 속성 창 맨 위에 있는 이벤트 아이콘을 클릭한 다음 Load를 두 번 클릭합니다.Private Sub WorkflowHostForm_Load(sender As Object, e As EventArgs) Handles Me.Load End Sub
private void WorkflowHostForm_Load(object sender, EventArgs e) { }
다음 코드를
WorkflowHostForm_Load
에 추가합니다.' Initialize the store and configure it so that it can be used for ' multiple WorkflowApplication instances. store = New SqlWorkflowInstanceStore(connectionString) WorkflowApplication.CreateDefaultInstanceOwner(store, Nothing, WorkflowIdentityFilter.Any) ' Set default ComboBox selections. NumberRange.SelectedIndex = 0 WorkflowType.SelectedIndex = 0 ListPersistedWorkflows()
// Initialize the store and configure it so that it can be used for // multiple WorkflowApplication instances. store = new SqlWorkflowInstanceStore(connectionString); WorkflowApplication.CreateDefaultInstanceOwner(store, null, WorkflowIdentityFilter.Any); // Set default ComboBox selections. NumberRange.SelectedIndex = 0; WorkflowType.SelectedIndex = 0; ListPersistedWorkflows();
폼이 로드되면
SqlWorkflowInstanceStore
가 구성되고 범위 및 워크플로 유형 콤보 상자가 기본값으로 설정되며 지속형 워크플로 인스턴스가InstanceId
콤보 상자에 추가됩니다.SelectedIndexChanged
에 대한InstanceId
처리기를 추가합니다. 처리기를 추가하려면 폼의 디자인 뷰로 전환하고InstanceId
콤보 상자를 선택한 다음 속성 창 맨 위에 있는 이벤트 아이콘을 클릭하고 SelectedIndexChanged를 두 번 클릭합니다.Private Sub InstanceId_SelectedIndexChanged(sender As Object, e As EventArgs) Handles InstanceId.SelectedIndexChanged End Sub
private void InstanceId_SelectedIndexChanged(object sender, EventArgs e) { }
다음 코드를
InstanceId_SelectedIndexChanged
에 추가합니다. 콤보 상자를 사용하여 워크플로를 선택할 때마다 이 처리기에 의해 상태 창이 업데이트됩니다.If InstanceId.SelectedIndex = -1 Then Return End If ' Clear the status window. WorkflowStatus.Clear() ' Get the workflow version and display it. ' If the workflow is just starting then this info will not ' be available in the persistence store so do not try and retrieve it. If Not workflowStarting Then Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) WorkflowVersion.Text = _ WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity) ' Unload the instance. instance.Abandon() End If
if (InstanceId.SelectedIndex == -1) { return; } // Clear the status window. WorkflowStatus.Clear(); // Get the workflow version and display it. // If the workflow is just starting then this info will not // be available in the persistence store so do not try and retrieve it. if (!workflowStarting) { WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store); WorkflowVersion.Text = WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity); // Unload the instance. instance.Abandon(); }
폼 클래스에 다음
ListPersistedWorkflows
메서드를 추가합니다.Private Sub ListPersistedWorkflows() Using localCon As New SqlConnection(connectionString) Dim localCmd As String = _ "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]" Dim cmd As SqlCommand = localCon.CreateCommand() cmd.CommandText = localCmd localCon.Open() Using reader As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection) While (reader.Read()) ' Get the InstanceId of the persisted Workflow. Dim id As Guid = Guid.Parse(reader(0).ToString()) InstanceId.Items.Add(id) End While End Using End Using End Sub
using (var localCon = new SqlConnection(connectionString)) { string localCmd = "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]"; SqlCommand cmd = localCon.CreateCommand(); cmd.CommandText = localCmd; localCon.Open(); using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while (reader.Read()) { // Get the InstanceId of the persisted Workflow. Guid id = Guid.Parse(reader[0].ToString()); InstanceId.Items.Add(id); } } }
ListPersistedWorkflows
는 인스턴스 저장소에서 지속형 워크플로 인스턴스를 쿼리하고 해당 인스턴스 ID를cboInstanceId
콤보 상자에 추가합니다.폼 클래스에 다음
UpdateStatus
메서드와 해당 대리자를 추가합니다. 이 메서드는 현재 실행 중인 워크플로의 상태로 폼의 상태 창을 업데이트합니다.Private Delegate Sub UpdateStatusDelegate(msg As String) Public Sub UpdateStatus(msg As String) ' We may be on a different thread so we need to ' make this call using BeginInvoke. If InvokeRequired Then BeginInvoke(New UpdateStatusDelegate(AddressOf UpdateStatus), msg) Else If Not msg.EndsWith(vbCrLf) Then msg = msg & vbCrLf End If WorkflowStatus.AppendText(msg) ' Ensure that the newly added status is visible. WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length WorkflowStatus.ScrollToCaret() End If End Sub
private delegate void UpdateStatusDelegate(string msg); public void UpdateStatus(string msg) { // We may be on a different thread so we need to // make this call using BeginInvoke. if (InvokeRequired) { BeginInvoke(new UpdateStatusDelegate(UpdateStatus), msg); } else { if (!msg.EndsWith("\r\n")) { msg += "\r\n"; } WorkflowStatus.AppendText(msg); WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length; WorkflowStatus.ScrollToCaret(); } }
폼 클래스에 다음
GameOver
메서드와 해당 대리자를 추가합니다. 워크플로가 완료되면 이 메서드는 InstanceId 콤보 상자에서 완료된 워크플로의 인스턴스 ID를 제거하여 폼 UI를 업데이트합니다.Private Delegate Sub GameOverDelegate() Private Sub GameOver() If InvokeRequired Then BeginInvoke(New GameOverDelegate(AddressOf GameOver)) Else ' Remove this instance from the InstanceId combo box. InstanceId.Items.Remove(InstanceId.SelectedItem) InstanceId.SelectedIndex = -1 End If End Sub
private delegate void GameOverDelegate(); private void GameOver() { if (InvokeRequired) { BeginInvoke(new GameOverDelegate(GameOver)); } else { // Remove this instance from the combo box. InstanceId.Items.Remove(InstanceId.SelectedItem); InstanceId.SelectedIndex = -1; } }
인스턴스 저장소, 워크플로 수명 주기 처리기 및 확장을 구성하려면
폼 클래스에
ConfigureWorkflowApplication
메서드를 추가합니다.Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication) End Sub
private void ConfigureWorkflowApplication(WorkflowApplication wfApp) { }
이 메서드는
WorkflowApplication
을 구성하고, 원하는 확장을 추가하고, 워크플로 수명 주기 이벤트에 대한 처리기를 추가합니다.ConfigureWorkflowApplication
에서SqlWorkflowInstanceStore
에WorkflowApplication
를 지정합니다.' Configure the persistence store. wfApp.InstanceStore = store
// Configure the persistence store. wfApp.InstanceStore = store;
그런 다음
StringWriter
인스턴스를 만들어Extensions
의WorkflowApplication
컬렉션에 추가합니다.StringWriter
는 확장에 추가될 경우 모든WriteLine
작업 출력을 캡처합니다. 워크플로가 유휴 상태가 되면WriteLine
출력이StringWriter
에서 추출되어 폼에 표시될 수 있습니다.' Add a StringWriter to the extensions. This captures the output ' from the WriteLine activities so we can display it in the form. Dim sw As New StringWriter() wfApp.Extensions.Add(sw)
// Add a StringWriter to the extensions. This captures the output // from the WriteLine activities so we can display it in the form. var sw = new StringWriter(); wfApp.Extensions.Add(sw);
Completed
이벤트에 대한 다음 처리기를 추가합니다. 워크플로가 성공적으로 완료되면 숫자를 추측한 횟수가 상태 창에 표시됩니다. 워크플로가 종료되면 종료 원인이 된 예외에 대한 정보가 표시됩니다. 처리기가 끝나면GameOver
메서드가 호출되어 워크플로 목록에서 완료된 워크플로를 제거합니다.wfApp.Completed = _ Sub(e As WorkflowApplicationCompletedEventArgs) If e.CompletionState = ActivityInstanceState.Faulted Then UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}") ElseIf e.CompletionState = ActivityInstanceState.Canceled Then UpdateStatus("Workflow Canceled.") Else Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns")) UpdateStatus($"Congratulations, you guessed the number in {turns} turns.") End If GameOver() End Sub
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { if (e.CompletionState == ActivityInstanceState.Faulted) { UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}"); } else if (e.CompletionState == ActivityInstanceState.Canceled) { UpdateStatus("Workflow Canceled."); } else { int turns = Convert.ToInt32(e.Outputs["Turns"]); UpdateStatus($"Congratulations, you guessed the number in {turns} turns."); } GameOver(); };
다음
Aborted
및OnUnhandledException
처리기를 추가합니다. 워크플로 인스턴스가 중단된 경우에는 해당 인스턴스가 종료되지 않으며 나중에 다시 시작할 수 있으므로GameOver
처리기에서Aborted
메서드가 호출되지 않습니다.wfApp.Aborted = _ Sub(e As WorkflowApplicationAbortedEventArgs) UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}") End Sub wfApp.OnUnhandledException = _ Function(e As WorkflowApplicationUnhandledExceptionEventArgs) UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}") GameOver() Return UnhandledExceptionAction.Terminate End Function
wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e) { UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}"); }; wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e) { UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}"); GameOver(); return UnhandledExceptionAction.Terminate; };
다음
PersistableIdle
처리기를 추가합니다. 이 처리기는 추가된StringWriter
확장을 검색하고WriteLine
활동에서 출력을 추출하여 상태 창에 표시합니다.wfApp.PersistableIdle = _ Function(e As WorkflowApplicationIdleEventArgs) ' Send the current WriteLine outputs to the status window. Dim writers = e.GetInstanceExtensions(Of StringWriter)() For Each writer In writers UpdateStatus(writer.ToString()) Next Return PersistableIdleAction.Unload End Function
wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e) { // Send the current WriteLine outputs to the status window. var writers = e.GetInstanceExtensions<StringWriter>(); foreach (var writer in writers) { UpdateStatus(writer.ToString()); } return PersistableIdleAction.Unload; };
PersistableIdleAction 열거형에는 세 개의 값(None, Persist 및 Unload)이 있습니다. Persist는 워크플로가 유지되도록 하지만 워크플로가 언로드되도록 하지는 않습니다. Unload는 워크플로가 유지되고 언로드되도록 합니다.
다음 예제는 전체
ConfigureWorkflowApplication
메서드입니다.Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication) ' Configure the persistence store. wfApp.InstanceStore = store ' Add a StringWriter to the extensions. This captures the output ' from the WriteLine activities so we can display it in the form. Dim sw As New StringWriter() wfApp.Extensions.Add(sw) wfApp.Completed = _ Sub(e As WorkflowApplicationCompletedEventArgs) If e.CompletionState = ActivityInstanceState.Faulted Then UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}") ElseIf e.CompletionState = ActivityInstanceState.Canceled Then UpdateStatus("Workflow Canceled.") Else Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns")) UpdateStatus($"Congratulations, you guessed the number in {turns} turns.") End If GameOver() End Sub wfApp.Aborted = _ Sub(e As WorkflowApplicationAbortedEventArgs) UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}") End Sub wfApp.OnUnhandledException = _ Function(e As WorkflowApplicationUnhandledExceptionEventArgs) UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}") GameOver() Return UnhandledExceptionAction.Terminate End Function wfApp.PersistableIdle = _ Function(e As WorkflowApplicationIdleEventArgs) ' Send the current WriteLine outputs to the status window. Dim writers = e.GetInstanceExtensions(Of StringWriter)() For Each writer In writers UpdateStatus(writer.ToString()) Next Return PersistableIdleAction.Unload End Function End Sub
private void ConfigureWorkflowApplication(WorkflowApplication wfApp) { // Configure the persistence store. wfApp.InstanceStore = store; // Add a StringWriter to the extensions. This captures the output // from the WriteLine activities so we can display it in the form. var sw = new StringWriter(); wfApp.Extensions.Add(sw); wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { if (e.CompletionState == ActivityInstanceState.Faulted) { UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}"); } else if (e.CompletionState == ActivityInstanceState.Canceled) { UpdateStatus("Workflow Canceled."); } else { int turns = Convert.ToInt32(e.Outputs["Turns"]); UpdateStatus($"Congratulations, you guessed the number in {turns} turns."); } GameOver(); }; wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e) { UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}"); }; wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e) { UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}"); GameOver(); return UnhandledExceptionAction.Terminate; }; wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e) { // Send the current WriteLine outputs to the status window. var writers = e.GetInstanceExtensions<StringWriter>(); foreach (var writer in writers) { UpdateStatus(writer.ToString()); } return PersistableIdleAction.Unload; }; }
여러 워크플로 유형을 시작 및 다시 시작할 수 있도록 설정하려면
워크플로 인스턴스를 다시 시작하려면 호스트에서 워크플로 정의를 제공해야 합니다. 이 자습서에는 세 가지 워크플로 유형이 있으며 이후 자습서 단계에서는 이러한 유형의 여러 버전을 소개합니다. 호스트 애플리케이션에서는 WorkflowIdentity
를 통해 식별 정보를 지속형 워크플로 인스턴스에 연결할 수 있습니다. 이 단원의 단계에서는 유틸리티 클래스를 만들어 지속형 워크플로 인스턴스의 워크플로 ID를 해당 워크플로 정의에 쉽게 매핑할 수 있도록 하는 방법을 보여 줍니다. WorkflowIdentity
및 버전 관리에 대한 자세한 내용은 WorkflowIdentity 및 버전 관리 사용을 참조하세요.
솔루션 탐색기에서 NumberGuessWorkflowHost를 마우스 오른쪽 단추로 클릭하고 추가, 클래스를 차례로 선택합니다. 이름 상자에
WorkflowVersionMap
를 입력하고 추가를 클릭합니다.다음
using
또는Imports
문을 파일의 맨 위에 다른using
또는Imports
문과 함께 추가합니다.Imports System.Activities Imports NumberGuessWorkflowActivities
using System.Activities; using NumberGuessWorkflowActivities;
WorkflowVersionMap
클래스 선언을 다음 선언으로 바꿉니다.Public Module WorkflowVersionMap Dim map As Dictionary(Of WorkflowIdentity, Activity) ' Current version identities. Public StateMachineNumberGuessIdentity As WorkflowIdentity Public FlowchartNumberGuessIdentity As WorkflowIdentity Public SequentialNumberGuessIdentity As WorkflowIdentity Sub New() map = New Dictionary(Of WorkflowIdentity, Activity) ' Add the current workflow version identities. StateMachineNumberGuessIdentity = New WorkflowIdentity With { .Name = "StateMachineNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } FlowchartNumberGuessIdentity = New WorkflowIdentity With { .Name = "FlowchartNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } SequentialNumberGuessIdentity = New WorkflowIdentity With { .Name = "SequentialNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } map.Add(StateMachineNumberGuessIdentity, New StateMachineNumberGuessWorkflow()) map.Add(FlowchartNumberGuessIdentity, New FlowchartNumberGuessWorkflow()) map.Add(SequentialNumberGuessIdentity, New SequentialNumberGuessWorkflow()) End Sub Public Function GetWorkflowDefinition(identity As WorkflowIdentity) As Activity Return map(identity) End Function Public Function GetIdentityDescription(identity As WorkflowIdentity) As String Return identity.ToString() End Function End Module
public static class WorkflowVersionMap { static Dictionary<WorkflowIdentity, Activity> map; // Current version identities. static public WorkflowIdentity StateMachineNumberGuessIdentity; static public WorkflowIdentity FlowchartNumberGuessIdentity; static public WorkflowIdentity SequentialNumberGuessIdentity; static WorkflowVersionMap() { map = new Dictionary<WorkflowIdentity, Activity>(); // Add the current workflow version identities. StateMachineNumberGuessIdentity = new WorkflowIdentity { Name = "StateMachineNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; FlowchartNumberGuessIdentity = new WorkflowIdentity { Name = "FlowchartNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; SequentialNumberGuessIdentity = new WorkflowIdentity { Name = "SequentialNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; map.Add(StateMachineNumberGuessIdentity, new StateMachineNumberGuessWorkflow()); map.Add(FlowchartNumberGuessIdentity, new FlowchartNumberGuessWorkflow()); map.Add(SequentialNumberGuessIdentity, new SequentialNumberGuessWorkflow()); } public static Activity GetWorkflowDefinition(WorkflowIdentity identity) { return map[identity]; } public static string GetIdentityDescription(WorkflowIdentity identity) { return identity.ToString(); } }
WorkflowVersionMap
에는 이 자습서에서 사용하는 세 개의 워크플로 정의에 매핑되며 다음 단원에서 워크플로가 시작 및 다시 시작될 때 사용되는 세 개의 워크플로 ID가 포함되어 있습니다.
새 워크플로를 시작하려면
Click
에 대한NewGame
처리기를 추가합니다. 처리기를 추가하려면 폼의 디자인 뷰로 전환하고NewGame
을 두 번 클릭합니다.NewGame_Click
처리기가 추가되고 폼의 뷰가 코드 뷰로 전환됩니다. 이 단추를 클릭할 때마다 새 워크플로가 시작됩니다.Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click End Sub
private void NewGame_Click(object sender, EventArgs e) { }
click 처리기에 다음 코드를 추가합니다. 이 코드는 인수 이름으로 키가 지정된 워크플로 입력 인수 사전을 만듭니다. 이 사전에는 범위 콤보 상자에서 검색된 임의 생성 숫자의 범위를 포함하는 항목이 하나 있습니다.
Dim inputs As New Dictionary(Of String, Object)() inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))
var inputs = new Dictionary<string, object>(); inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));
다음으로는 워크플로를 시작하는 다음 코드를 추가합니다. 선택된 워크플로 유형에 해당하는
WorkflowIdentity
및 워크플로 정의는WorkflowVersionMap
도우미 클래스를 사용하여 검색됩니다. 그런 다음 워크플로 정의,WorkflowApplication
및 입력 인수 사전을 사용하여 새WorkflowIdentity
인스턴스가 만들어집니다.Dim identity As WorkflowIdentity = Nothing Select Case WorkflowType.SelectedItem.ToString() Case "SequentialNumberGuessWorkflow" identity = WorkflowVersionMap.SequentialNumberGuessIdentity Case "StateMachineNumberGuessWorkflow" identity = WorkflowVersionMap.StateMachineNumberGuessIdentity Case "FlowchartNumberGuessWorkflow" identity = WorkflowVersionMap.FlowchartNumberGuessIdentity End Select Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity) Dim wfApp = New WorkflowApplication(wf, inputs, identity)
WorkflowIdentity identity = null; switch (WorkflowType.SelectedItem.ToString()) { case "SequentialNumberGuessWorkflow": identity = WorkflowVersionMap.SequentialNumberGuessIdentity; break; case "StateMachineNumberGuessWorkflow": identity = WorkflowVersionMap.StateMachineNumberGuessIdentity; break; case "FlowchartNumberGuessWorkflow": identity = WorkflowVersionMap.FlowchartNumberGuessIdentity; break; }; Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity); WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);
그런 다음 워크플로 목록에 워크플로를 추가하고 폼에 워크플로 버전 정보를 표시하는 다음 코드를 추가합니다.
' Add the workflow to the list and display the version information. workflowStarting = True InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id) WorkflowVersion.Text = identity.ToString() workflowStarting = False
// Add the workflow to the list and display the version information. workflowStarting = true; InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id); WorkflowVersion.Text = identity.ToString(); workflowStarting = false;
ConfigureWorkflowApplication
을 호출하여 이WorkflowApplication
인스턴스의 인스턴스 저장소, 확장명 및 워크플로 수명 주기 처리기를 구성합니다.' Configure the instance store, extensions, and ' workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp)
// Configure the instance store, extensions, and // workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp);
마지막으로
Run
를 호출합니다.' Start the workflow. wfApp.Run()
// Start the workflow. wfApp.Run();
다음 예제는 전체
NewGame_Click
처리기입니다.Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click ' Start a new workflow. Dim inputs As New Dictionary(Of String, Object)() inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem)) Dim identity As WorkflowIdentity = Nothing Select Case WorkflowType.SelectedItem.ToString() Case "SequentialNumberGuessWorkflow" identity = WorkflowVersionMap.SequentialNumberGuessIdentity Case "StateMachineNumberGuessWorkflow" identity = WorkflowVersionMap.StateMachineNumberGuessIdentity Case "FlowchartNumberGuessWorkflow" identity = WorkflowVersionMap.FlowchartNumberGuessIdentity End Select Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity) Dim wfApp = New WorkflowApplication(wf, inputs, identity) ' Add the workflow to the list and display the version information. workflowStarting = True InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id) WorkflowVersion.Text = identity.ToString() workflowStarting = False ' Configure the instance store, extensions, and ' workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp) ' Start the workflow. wfApp.Run() End Sub
private void NewGame_Click(object sender, EventArgs e) { var inputs = new Dictionary<string, object>(); inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem)); WorkflowIdentity identity = null; switch (WorkflowType.SelectedItem.ToString()) { case "SequentialNumberGuessWorkflow": identity = WorkflowVersionMap.SequentialNumberGuessIdentity; break; case "StateMachineNumberGuessWorkflow": identity = WorkflowVersionMap.StateMachineNumberGuessIdentity; break; case "FlowchartNumberGuessWorkflow": identity = WorkflowVersionMap.FlowchartNumberGuessIdentity; break; }; Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity); var wfApp = new WorkflowApplication(wf, inputs, identity); // Add the workflow to the list and display the version information. workflowStarting = true; InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id); WorkflowVersion.Text = identity.ToString(); workflowStarting = false; // Configure the instance store, extensions, and // workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp); // Start the workflow. wfApp.Run(); }
워크플로를 다시 시작하려면
Click
에 대한EnterGuess
처리기를 추가합니다. 처리기를 추가하려면 폼의 디자인 뷰로 전환하고EnterGuess
을 두 번 클릭합니다. 이 단추를 클릭할 때마다 워크플로가 다시 시작됩니다.Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click End Sub
private void EnterGuess_Click(object sender, EventArgs e) { }
워크플로 목록에 워크플로가 선택되어 있고 사용자의 추측이 올바른지 확인하는 다음 코드를 추가합니다.
If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim userGuess As Integer If Not Int32.TryParse(Guess.Text, userGuess) Then MessageBox.Show("Please enter an integer.") Guess.SelectAll() Guess.Focus() Return End If
if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } int guess; if (!Int32.TryParse(Guess.Text, out guess)) { MessageBox.Show("Please enter an integer."); Guess.SelectAll(); Guess.Focus(); return; }
그런 다음 지속형 워크플로 인스턴스의
WorkflowApplicationInstance
를 검색합니다.WorkflowApplicationInstance
는 워크플로 정의와 아직 연결되지 않은 지속형 워크플로 인스턴스를 나타냅니다.DefinitionIdentity
의WorkflowApplicationInstance
에는 지속형 워크플로 인스턴스의WorkflowIdentity
가 포함되어 있습니다. 이 자습서에서는WorkflowVersionMap
를 올바른 워크플로 정의에 매핑하기 위해WorkflowIdentity
유틸리티 클래스가 사용됩니다. 워크플로 정의가 검색되면 올바른 워크플로 정의를 사용하여WorkflowApplication
이 만들어집니다.Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = _ WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity)
WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
WorkflowApplication
이 만들어진 후ConfigureWorkflowApplication
을 호출하여 인스턴스 저장소, 워크플로 수명 주기 처리기 및 확장을 구성합니다. 이러한 단계는 새WorkflowApplication
이 만들어질 때마다 수행되어야 하며, 워크플로 인스턴스가WorkflowApplication
에 로드되기 전에 수행되어야 합니다. 워크플로가 로드된 후에는 사용자의 추측과 함께 워크플로가 다시 시작됩니다.' Configure the extensions and lifecycle handlers. ' Do this before the instance is loaded. Once the instance is ' loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Resume the workflow. wfApp.ResumeBookmark("EnterGuess", userGuess)
// Configure the extensions and lifecycle handlers. // Do this before the instance is loaded. Once the instance is // loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Resume the workflow. wfApp.ResumeBookmark("EnterGuess", guess);
마지막으로, Guess 텍스트 상자를 지우고 다른 값을 추측할 수 있도록 폼을 준비합니다.
' Clear the Guess textbox. Guess.Clear() Guess.Focus()
// Clear the Guess textbox. Guess.Clear(); Guess.Focus();
다음 예제는 전체
EnterGuess_Click
처리기입니다.Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim userGuess As Integer If Not Int32.TryParse(Guess.Text, userGuess) Then MessageBox.Show("Please enter an integer.") Guess.SelectAll() Guess.Focus() Return End If Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = _ WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity) ' Configure the extensions and lifecycle handlers. ' Do this before the instance is loaded. Once the instance is ' loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Resume the workflow. wfApp.ResumeBookmark("EnterGuess", userGuess) ' Clear the Guess textbox. Guess.Clear() Guess.Focus() End Sub
private void EnterGuess_Click(object sender, EventArgs e) { if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } int guess; if (!Int32.TryParse(Guess.Text, out guess)) { MessageBox.Show("Please enter an integer."); Guess.SelectAll(); Guess.Focus(); return; } WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity); // Configure the extensions and lifecycle handlers. // Do this before the instance is loaded. Once the instance is // loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Resume the workflow. wfApp.ResumeBookmark("EnterGuess", guess); // Clear the Guess textbox. Guess.Clear(); Guess.Focus(); }
워크플로를 종료하려면
Click
에 대한QuitGame
처리기를 추가합니다. 처리기를 추가하려면 폼의 디자인 뷰로 전환하고QuitGame
을 두 번 클릭합니다. 이 단추를 클릭할 때마다 현재 선택된 워크플로가 종료됩니다.Private Sub QuitGame_Click(sender As Object, e As EventArgs) Handles QuitGame.Click End Sub
private void QuitGame_Click(object sender, EventArgs e) { }
QuitGame_Click
처리기에 다음 코드를 추가합니다. 이 코드는 먼저 워크플로 목록에서 워크플로가 선택되어 있는지 확인합니다. 다음에는 지속형 인스턴스를WorkflowApplicationInstance
에 로드하고,DefinitionIdentity
를 사용하여 올바른 워크플로 정의를 결정한 다음,WorkflowApplication
을 초기화합니다. 그런 다음ConfigureWorkflowApplication
을 호출하여 확장 및 워크플로 수명 주기 처리기가 구성됩니다. 구성이 완료되면WorkflowApplication
이 로드되고Terminate
가 호출됩니다.If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition. Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity) ' Configure the extensions and lifecycle handlers. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Terminate the workflow. wfApp.Terminate("User resigns.")
if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity); // Configure the extensions and lifecycle handlers ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Terminate the workflow. wfApp.Terminate("User resigns.");
애플리케이션을 빌드하고 실행하려면
솔루션 탐색기 에서 Program.cs(또는 Module1.vb) 를 두 번 클릭하여 코드를 표시합니다.
다음
using
(또는Imports
) 문을 파일의 맨 위에 다른using
(또는Imports
) 문과 함께 추가합니다.Imports System.Windows.Forms
using System.Windows.Forms;
방법: 워크플로 실행에서 기존 워크플로 호스팅 코드를 제거하거나 주석으로 처리하고 다음 코드로 바꿉니다.
Sub Main() Application.EnableVisualStyles() Application.Run(New WorkflowHostForm()) End Sub
static void Main(string[] args) { Application.EnableVisualStyles(); Application.Run(new WorkflowHostForm()); }
솔루션 탐색기에서 NumberGuessWorkflowHost를 마우스 오른쪽 단추로 클릭하고 속성을 선택합니다. 애플리케이션 탭에서 출력 형식에 Windows 애플리케이션을 지정합니다. 이 단계는 선택적이지만 이 단계를 수행하지 않으면 폼뿐만 아니라 콘솔 창도 표시됩니다.
Ctrl+Shift+B를 눌러 애플리케이션을 빌드합니다.
NumberGuessWorkflowHost가 시작 애플리케이션으로 설정되어 있는지 확인하고 Ctrl+F5를 눌러 애플리케이션을 시작합니다.
추측 게임의 범위와 시작할 워크플로 유형을 선택하고 New Game을 클릭합니다. Guess 상자에 추측 값을 입력하고 Go를 클릭하여 추측 값을 제출합니다.
WriteLine
활동의 출력이 폼에 표시됩니다.다른 워크플로 유형 및 숫자 범위를 사용하여 여러 워크플로를 시작하고 몇 개의 추측 값을 입력한 다음 Workflow Instance Id 목록에서 워크플로 인스턴스 ID를 선택하여 워크플로 간을 전환합니다.
새 워크플로로 전환할 경우 이전 추측 값 및 워크플로 진행률이 상태 창에 표시되지 않습니다. 상태를 사용할 수 없는 것은 상태가 캡처 및 저장되지 않았기 때문입니다. 자습서의 다음 단계인 방법: 사용자 지정 추적 참가자 만들기에서는 이 정보를 저장하는 사용자 지정 추적 참가자를 만듭니다.
.NET