任务 1:使客户端与其宿主之间可以通信

在此任务中,将通过使用 WCF 协定使客户端与其宿主之间可以通信。主机应用程序将在客户端上调用一个操作,此操作旨在启动客户端工作流并以起始值将其初始化。

此外,在此任务中将学习如何通过工作流优先方法以编程方式创建 WCF 协定,而非通过新的接口另行创建一个 WCF 协定。

提示

使用 Visual Studio 工作流设计器创建或管理工作流服务时,该设计器有时会产生虚假的验证错误。如果可以成功地生成项目,请忽略验证错误。

过程

创建本地宿主通信协定

  1. 注意:使用 Visual Studio 工作流设计器创建或管理工作流服务时,该设计器有时会产生虚假的验证错误。如果可以成功地生成项目,请忽略验证错误。

  2. 打开 WorkflowServiceTutorial 解决方案。

  3. 在 WorkflowServiceClient 项目节点中打开该项目的工作流设计器。

  4. 将一个 ReceiveActivity 活动拖至设计器上,并将其放置在第一个 SendActivity 活动上方,以使 ReceiveActivity 活动将在工作流中首先执行。

    此活动将实现在本地宿主通信协定中定义的操作。

  5. 选择 ReceiveActivity 活动,并在**“属性”窗格中的“ServiceOperationInfo”下单击省略号,从而打开“选择操作”**复选框。

  6. 此时将使用工作流优先创作样式定义新的协定,因此请单击右上角的**“添加约定”**,并突出显示 Contract1。

  7. 在**“约定名称”**文本框中将协定命名为 ILocalHostContract。

  8. 突出显示 ILocalHostContract 下的第一个操作,并将其重命名为 StartClientWorkflow。

  9. 在**“参数”选项卡中单击“添加”符号,并添加一个名称为 initialValue、类型为 Int32、“方向”为 In 的新参数,然后单击“确定”**。

  10. 选择 ReceiveActivity 活动,并在**“属性”**窗格下将 initialValue 绑定到全局变量 inputValue。

  11. 此外,在**“属性”窗格下将 CanCreateInstance 属性设置为“True”**。

    现在已经以编程方式为您定义了一个名为 ILocalHostContract 的新协定,但仍必须更改一些文件,然后才能使用此协定与工作流服务交互。

  12. 在 WorkflowServiceClient 项目中打开 App.config。

  13. 添加以下配置代码:

          <services>
            <service name="WorkflowServiceClient.ClientWorkflow" behaviorConfiguration="WorkflowServiceClient.ClientWorkflowBehavior">
              <host>
                <baseAddresses>
                  <add baseAddress="https://localhost:8090/ClientWorkflow" />
                </baseAddresses>
              </host>
              <endpoint address=""
                        binding="wsHttpContextBinding"
                        contract="ILocalHostContract" />
              <endpoint address="mex"
                        binding="mexHttpBinding"
                        contract="IMetadataExchange" />
            </service>
          </services>
          <behaviors>
            <serviceBehaviors>
              <behavior name="WorkflowServiceClient.ClientWorkflowBehavior"  >
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
                <serviceCredentials>
                  <windowsAuthentication
                      allowAnonymousLogons="false"
                      includeWindowsGroups="true" />
                </serviceCredentials>
              </behavior>
            </serviceBehaviors>
          </behaviors>
    

    至此,已经为本地宿主通信协定定义了终结点地址。本教程中通篇使用的客户端也将实现此新协定。

设置本地宿主通信

  1. 打开 Program.cs,将以下 using 语句添加到文件顶部:

    using System.ServiceModel;
    using System.ServiceModel.Description;
    

    如果已创建 Visual Basic 解决方案,请右击 WorkflowServiceClient 项目节点并选择**“属性”。选择“引用”选项卡,并在“导入的命名空间”**下单击 System.ServiceModelSystem.ServiceModel.Description 的复选框。

  2. 由于将使用 WorkflowServiceHost 实现本地宿主通信并使客户端工作流操作调用与工作流服务一起运行,因此必须修改 Main 方法以使控制台主机应用程序能与工作流服务客户端进行通信。以下代码显示的是必须如何更改 Main 的实现才能便于进行本地宿主通信。

    Shared Sub Main()
        ' Create a WorkflowServiceHost object to listen for operation invocations.
        Using ConsoleHost As New WorkflowServiceHost(GetType(ClientWorkflow))
            Dim waitHandle As New AutoResetEvent(False)
    
            ' Add ChannelManagerService to the list of services used 
            ' by the WorkflowRuntime.
            Dim cms As New ChannelManagerService()
            ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.AddService(cms)
            AddHandler ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.WorkflowCompleted, AddressOf OnWorkflowCompleted
            AddHandler ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.WorkflowTerminated, AddressOf OnWorkflowTerminated
            AddHandler ConsoleHost.Closed, AddressOf OnConsoleClosed
    
            ' Call Open to start receiving messages.
            ConsoleHost.Open()
    
            ' After the client workflow is started, block until receiving waitHandle.Set is called
            ' so that the console application does not exit before the client workflow has completed
            ' and ConsoleHost.Close is called.
            waitHandle.WaitOne()
        End Using
    End Sub
    
    Shared Sub OnWorkflowCompleted(ByVal sender As Object, ByVal e As WorkflowCompletedEventArgs)
        Console.WriteLine("The client workflow has completed." + vbLf + "Press <Enter> to exit the client application.")
        Console.ReadLine()
        WaitHandle.Set()
    End Sub
    
    Shared Sub OnWorkflowTerminated(ByVal sender As Object, ByVal e As WorkflowTerminatedEventArgs)
        Console.WriteLine(e.Exception.Message)
        WaitHandle.Set()
    End Sub
    
    ' After the WorkflowServiceHost transitions to the closed state, allow the console 
    ' application to exit by signaling to the AutoResetEvent object that it is okay to unblock 
    ' the main thread.
    Shared Sub OnConsoleClosed(ByVal sender As Object, ByVal e As EventArgs)
        WaitHandle.Set()
    End Sub
    
    static void Main(string[] args)
    {
        // Create a WorkflowServiceHost object to listen for operation invocations.
        using (WorkflowServiceHost ConsoleHost = new WorkflowServiceHost(typeof(ClientWorkflow)))
        {
            AutoResetEvent waitHandle = new AutoResetEvent(false);
    
            // Add ChannelManagerService to the list of services used 
            // by the WorkflowRuntime.
            ChannelManagerService cms = new ChannelManagerService();
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.AddService(cms);
    
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
            { 
                Console.WriteLine("The client workflow has completed. \nPress <Enter> to exit the client application."); 
                Console.ReadLine();
                ConsoleHost.Close();
            };
    
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
            {
                Console.WriteLine(e.Exception.Message);
                waitHandle.Set();
            };
    
            // After the WorkflowServiceHost transitions to the closed state, allow the console 
            // application to exit by signaling to the AutoResetEvent object that it is okay to unblock 
            // the main thread.
            ConsoleHost.Closed += delegate(object sender, EventArgs e)
            {
                waitHandle.Set();
            };
    
            // Call Open to start receiving messages.
            ConsoleHost.Open();
    
            // After the client workflow is started, block until receiving waitHandle.Set is called
            // so that the console application does not exit before the client workflow has completed
            // and ConsoleHost.Close is called.
            waitHandle.WaitOne();
        }
    }
    
  3. 生成并运行客户端服务。

  4. 使用 SvcUtil.exe 生成与本地宿主通信操作进行交互所需的代理代码和配置代码。

    使用 SvcUtil.exe

    若要使用 SvcUtil.exe,请参见 ServiceModel Metadata Utility Tool

    生成代理代码和配置文件之后,通过以下操作将这些文件添加到 WorkflowServiceClient 项目:

    1. 导航到**“解决方案资源管理器”**窗格。
    2. 右击 WorkflowServiceClient 项目节点。
    3. 突出显示**“添加”并选择“现有项”**。
    4. 导航到 SvcUtil.exe 生成配置文件和代理代码文件的文件夹。
    5. 选择这些文件,然后单击**“确定”**。
  5. 生成代理代码文件和配置文件后,即修改 App.config 中的配置代码,以使主机应用程序能识别出使本地宿主通信更加便利的新客户端终结点。此外,如有必要,所使用的绑定配置信息可以和当前与工作流服务进行通信所使用的绑定配置信息相同。以下代码示例显示的是要添加到 App.Config 文件的代码。

        <bindings>
        ...
        </bindings>
        <client>
            <endpoint address="https://localhost:8080/ServerWorkflow" binding="wsHttpContextBinding"
                    bindingConfiguration="WSHttpContextBinding_IWorkflow1" contract="IWorkflow1"
                    name="WSHttpContextBinding_IWorkflow1">
                <identity>
                    <userPrincipalName value="someone@example.com" />
                </identity>
            </endpoint>
            <endpoint address="https://localhost:8090/ClientWorkflow" binding="wsHttpContextBinding"
                    bindingConfiguration="WSHttpContextBinding_IWorkflow1"
                    contract="ILocalHostContract" name="WSHttpContextBinding_ILocalHostContract">
                <identity>
                    <userPrincipalName value="someone@example.com" />
                </identity>
            </endpoint>
        </client>
        <services>
        ...
        </services>
        <behaviors>
        ...
        </behaviors>
    
  6. 既然已生成了用于在宿主与工作流服务客户端之间进行通信的代理客户端代码,那么就必须添加代码以便在客户端工作流中调用 LocalHostContractClient.StartClientWorkflow 操作,因此请打开 Program.cs(或 Module1.vb,如果已创建 Visual Basic 解决方案)并添加以下代码。

    Class Program
    
        Shared WaitHandle As New AutoResetEvent(False)
    
        Shared Sub Main()
            ' Create a WorkflowServiceHost object to listen for operation invocations.
            Using ConsoleHost As New WorkflowServiceHost(GetType(WorkflowServiceClient.ClientWorkflow))
                Dim waitHandle As New AutoResetEvent(False)
    
                ' Create a client that is used by the host application to communicate with the workflow service client.            Dim LCSClient As New WorkflowServiceClient.LocalHostContractClient("WSHttpContextBinding_ILocalHostContract")
    
                ' Add ChannelManagerService to the list of services used 
                ' by the WorkflowRuntime.
                Dim cms As New ChannelManagerService()
                ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.AddService(cms)
                AddHandler ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.WorkflowCompleted, AddressOf OnWorkflowCompleted
                AddHandler ConsoleHost.Description.Behaviors.Find(Of WorkflowRuntimeBehavior)().WorkflowRuntime.WorkflowTerminated, AddressOf OnWorkflowTerminated
                AddHandler ConsoleHost.Closed, AddressOf OnConsoleClosed
    
                ' Call Open to start receiving messages.
                 ConsoleHost.Open()
    
                Console.WriteLine("Client host service is ready.")            Console.WriteLine("Enter a starting value: ")            ' Read in a number from the user and use it as the initial number in the arithmetic operation calls.            LCSClient.StartClientWorkflow(Int32.Parse(Console.ReadLine()))
    
                ' After the client workflow is started, block until receiving waitHandle.Set is called
                ' so that the console application does not exit before the client workflow has completed
                ' and ConsoleHost.Close is called.
                waitHandle.WaitOne()
            End Using
        End Sub
    
        Shared Sub OnWorkflowCompleted(ByVal sender As Object, ByVal e As WorkflowCompletedEventArgs)
            Console.WriteLine("The client workflow has completed." + vbLf + "Press <Enter> to exit the client application.")
            Console.ReadLine()
            WaitHandle.Set()
        End Sub
    
        Shared Sub OnWorkflowTerminated(ByVal sender As Object, ByVal e As WorkflowTerminatedEventArgs)
            Console.WriteLine(e.Exception.Message)
            WaitHandle.Set()
        End Sub
    
        ' After the WorkflowServiceHost transitions to the closed state, allow the console 
        ' application to exit by signaling to the AutoResetEvent object that it is okay to unblock 
        ' the main thread.
        Shared Sub OnConsoleClosed(ByVal sender As Object, ByVal e As EventArgs)
            WaitHandle.Set()
        End Sub
    End Class
    
    static void Main(string[] args)
    {
        // Create a WorkflowServiceHost object to listen for operation invocations.
        using (WorkflowServiceHost ConsoleHost = new WorkflowServiceHost(typeof(ClientWorkflow)))
        {
            AutoResetEvent waitHandle = new AutoResetEvent(false);
    
            // Create a client that is used by the host application to communicate with the workflow service client.        LocalHostContractClient LCSClient = new LocalHostContractClient("WSHttpContextBinding_ILocalHostContract");
    
            // Add ChannelManagerService to the list of services used 
            // by the WorkflowRuntime.
            ChannelManagerService cms = new ChannelManagerService();
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.AddService(cms);
    
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) 
            { 
                Console.WriteLine("The client workflow has completed. \nPress <Enter> to exit the client application."); 
                Console.ReadLine();
                ConsoleHost.Close();
            };
    
            ConsoleHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
            {
                Console.WriteLine(e.Exception.Message);
                waitHandle.Set();
            };
    
            // After the WorkflowServiceHost transitions to the closed state, allow the console 
            // application to exit by signaling to the AutoResetEvent object that it is okay to unblock 
            // the main thread.
            ConsoleHost.Closed += delegate(object sender, EventArgs e)
            {
                waitHandle.Set();
            };
    
            // Call Open to start receiving messages.
            ConsoleHost.Open();
    
            Console.WriteLine("Client host service is ready.");        Console.WriteLine("Enter a starting value: ");        // Read in a number from the user and use it as the initial number in the arithmetic operation calls.        LCSClient.StartClientWorkflow(Int32.Parse(Console.ReadLine()));
    
            // After the client workflow is started, block until receiving waitHandle.Set is called
            // so that the console application does not exit before the client workflow has completed and 
            // ConsoleHost.Close is called.
            waitHandle.WaitOne();
        }
    }
    

    由于实现此操作的 ReceiveActivityCanCreateInstance 设置为 True,因此将创建新的工作流实例,并将运行工作流的其余部分,如同上一个练习中那样。此外,将通过命令提示符输入的值作为初始种子值,用于根据工作流服务调用其余的算术运算。

  7. 打开 Workflow1.cs(或 Workflow1.vb,如果已创建 Visual Basic 解决方案)并移除 sendActivity2_BeforeSend 方法实现中将数字 1 赋给 inputValue 变量的那行代码,以便用户在命令提示符下输入一个值后,将该值作为初始种子值,用于根据工作流服务调用所有操作。以下代码示例显示的是事件处理程序方法实现经过修订后的形式。

    Private Sub sendActivity2_BeforeSend(ByVal sender As System.Object, ByVal e As System.Workflow.Activities.SendActivityEventArgs)
        Console.WriteLine("The initial input value is {0}", inputValue)
    End Sub
    
    private void sendActivity2_BeforeSend(object sender, SendActivityEventArgs e)
    {
        Console.WriteLine("The initial input value is {0}", inputValue);
    }
    
  8. 生成并运行 WorkflowServiceTutorial 解决方案。从控制台主机应用程序中应该能看到类似于此的输出。

    Client host service is ready.
    Enter a starting value:
    7
    A service instance has successfully been created.
    The initial input value is 7
    The value after invoking the Add operation is 7
    The new input value is 2
    The value after invoking the Subtract operation is 5
    The new input value is 6
    The value after invoking the Multiply operation is 30
    The new input value is 3
    The value after invoking the Divide operation is 10
    The workflow service instance has successfully shutdown.
    The client workflow has completed.
    Press <Enter> to exit the client application.
    

另请参见

参考

任务 1:使客户端与其宿主之间可以通信

版权所有 (C) 2007 Microsoft Corporation。保留所有权利。