使用 .NET Framework 4.0 的 WCF Service Discovery

概述

Windows Communication Foundation 4 中包含一种支持服务发现的新功能。通过服务发现,您可以使用临时发现功能在同一子网中定位服务,或使用代理建立与服务器的连接,而不用顾虑服务器在何处。在本实验中,将创建一个简单的聊天应用程序,使用这两种方法来了解可用的服务。

目标

在本次动手实验中,您将学习:

•              如何使服务可在临时模式中被发现

•              如何使用 DiscoveryClient 搜索服务

•              如何实现并使用 DiscoveryProxy

•              如何接桥旧的发现机制

               

系统要求

您必须拥有以下工具才能完成本实验:

•             Microsoft Visual Studio 2010 Beta 2

•             .NET Framework 4

               

安装

使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行。

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.            如果之前没有执行,运行 Training Kit 的 Configuration Wizard。为此,运行位于 %TrainingKitInstallationFolder%\Labs\WCFServiceDiscovery\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意: 为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。

               

练习

本次动手实验由以下练习组成:

1.            临时发现

2.            元数据扩展

3.            公告

4.            发现代理

5.            旧式代理

               

初始材料

这次动手实验包括以下初始材料。

•              Visual Studio 解决方案。您将发现可以用作练习起点的 C# 和 Visual Basic Visual Studio 解决方案,具体解决方案取决于练习。

   如果我束手无策了怎么办?

该动手实验中的源代码包括一个最终文件夹,如果完成了每个练习的每一步,您可以在该文件夹中找到应该获取的 Visual Studio 解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。

               

下一步

练习 1:临时发现

 

               

练习 1:临时发现

在本实验中,将使用 System.ServiceModel.Discovery(一种 WS-Discovery 协议实现)向 DiscoveryChat 程序添加临时发现机制。为了使服务能够在临时模式下被发现,服务需要响应传入的探测消息。临时发现机制会显示这些探测消息通过 UDP 多播从已知端口传入。

任务 1 –为 DiscoveryChat 应用程序配置服务发现

DiscoveryChat 是一个聊天应用程序,可使用临时发现或通过代理的托管发现自动发现网络中的用户。

 

图 1

两个没有启用发现的简易聊天窗口

您需要做的第一件事是在简易的聊天应用程序中启用发现。

1.            从Start | All Programs | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

    注意

Visual Studio 2010 必须在已提升的模式下运行。为此,右键单击 Visual Studio 2010 图标并选择 Run as Administrator。

2.            打开 %TrainingKitInstallationfolder%\Labs\WCFServiceDiscovery\Source\Ex1-AdHocDiscovery\Begin 文件夹下的初始解决方案 Begin.sln,选择您希望使用的语言(C# 或 VB)。

3.            要启用发现,需要在要发现的服务上添加服务行为。打开 DiscoveryChat 项目中的 app.config 配置文件。

4.            在 <serviceBehaviors> 元素中添加一个新的服务行为 DiscoveryBehavior。

XML

<behaviors>

  <serviceBehaviors>

    <behavior name="DiscoveryBehavior">

      <serviceDiscovery />

    </behavior>

  </serviceBehaviors>

</behaviors>

5.            添加 behaviorConfiguration 属性来引用刚创建的 DiscoveryBehavior,修改服务描述。

XML

<services>

<service name="Microsoft.Samples.Discovery.ChatService"

           behaviorConfiguration="DiscoveryBehavior">

    <endpoint

      address=""

      binding="basicHttpBinding"

      contract="ISimpleChatService"/>

  </service>

</services>

6.            接下来,需要添加一个 UDP 发现端点。将在此端点上对发现探测消息进行处理。

    探测消息

探测消息是一种 WS-Discovery 消息,由客户端用来按服务类型搜索网络中的服务。有关探测消息的详细信息,请参见 WS-Discovery 规范的第 5.2 节。

XML

<services>

  <service name="Microsoft.Samples.Discovery.ChatService "

           behaviorConfiguration="DiscoveryBehavior">

    <endpoint

      address=""

      binding="basicHttpBinding"

      contract="ISimpleChatService"/>

    <endpoint

      name="udpDiscoveryEpt"

      kind="udpDiscoveryEndpoint"/>

  </service>

</services>

    使服务可被发现

DiscoveryBehavior 和 UDP 端点(由属性 kind = “udpDiscoveryEndpoint”指定)的结合使服务可被发现。现在,发现行为将会响应与协定类型匹配的传入 UDP 探测消息。

               

任务 2 –启用临时发现

在本任务中,您将添加代码来使用服务发现异步搜索同一子网上的其他聊天用户。

1.            首先,您需要引用新的发现集合。右键单击 DiscoveryChat 项目,选择 Add Reference。

a.            使用 .NET 选项卡,添加对 System.ServiceModel.Discovery 的引用。

 

图 2

System.ServiceModel.Discovery 集合引用

2.            打开 DiscoveryChat 项目中的 SimpleChat.cs (C#) 或 SimpleChat.vb (Visual Basic) 文件。选择文件并按 F7 可以打开代码视图。

3.            要执行临时发现,需要添加一个 DiscoveryClient 类型的成员字段。为此,执行以下步骤:

a.            在类的顶部,添加以下 System.ServiceModel.Discovery 命名空间指令

C#

using System.ServiceModel.Discovery;

Visual Basic

Imports System.ServiceModel.Discovery

b.            在 SimpleChat 类中添加以下成员变量

C#

public partial class SimpleChat :Form

{

    private DiscoveryClient discoveryClient;

Visual Basic

Public Partial Class SimpleChat

Inherits Form

    Private WithEvents discoveryClient As DiscoveryClient

4.            如果 DiscoveryClient 找到了一个服务,它提供一个包含该服务元数据的 EndpointDiscoveryMetadata 实例。您将使用这一元数据在一个列表框中显示可用聊天用户的列表。为此,将 PopulateUserList 方法添加到 SimpleChat 类中。

注意: 由于 DiscoveryChat 应用程序承载着一个服务,因此 DiscoveryClient 将找到该服务以及子网中的其他服务。

(代码片段 – 服务发现 – PopulateUserList 方法 CSharp)

C#

private void PopulateUserList(EndpointDiscoveryMetadata endpointDiscoveryMetadata)

{

    if (!this.EndpointIsSelf(endpointDiscoveryMetadata.Address.Uri))

    {

        this.AddUser(new PeerUser(endpointDiscoveryMetadata.Address));

        this.EnableUsersBox();

    }

}

(代码片段 – 服务发现 – PopulateUserList 方法 VB)

Visual Basic

Private Sub PopulateUserList(ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata)

    If Not Me.EndpointIsSelf(endpointDiscoveryMetadata.Address.Uri) Then

        Me.AddUser(New PeerUser(endpointDiscoveryMetadata.Address))

        Me.EnableUsersBox()

    End If

End Sub

注意:实验代码中提供了 PeerUser 类。它用于跟踪其他用户的聊天对话。AddUser 方法将用户添加到列表框中。

5.            找到 AdHocDiscovery 方法存根,并添加以下代码。此代码将初始化发现,查找实现 ISimpleChatService 协定的服务,找到服务后,将其添加到可用服务列表中。

(代码片段 – 服务发现 – AdHocDiscovery 方法 CSharp)

C#

private void AdHocDiscovery()

{

    this.discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());

    this.discoveryClient.FindProgressChanged +=

        new EventHandler<FindProgressChangedEventArgs>(this.OnFindProgressChanged);

    this.discoveryClient.FindCompleted +=

        new EventHandler<FindCompletedEventArgs>(this.OnFindCompleted);

    // Setup the form for discovery

    this..ShowDiscoveryInProgress(true);

    // Do async discovery

    this..discoveryClient.FindAsync(new FindCriteria(typeof(ISimpleChatService)));

}

(代码片段 – 服务发现 – AdHocDiscovery 方法 VB)

Visual Basic

Private Sub AdHocDiscovery()

    Me.discoveryClient = New DiscoveryClient(New UdpDiscoveryEndpoint())

    ‘ Setup the form for discovery

    Me..ShowDiscoveryInProgress(True)

    ‘ Do async discovery

    Me.discoveryClient.FindAsync(New FindCriteria(GetType(ISimpleChatService)))

End Sub

    使用 DiscoveryClient

可分别使用 Find 或 FindAsync 方法来同步或异步启动发现过程。在大多数情况下,将使用 FindAsync 方法(如前面的代码所示)。

在发现过程中,可以注册一些事件处理程序。例如,只要有服务端点被发现,就会调用 FindProgressChanged。如果希望在完成发现进程后收到通知,应使用 FindCompleted 处理程序。

6.           按如下显示的代码实现 OnFindProgressChanged 和 OnFindCompleted 处理程序,将其粘贴到 AdHocDiscovery() 方法实现之下。

(代码片段 – 服务发现 – DiscoveryClient 事件处理程序 CSharp)

C#

private void OnFindProgressChanged(object sender, FindProgressChangedEventArgs e)

{

    this.PopulateUserList(e.EndpointDiscoveryMetadata);

}

private void OnFindCompleted(object sender, FindCompletedEventArgs e)

{

    if (e.Cancelled)

    {

        this.ShowStatus("Discovery cancelled");

    }

    else if (e.Error != null)

    {

        this..discoveryClient.Close();

        MessageBox..Show(

            e.Error.Message,

            this..Text,

            MessageBoxButtons.OK,

            MessageBoxIcon..Information,

            MessageBoxDefaultButton..Button1,

            (MessageBoxOptions)0);

    }

    else

    {

        if (this.discoveryClient.InnerChannel.State == CommunicationState.Opened)

        {

            this..discoveryClient.Close();

        }

    }

    this.discoveryClient = null;

    this.ShowDiscoveryInProgress(false);

}

(代码片段 – 服务发现 – DiscoveryClient 事件处理程序 VB)

Visual Basic

Private Sub OnFindProgressChanged(ByVal sender As Object,

                                      ByVal e As FindProgressChangedEventArgs) Handles discoveryClient.FindProgressChanged

    Me.PopulateUserList(e.EndpointDiscoveryMetadata)

End Sub

Private Sub OnFindCompleted(ByVal sender As Object,

                            ByVal e As FindCompletedEventArgs) Handles discoveryClient.FindCompleted

    If e.Cancelled Then

        Me.ShowStatus("Discovery cancelled")

    ElseIf e.Error IsNot Nothing Then

        Me.discoveryClient.Close()

        MsgBox(e.Error.Message, MsgBoxStyle.Information, Me.Text)

    Else

        If Me.discoveryClient.InnerChannel.State = CommunicationState.Opened Then Me.discoveryClient.Close()

    End If

    Me.discoveryClient = Nothing

    Me.ShowDiscoveryInProgress(False)

End Sub

7.            需要做的另一件事是处理用户在发现过程中注销的情况。如果发生这种情况,您仅需中止发现进程,并关闭服务主机。按如下显示代码实现 AbortDiscovery 方法。

(代码片段 – 服务发现 – AbortDiscovery 方法 CSharp)

C#

private void AbortDiscovery()

{

    if (this.discoveryClient != null)

    {

        this..discoveryClient.Close();

    }

}

(代码片段 – 服务发现 – AbortDiscovery 方法 VB)

Visual Basic

Private Sub AbortDiscovery()

    If Me.discoveryClient IsNot Nothing Then Me.discoveryClient.Close()

End Sub

8.            按 Ctrl+Shift+B 生成解决方案。

               

下一步

练习 1:验证

               

练习 1:验证

1.            按 Ctrl+F5,在无调试的情况下启动 DiscoveryChat.exe 应用程序的实例。

2.            切换回 Visual Studio 并再次按 Ctrl+F5,启动 DiscoveryChat.exe 应用程序的另一个实例。

3.            切换到 DiscoveryChat.exe 的某个实例,按如下方式设置聊天:

a.            User Name:Fred

b.            单击 Sign In

 

图 3

使用 Fred 用户名登录

注意: Windows 防火墙可能会提示您允许聊天客户端访问以使用网络。允许该操作是安全的。

4.            切换到另一个 DiscoveryChat.exe 实例,并按如下方式设置聊天:

a.            Username:Wilma

b.            单击 Sign In

5.            Wilma 实例登录后,将立即执行临时发现,并找到 Fred 实例。

6.            双击可用用户列表中的 URI,开始与 Fred 的聊天。

a.            发送消息文本:Hi Fred

b.            单击 Send

7.            开始聊天后,您将看到 Wilma 出现在 Fred Discovery 聊天窗口的用户列表中。

 

图 4

相互发现的两个 DiscoveryChat 应用程序实例

8.            关闭这两个 DiscoveryChat 应用程序实例。

               

下一步

练习 2:元数据扩展

               

练习 2:元数据扩展

虽然聊天应用程序运行正常,但您可能会注意到一个问题。当 Wilma 发现了其他聊天实例后,您所知道的仅是服务端点的 Uri。所以,她的聊天窗口显示的是主机名称。而对于 Fred,由于聊天消息中包含 Wilma 的姓名,所以知道聊天消息来自于 Wilma。在本练习中,您将学习如何扩展 WS-Discovery 中使用的元数据,以提供其他信息(如聊天会话使用的用户名)。

任务 1 –通过扩展添加 EndpointDiscoveryBehavior

1.            打开 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Ex2-MetadataExtensions\Begin 下的初始解决方案 Begin.sln,选择您希望使用的语言(C# 或 VB)。也可以继续使用上一个练习完成时获得的解决方案。

    注意

Visual Studio 2010 必须在已提升的模式下运行。为此,右键单击 Visual Studio 2010 图标并选择 Run as Administrator。

2.            打开 DiscoveryChat 项目中的 SimpleChat.cs (C#) 或 SimpleChat.vb (Visual Basic) 文件。选择文件并按 F7 可以打开代码视图。

3.            在响应发现探测时,可以将 XML 添加到端点元数据中。为此,需要添加命名空间指令,具体来讲就是 System.ServiceModel.Description 命名空间。

(代码片段 – 服务发现 – ServiceModel.Description CSharp)

C#

using System.Linq;

using System.Xml.Linq;

using System.ServiceModel.Description;

(代码片段 – 服务发现 – ServiceModel.Description VB)

Visual Basic

Imports System.ServiceModel.Description

4.            找到 OpenServices 方法,并按照以下代码所示对其进行修改。

(代码片段 – 服务发现 – OpenServices 方法 CSharp)

C#

private void OpenServices()

{

    // Create a singleton instance for the host

ChatService chatService = new ChatService(this);

chatServiceHost = new ServiceHost(chatService, _localAddress);

    // Create a discovery behavior

    var endpointDiscoveryBehavior = new EndpointDiscoveryBehavior();

    // Add an extension element with the username

    endpointDiscoveryBehavior.Extensions.Add(

        new XElement(

            "root",

            new XElement("Name", this.userName)));

    // Find the endpoint

    ServiceEndpoint simpleEndpoint =

        this.chatServiceHost.Description.Endpoints.Find(typeof(ISimpleChatService));

    // Add our behavior to the endpoint before opening it

    simpleEndpoint.Behaviors.Add(endpointDiscoveryBehavior);

ShowStatus("Opening chat service...");

chatServiceHost.BeginOpen(

(result) =>

        {

chatServiceHost.EndOpen(result);

ShowStatus("Chat service ready");

        },

null);

}

(代码片段 – 服务发现 – OpenServices 方法 VB)

Visual Basic

Private Sub OpenServices()

' Create a singleton instance for the host

Dim chatService As New ChatService(Me)

Me.chatServiceHost = New ServiceHost(chatService, Me._localAddress)

    ' Create a discovery behavior

    Dim endpointDiscoveryBehavior As New EndpointDiscoveryBehavior()

    ' Add an extension element with the username

    endpointDiscoveryBehavior.Extensions.Add(<root><Name><%= Me.UserName %></Name></root>)

    ' Find the endpoint

    Dim simpleEndpoint = Me.chatServiceHost.Description.Endpoints.Find(GetType(ISimpleChatService))

    ' Add our behavior to the endpoint before opening it

    simpleEndpoint.Behaviors.Add(endpointDiscoveryBehavior)

Me.ShowStatus("Opening chat service...")

Me.chatServiceHost.BeginOpen(Sub(result)

chatServiceHost.EndOpen(result)

Me.ShowStatus("Chat service ready")

End Sub, Nothing)

End Sub

注意:(对于 Visual Basic 用户)上述代码片段使用了隐式输入。您需要在 VB 文件中设置 Option Infer On 或在项目级别上设置 Option Infer。

5.            接下来,您需要添加代码来在执行其他服务的发现时查找此元数据。添加 GetPeerName 方法,它会返回扩展元数据中 OpenServices 方法之后的第一个名为“Name”的节点。

(代码片段 – 服务发现 – GetPeerName 方法 CSharp)

C#

private static string GetPeerName(EndpointDiscoveryMetadata metadata)

{

    XElement peerNameElement =

        metadata.Extensions.Elements("Name").FirstOrDefault();

    if (peerNameElement != null)

        return peerNameElement.Value;

    return null;

}

(代码片段 – 服务发现 – GetPeerName 方法 VB)

Visual Basic

Private Shared Function GetPeerName(ByVal metadata As EndpointDiscoveryMetadata) As String

    Dim peerNameElement = metadata.Extensions.<Name>

    If peerNameElement IsNot Nothing Then Return peerNameElement.Value

    Return Nothing

End Function

6.            在名称和服务端点都可发现之后,您需要修改 PopulateUserList 方法来调用 GetPeerName 方法。如果名称节点存在,则 PeerUser 类会将其用作列表框中的显示名称。为此,使用以下代码替换当前对 AddUser 方法的调用。

C#

private void PopulateUserList(

EndpointDiscoveryMetadata endpointDiscoveryMetadata)

{

if (!EndpointIsSelf(endpointDiscoveryMetadata.Address.Uri))

    {

        this.AddUser(new PeerUser(GetPeerName(endpointDiscoveryMetadata), endpointDiscoveryMetadata.Address));

this.EnableUsersBox();

    }

}

Visual Basic

Private Sub PopulateUserList(

ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata)

If Not Me.EndpointIsSelf(endpointDiscoveryMetadata.Address.Uri) Then

        Me.AddUser(New PeerUser(GetPeerName(endpointDiscoveryMetadata), endpointDiscoveryMetadata.Address))

Me.EnableUsersBox()

End If

End Sub

7.            按 Ctrl+Shift+B 编译解决方案。

               

下一步

练习 2:验证

               

练习 2:验证

1.            按 Ctrl+F5 在不调试的情况下启动 DiscoveryChat 应用程序的实例。

2.            切换回 Visual Studio 并再次按 Ctrl+F5 启动应用程序的另一个实例。

3.            切换到某个实例,按如下方式设置聊天:

a.            User Name:Fred

b.            单击 Sign In

4.            切换到另一个 DiscoveryChat 实例并根据以下步骤设置聊天:

a.            User Name:Wilma

b.            单击 Sign In

5.            登录后,Wilma 的聊天窗口将发现 Fred 登录。切换到 Fred 的聊天窗口,并单击 Discover Users 按钮查找 Wilma。

6.            您应该会在 Available Users 窗格中看到另一个实例的用户名。可以双击该用户名开始聊天。

 

图 5

扩展功能允许我们在发现元数据中发送用户名

7.            关闭这两个 DiscoveryChat 应用程序实例。

               

               

下一步

练习 3:公告

               

练习 3:公告

多数聊天应用程序会在其他用户登录时通知您。此应用程序可以发现其他用户,但如果能在其他用户登录时通知您就更好了。发现可通过公告支持此功能。

任务 1 –启用公告端点

1.            打开 %TrainingKitInstallFolder%\ Labs\WCFServiceDiscovery\Source\Ex3-Announcements\Begin 下的初始解决方案Begin.sln,选择您希望使用的语言(C# 或 VB)。也可以继续使用上一个练习完成时获得的解决方案。

   注意

Visual Studio 2010 必须在已提升的模式下运行。为此,右键单击 Visual Studio 2010 图标并选择 Run as Administrator。

2.            打开 DiscoveryChat 项目中的 App.config 文件。

3.            要使用公告,您需要添加公告端点。为此,找到前面在本实验中添加的 DiscoveryBehavior 行为,并按照以下代码对其进行修改。

XML

<behavior name="DiscoveryBehavior">

  <serviceDiscovery>

    <announcementEndpoints>

      <endpoint name="udpEndpointName"

                kind="udpAnnouncementEndpoint"/>

    </announcementEndpoints>

  </serviceDiscovery>

    </behavior>

   公告端点

向发现服务行为添加公告端点可为该服务创建一个默认的公告客户端。这可确保在打开和关闭服务时分别发送一次联机和脱机公告。

4.            现在,您需要在代码中添加一个公告服务以接收公告消息。打开 SimpleChat.cs (C#) 或 SimpleChat.vb (Visual Basic),声明以下成员字段;您可以在 discoveryClient 成员声明之后执行此操作。

(代码片段 – 服务发现 – AnnouncementService 成员 CSharp)

C#

private AnnouncementService announcementService;

private ServiceHost announcementServiceHost;

(代码片段 – 服务发现 – AnnouncementService 成员 VB)

Visual Basic

Private WithEvents announcementService As AnnouncementService

Private announcementServiceHost As ServiceHost

5.            按照以下代码所示创建 OpenAnnouncementService 方法。

(代码片段 – 服务发现 – OpenAnnouncementService 方法 CSharp)

C#

private void OpenAnnouncementService()

{

    this.announcementService = new AnnouncementService();

    // Add event handlers

    this.announcementService.OnlineAnnouncementReceived +=

        new EventHandler<AnnouncementEventArgs>(this.OnOnlineAnnouncement);

    this.announcementService.OfflineAnnouncementReceived +=

        new EventHandler<AnnouncementEventArgs>(this.OnOfflineAnnouncement);

    // Create the service host with a singleton

    this.announcementServiceHost = new ServiceHost(this.announcementService);

    // Add the announcement endpoint

    this.announcementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());

    // Open the host async

    this.announcementServiceHost.BeginOpen(

        (result) =>

        {

            announcementServiceHost.EndOpen(result);

        },

        null);

}

(代码片段 – 服务发现 – OpenAnnouncementService 方法 VB)

Visual Basic

Private Sub OpenAnnouncementService()

    Me.announcementService = New AnnouncementService()

    ' Create the service host with a singleton

    Me.announcementServiceHost = New ServiceHost(Me.announcementService)

    ' Add the announcement endpoint

    Me.announcementServiceHost.AddServiceEndpoint(New UdpAnnouncementEndpoint())

    ' Open the host async

    Me.announcementServiceHost.BeginOpen(Sub(result)

                                     announcementServiceHost.EndOpen(result)

                                         End Sub, Nothing)

End Sub

   公告服务

公告服务的自承载实现公开了您在使用公告时可能会感兴趣的两个不同事件:OnlineAnnouncementReceived 和 OfflineAnnouncementReceived。当收到联机(Hello)和脱机(Bye)公告消息时,会分别触发这些事件。

6.            现在,您需要实现这些公告的处理程序。当新用户联机登录时,将他们添加到列表中。按照以下代码所示添加 OnOnlineAnnouncement 方法。

(代码片段 – 服务发现 – OnOnlineAnnouncement 方法 CSharp)

C#

private void OnOnlineAnnouncement(object sender, AnnouncementEventArgs e)

{

    EndpointDiscoveryMetadata metadata =

        e.EndpointDiscoveryMetadata;

    // We are looking for services that

    // implement the ISimpleChatService contract

    FindCriteria criteria =

        new FindCriteria(typeof(ISimpleChatService));

    if (criteria.IsMatch(metadata))

    {

        if (this.GetUser(metadata.Address.Uri) == null)

        {

            this.PopulateUserList(metadata);

        }

    }

}

(代码片段 – 服务发现 – OnOnlineAnnouncement 方法 VB)

Visual Basic

Private Sub OnOnlineAnnouncement(ByVal sender As Object,

                                 ByVal e As AnnouncementEventArgs) Handles announcementService.OnlineAnnouncementReceived

    Dim metadata = e.EndpointDiscoveryMetadata

    ' We are looking for services that

    ' implement the ISimpleChatService contract

    Dim criteria As New FindCriteria(GetType(ISimpleChatService))

    If criteria.IsMatch(metadata) And Me.GetUser(metadata.Address.Uri) Is Nothing Then Me.PopulateUserList(metadata)

End Sub

7.            当用户脱机时,将其删除并关闭所有活动的聊天窗口。按照以下代码所示添加 OnOfflineAnnouncement 方法。

(代码片段 – 服务发现 – OnOfflineAnnouncement 方法 CSharp)

C#

private void OnOfflineAnnouncement(object sender, AnnouncementEventArgs e)

{

    EndpointDiscoveryMetadata metadata =

        e.EndpointDiscoveryMetadata;

    FindCriteria criteria =

        new FindCriteria(typeof(ISimpleChatService));

    if (criteria.IsMatch(metadata))

    {

        this.RemoveUser(metadata.Address.Uri);

    }

}

(代码片段 – 服务发现 – OnOfflineAnnouncement 方法 VB)

Visual Basic

Private Sub OnOfflineAnnouncement(ByVal sender As Object,

                                  ByVal e As AnnouncementEventArgs) Handles announcementService.OfflineAnnouncementReceived

    Dim metadata = e.EndpointDiscoveryMetadata

    Dim criteria As New FindCriteria(GetType(ISimpleChatService))

    If criteria.IsMatch(metadata) Then Me.RemoveUser(metadata.Address.Uri)

End Sub

8.            找到 OpenServices 方法,并在方法实现的末尾添加对 OpenAnnouncementService 服务的调用。

C#

    this.ShowStatus("Opening chat service...");

this.chatServiceHost.BeginOpen(

(result) =>

        {

chatServiceHost.EndOpen(result);

this.ShowStatus("Chat service ready");

        },

null);

    this.OpenAnnouncementService();

}

Visual Basic

Me.ShowStatus("Opening chat service...")

Me.chatServiceHost.BeginOpen(Sub(result)

chatServiceHost.EndOpen(result)

Me.ShowStatus("Chat service ready")

End Sub, Nothing)

    Me.OpenAnnouncementService()

End Sub

9.            按 CTRL+SHIFT+B 生成解决方案。

               

下一步

练习 3:验证

               

练习 3:验证

现在将对应用程序进行测试,验证客户端是否可以检测到公告。

1.            按 Ctrl+F5 在不调试的情况下启动 DiscoveryChat 应用程序的实例。

2.            切换回 Visual Studio 并再次按 Ctrl+F5,启动另 DiscoveryChat 应用程序的另一个实例。

3.            切换到 DiscoveryChat 的某个实例,按如下方式设置聊天:

a.            User Name:Fred

b.            单击 Sign in

4.            切换到另一个 DiscoveryChat 实例并根据以下步骤设置聊天:

a.            User Name:Wilma

b.            单击 Sign in

5.            Wilma 登录后,Fred 的聊天窗口将检测到联机公告,并自动将她添加到可用用户列表中。

 

图 6

自动发现用户

6.            单击 Wilma 聊天窗口中的 Sign Out 按钮,验证脱机公告是否可正常工作。这应该在 Fred 的窗口将 Wilma 从可用用户列表中移除。

7.            尝试从两个应用程序登录和注销。每个应用程序都能接收到对方的联机/脱机公告。

8.            关闭这两个 DiscoveryChat 应用程序实例。

               

下一步

练习 4:发现代理

               

练习 4:发现代理

前面所有的练习都依赖于一个著名的 UDP 多播发现端点。端口和多播地址由 WS-Discovery 协议文档指定。使用这种多播发现被称为临时发现。临时发现只能识别同一子网中的服务。而使用托管发现,无论服务在哪里,只要它们在发现代理中注册了,就可以找到它们。

下面的顺序图显示了发现代理如何响应目标服务行为:

 

图 7

扩展功能允许我们在发现元数据中发送用户名

   发现代理

有关发现代理和 WS-Discovery 消息交换的详细信息,请参见 WS-Discovery 规范的第 3 节。

任务 1 –创建发现代理

System.ServiceModel.Discovery 命名空间包含一个基类,用于帮助您构建发现代理。要实现代理,必须改写 System.ServiceModel.Discovery.DiscoveryProxy 类提供的很多方法。这些方法都是异步的,可保证服务器的最大可伸缩性。在本动手实验中,将主要练习在代理中支持公告功能。当一个聊天客户端联机或脱机,元数据的缓存将被更新。然后,如果有客户端希望在代理上查询聊天客户端列表,您将响应查找请求。

1.            打开 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Ex4-DiscoveryProxy 路径下 begin 文件夹中的 Discovery.sln,选择您希望使用的语言(C# 或 VB)。也可以继续使用上一个练习完成时获得的解决方案。

    注意

Visual Studio 2010 必须在已提升的模式下运行。为此,右键单击 Visual Studio 2010 图标并选择 Run as Administrator。

2.            添加一个类型为 Console Application 的新项目。为此,在 Solution Explorer 中右键单击 Begin 解决方案,指向 Add 并单击 New Project。在 Visual C# 或 Visual Basic 的 Installed Templates 列表中,单击 Windows。在 Templates 窗格中,单击  Console Application 并确保将 .NET Framework 4.0 选择为目标运行时。最后,将项目名设置为 DiscoveryProxy,并单击 OK。

 

图 8

添加名为“DiscoveryProxy”的新 Console Application 项目 (C#)

 

图 9

添加名为“DiscoveryProxy”的新 Console Application 项目 (Visual Basic)

3.            将 DiscoveryProxy 项目的目标框架更改为 .NET Framework 4。为此,在 Solution Explorer 中右键单击 DiscoveryProxy 项目并选择 Properties。

a.            对于 C# 项目,单击 Application 选项卡并在 Target Framework 下拉列表中选择 .NET Framework 4 选项。

 

图 10

在 C# 项目中更改目标框架

b.            对于 Visual Basic 项目,选择 Compile 选项卡并单击 Advanced Compile Options 按钮。在 Advanced Compiler Settings 对话框中,选择 Target Framework 下拉列表中的 .NET Framework 4 选项,并单击 OK。

 

图 11

在 Visual Basic 项目中更改目标框架

在 Target Framework Change 消息框中单击 Yes 确认您希望更改目标框架。

 

图 12

确认对话框

注意:默认情况下,Visual Studio 2010 中随附有 Console Application 项目模板,可以 .NET Framework Client Profile 4 为目标创建项目。但如果您的项目以其他 .NET Framework 配置文件为目标,则此解决方案无法编译。

4.            右键单击 DiscoveryProxy 项目并单击 Add Reference。使用 Projects 选项卡,添加对 DiscoveryChat 项目的引用。重复上述步骤,使用 .NET 选项卡添加对 System.ServiceModel 和 System.ServiceModel.Discovery 库的引用。

5.            按 Ctrl+Shift+B 生成解决方案。

6.            (对于纯 Visual Basic 用户)在项目级别导入以下命名空间,以便在练习过程中使用。为此,右键单击 DiscoveryProxy 项目并选择 Properties。打开 References 页面,在 Imported namespaces 部分,选择以下命名空间:

a.            System.Net

b.            System.ServiceModel

c.             System.ServiceModel.Discovery

d.            System.Collections.ObjectModel

e.            Microsoft.Samples.Discovery.Contracts

注意:对于 C# 用户,需要时将向所需的每个文件添加具体的 using 指令。

7.            创建 ChatDiscoveryProxy 类。在 Solution Explorer 中右键单击 DiscoveryProxy 项目,指向 Add 并单击 Class。在 Name 框中键入 ChatDiscoveryProxy。

 

图 13

添加名为“ChatDiscoveryProxy”的新类 (C#)

 

图 14

添加名为“ChatDiscoveryProxy”的新类 (Visual Basic)

8.            (对于纯 C# 用户)将为新类添加以下 using 指令。

(代码片段 – 服务发现 – ChatDiscoveryProxy using 语句 CSharp)

C#

using System.ServiceModel;

using System.ServiceModel.Discovery;

using System.Collections.ObjectModel;

using Microsoft.Samples.Discovery.Contracts;

9.            使您的类继承自 System.ServiceModel.Discovery.DiscoveryProxy 基类。

C#

public class ChatDiscoveryProxy :System.ServiceModel.Discovery.DiscoveryProxy

Visual Basic

Public Class ChatDiscoveryProxy

    Inherits Discovery.DiscoveryProxy

10.          在本实验中,您的类将维护一个线程安全内存型缓存。因此,您需要使您的 WCF 服务实例保持独立。由于它是线程安全的,并且您希望保持最大的可伸缩性,因此还需要允许同时进行多个调用。修改 ChatDiscoveryProxy 类签名,添加以下属性。

C#

[ServiceBehavior(

    InstanceContextMode = InstanceContextMode.Single,

    ConcurrencyMode = ConcurrencyMode..Multiple)]

public class ChatDiscoveryProxy :System.ServiceModel.Discovery.DiscoveryProxy

Visual Basic

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Multiple)>

Public Class ChatDiscoveryProxy

    Inherits Discovery.DiscoveryProxy

注意: 使用内存型缓存意味着,如果我们的发现代理主机停机,所有服务信息都将丢失。更可靠的实现将会使用持久的存储,如数据库;这将保证服务元数据在服务无法使用时也不会丢失。

11.          您需要创建一个线程安全集合类来保存已发现服务的实例。右键单击 DiscoveryProxy 项目,指向 Add 并单击 Class。在 Name 框中键入 ChatServiceCollection。

注意:有关详细信息,请参见集合和同步(线程安全)。

12.          (对于纯 C# 用户)将为新类添加以下 using 指令。

C#

using System.ServiceModel.Discovery;

13.          将您的类标记为 internal (C#) 或 Friend (Visual Basic),并使其继承自 SynchronizedKeyedCollection 基类。

C#

internal class ChatServiceCollection :SynchronizedKeyedCollection<Uri, EndpointDiscoveryMetadata>

Visual Basic

Friend Class ChatServiceCollection

    Inherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)

14.          实现 SynchronizedKeyedCollection 类的 GetKeyForItem 方法,如以下代码所示。

(代码片段 – 服务发现 – GetKeyForItem 方法 CSharp)

C#

internal class ChatServiceCollection :

SynchronizedKeyedCollection<Uri, EndpointDiscoveryMetadata>

{

protected override Uri GetKeyForItem(EndpointDiscoveryMetadata item)

    {

        if (item == null)

        {

            throw new ArgumentNullException("item");

        }

        return item.Address.Uri;

    }

}

(代码片段 – 服务发现 – GetKeyForItem 方法 VB)

Visual Basic

Friend Class ChatServiceCollection

Inherits SynchronizedKeyedCollection(Of Uri, EndpointDiscoveryMetadata)

    Protected Overrides Function GetKeyForItem(ByVal item As EndpointDiscoveryMetadata) As Uri

        If item Is Nothing Then Throw New ArgumentNullException("item")

        Return item.Address.Uri

    End Function

End Class

15.          切换回 ChatDiscoveryProxy 类实现,然后添加静态 ChatServiceCollection 属性及其支持字段,按照以下代码所示。

(代码片段 – 服务发现 – ChatServiceCollection 成员 CSharp)

C#

public class ChatDiscoveryProxy :DiscoveryProxy

{

   private static ChatServiceCollection cache = new ChatServiceCollection();

    internal static ChatServiceCollection Cache

    {

        get { return cache; }

    }

}

(代码片段 – 服务发现 – ChatServiceCollection 成员 VB)

Visual Basic

Public Class ChatDiscoveryProxy

Inherits Discovery.DiscoveryProxy

    Private Shared _cache As New ChatServiceCollection()

    Friend Shared ReadOnly Property Cache As ChatServiceCollection

        Get

            Return _cache

        End Get

    End Property

End Class

16.          您将需要许多帮助程序类来完成服务。这些类十分简洁,您需要将其添加到您的解决方案中。为此,右键单击 DiscoveryProxy 项目,指向 Add 并单击 Existing Item。然后,浏览到 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Assets\DiscoveryProxy 文件夹,选择您希望使用的语言(C# 或 VB)并添加以下文件:

a.            AsyncResult.cs (C#) 或 AsyncResult.vb (Visual Basic)

b.            CompletedAsyncResult.cs (C#) 或 CompletedAsyncResult.vb (Visual Basic)

c.             FindAsyncResult.cs (C#) 或 FindAsyncResult.vb (Visual Basic)

d.            EndpointDiscoveryMetadataExtensions.cs (C#) 或 EndpointDiscoveryMetadataExtensions.vb (Visual Basic)

17.          当收到联机公告时,您需要确定该服务是否您希望缓存的服务。如果是,将其添加到缓存。添加以下代码,改写 ChatDiscoveryProxy 类中的 OnBeginOnlineAnnouncement 方法。

(代码片段 – 服务发现 – OnBeginOnlineAnnouncement 方法 CSharp)

C#

protected override IAsyncResult OnBeginOnlineAnnouncement(DiscoveryMessageSequence messageSequence,

                                                EndpointDiscoveryMetadata endpointDiscoveryMetadata,

                                                AsyncCallback callback,

                                                object state)

{

    if (endpointDiscoveryMetadata == null)

    {

        throw new ArgumentNullException("endpointDiscoveryMetadata");

    }

    // We care only about ISimpleChatService services

    FindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));

    if (criteria.IsMatch(endpointDiscoveryMetadata))

    {

        endpointDiscoveryMetadata.WriteLine("Adding");

        Cache.Add(endpointDiscoveryMetadata);

    }

    return new CompletedAsyncResult(callback, state);

}

(代码片段 – 服务发现 – OnBeginOnlineAnnouncement 方法 VB)

Visual Basic

Protected Overrides Function OnBeginOnlineAnnouncement(ByVal messageSequence As DiscoveryMessageSequence, ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult

    If endpointDiscoveryMetadata Is Nothing Then Throw New ArgumentNullException("endpointDiscoveryMetadata")

    ' We care only about ISimpleChatService services

    Dim criteria As New FindCriteria(GetType(ISimpleChatService))

    If criteria.IsMatch(endpointDiscoveryMetadata) Then

        endpointDiscoveryMetadata.WriteLine("Adding")

        Cache.Add(endpointDiscoveryMetadata)

    End If

    Return New CompletedAsyncResult(callback, state)

End Function

18.          如果收到脱机公告消息,您可能希望从缓存中删除元数据(如果该位置存在元数据)。添加以下代码,改写 OnBeginOfflineAnnouncement 方法。

(代码片段 – 服务发现 – OnBeginOfflineAnnouncement 方法 CSharp)

C#

protected override IAsyncResult OnBeginOfflineAnnouncement(DiscoveryMessageSequence messageSequence, EndpointDiscoveryMetadata endpointDiscoveryMetadata, AsyncCallback callback, object state)

{

    try

    {

        if (endpointDiscoveryMetadata == null)

        {

            throw new ArgumentNullException("endpointDiscoveryMetadata");

        }

        // We care only about ISimpleChatService services

        FindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));

        if (criteria.IsMatch(endpointDiscoveryMetadata))

        {

            endpointDiscoveryMetadata.WriteLine("Removing");

            Cache.Remove(endpointDiscoveryMetadata.Address.Uri);

        }

    }

    catch (KeyNotFoundException)

    {

        // No problem if it does not exist in the cache

    }

    return new CompletedAsyncResult(callback, state);

}

(代码片段 – 服务发现 – OnBeginOfflineAnnouncement 方法 VB)

Visual Basic

Protected Overrides Function OnBeginOfflineAnnouncement(ByVal messageSequence As DiscoveryMessageSequence, ByVal endpointDiscoveryMetadata As EndpointDiscoveryMetadata, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult

    Try

        If endpointDiscoveryMetadata Is Nothing Then Throw New ArgumentNullException("endpointDiscoveryMetadata")

        ' We care only about ISimpleChatService services

        Dim criteria As New FindCriteria(GetType(ISimpleChatService))

        If criteria.IsMatch(endpointDiscoveryMetadata) Then

            endpointDiscoveryMetadata.WriteLine("Removing")

            Cache.Remove(endpointDiscoveryMetadata.Address.Uri)

        End If

    Catch e1 As KeyNotFoundException

        ' No problem if it does not exist in the cache

    End Try

    Return New CompletedAsyncResult(callback, state)

End Function

19.          现在您可以改写 OnBeginFind 方法了,客户端向代理发出发现查找请求时将调用该方法。可以在这里搜索已知服务端点的缓存,并使用任何匹配端点响应客户端查找请求。

(代码片段 – 服务发现 – OnBeginFind 方法 CSharp)

C#

protected override IAsyncResult OnBeginFind(FindRequestContext findRequestContext, AsyncCallback callback, object state)

{

    if (findRequestContext == null)

    {

        throw new ArgumentNullException("findRequestContext");

    }

    Console..WriteLine(

        "Find request for contract {0}",

        findRequestContext.Criteria.ContractTypeNames.FirstOrDefault());

    // Query to find the matching endpoints

    var query = from service in Cache

                where findRequestContext.Criteria.IsMatch(service)

                select service;

    // Collection to contain the results of the query

    var matchingEndpoints = new Collection<EndpointDiscoveryMetadata>();

    // Execute the query and add the matching endpoints

    foreach (EndpointDiscoveryMetadata metadata in query)

    {

        metadata.WriteLine("\tFound");

        matchingEndpoints.Add(metadata);

        findRequestContext.AddMatchingEndpoint(metadata);

    }

    return new FindAsyncResult(matchingEndpoints, callback, state);

}

(代码片段 – 服务发现 – OnBeginFind 方法 VB)

Visual Basic

Protected Overrides Function OnBeginFind(ByVal findRequestContext As FindRequestContext, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult

    If findRequestContext Is Nothing Then Throw New ArgumentNullException("findRequestContext")

    Console.WriteLine("Find request for contract {0}", findRequestContext.Criteria.ContractTypeNames.FirstOrDefault())

    ' Query to find the matching endpoints

    Dim query = From service In Cache

                Where findRequestContext.Criteria.IsMatch(service)

                Select service

    ' Collection to contain the results of the query

    Dim matchingEndpoints = New Collection(Of EndpointDiscoveryMetadata)()

    ' Execute the query and add the matching endpoints

    For Each metadata As EndpointDiscoveryMetadata In query

        metadata.WriteLine(Constants.vbTab & "Found")

        matchingEndpoints.Add(metadata)

        findRequestContext.AddMatchingEndpoint(metadata)

    Next metadata

    Return New FindAsyncResult(matchingEndpoints, callback, state)

End Function

20.          改写 OnEndFind 方法,完成查找操作。

(代码片段 – 服务发现 – OnEndFind 方法 CSharp)

C#

protected override void OnEndFind(IAsyncResult result)

{

    FindAsyncResult.End(result);

}

(代码片段 – 服务发现 – OnEndFind 方法 VB)

Visual Basic

Protected Overrides Sub OnEndFind(ByVal result As IAsyncResult)

    FindAsyncResult.End(result)

End Sub

注意:在本实验解决方案中,发现代理以 Console Application 的形式执行。而生产环境则会使用更加可靠的解决方案,如 Windows Service。

21.          改写在 System.ServiceModel.Discovery.DiscoveryProxy 类中声明的其他所需抽象方法。

(代码片段 – 服务发现 – DiscoveryProxy 抽象方法 CSharp)

C#

protected override IAsyncResult OnBeginResolve(ResolveCriteria resolveCriteria, AsyncCallback callback, object state)

{

    return new CompletedAsyncResult(callback, state);

}

protected override EndpointDiscoveryMetadata OnEndResolve(IAsyncResult result)

{

    return CompletedAsyncResult<EndpointDiscoveryMetadata>.End(result);

}

protected override void OnEndOfflineAnnouncement(IAsyncResult result)

{

    CompletedAsyncResult.End(result);

}

protected override void OnEndOnlineAnnouncement(IAsyncResult result)

{

    CompletedAsyncResult.End(result);

}

(代码片段 – 服务发现 – DiscoveryProxy 抽象方法 VB)

Visual Basic

Protected Overrides Function OnBeginResolve(ByVal resolveCriteria As ResolveCriteria, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult

    Return New CompletedAsyncResult(callback, state)

End Function

Protected Overrides Function OnEndResolve(ByVal result As IAsyncResult) As EndpointDiscoveryMetadata

    Return CompletedAsyncResult(Of EndpointDiscoveryMetadata).End(result)

End Function

Protected Overrides Sub OnEndOfflineAnnouncement(ByVal result As IAsyncResult)

    CompletedAsyncResult.End(result)

End Sub

Protected Overrides Sub OnEndOnlineAnnouncement(ByVal result As IAsyncResult)

    CompletedAsyncResult.End(result)

End Sub

22.          现在,您需要修改 Main 方法,为 ChatDiscoveryProxy 服务创建 ServiceHost 方法。打开 DiscoveryProxy 项目中的 Program.cs (C#) 或 Module1.vb (Visual Basic)。

23.          (对于纯 C# 用户)将为 Program.cs 添加以下 using 指令。

C#

using System.Net;

using System.ServiceModel;

using System.ServiceModel.Discovery;

24.          要承载 ChatDiscoveryProxy 服务,您需要为服务主机创建 DiscoveryEndpoint 端点。在 Program 类 (C#) 或 Module1 模块 (Visual Basic) 中添加以下方法。

    注意

在本实验解决方案中,您将对代理服务使用 TCP 端口 8001。

(代码片段 – 服务发现 – HostDiscoveryEndpoint 方法 CSharp)

C#

private static ServiceHost HostDiscoveryEndpoint(string hostName)

{

    // Create a new ServiceHost with a singleton ChatDiscovery Proxy

    ServiceHost myProxyHost = new

        ServiceHost(new ChatDiscoveryProxy());

    string proxyAddress = "net.tcp://" +

        hostName + ":8001/discoveryproxy";

    // Create the discovery endpoint

    DiscoveryEndpoint discoveryEndpoint =

        new DiscoveryEndpoint(

            new NetTcpBinding(),

            new EndpointAddress(proxyAddress));

    discoveryEndpoint.IsSystemEndpoint = false;

    // Add UDP Annoucement endpoint

    myProxyHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());

    // Add the discovery endpoint

    myProxyHost.AddServiceEndpoint(discoveryEndpoint);

    myProxyHost.Open();

    Console.WriteLine("Discovery Proxy {0}",

        proxyAddress);

    return myProxyHost;

}

(代码片段 – 服务发现 – HostDiscoveryEndpoint 方法 VB)

Visual Basic

Private Function HostDiscoveryEndpoint(ByVal hostName As String) As ServiceHost

    ' Create a new ServiceHost with a singleton ChatDiscovery Proxy

    Dim myProxyHost As New ServiceHost(New ChatDiscoveryProxy())

    Dim proxyAddress = "net.tcp://" & hostName & ":8001/discoveryproxy"

    ' Create the discovery endpoint

    Dim discoveryEndpoint As New DiscoveryEndpoint(New NetTcpBinding(), New EndpointAddress(proxyAddress))

    discoveryEndpoint.IsSystemEndpoint = False

    ' Add UDP Annoucement endpoint

    myProxyHost.AddServiceEndpoint(New UdpAnnouncementEndpoint())

    ' Add the discovery endpoint

    myProxyHost.AddServiceEndpoint(discoveryEndpoint)

    myProxyHost.Open()

    Console.WriteLine("Discovery Proxy {0}", proxyAddress)

    Return myProxyHost

End Function

25.          按照以下代码所示,修改 Main 方法以承载 ChatDiscoveryProxy 服务。

(代码片段 – 服务发现 – DiscoveryProxy Main 方法 CSharp)

C#

static void Main(string[] args)

{

    Console.Title = "DiscoveryProxy Service";

    Console.WriteLine("DiscoveryProxy Console Host");

    string hostName = Dns.GetHostName();

    using (ServiceHost proxyHost = HostDiscoveryEndpoint(hostName))

    {

        Console.WriteLine("Press <Enter> to exit");

        Console.ReadLine();

        proxyHost.Close();

    }

}

(代码片段 – 服务发现 – DiscoveryProxy Main 方法 VB)

Visual Basic

Sub Main()

    Console.Title = "DiscoveryProxy Service"

    Console.WriteLine("DiscoveryProxy Console Host")

    Dim hostName = Dns.GetHostName()

    Using proxyHost As ServiceHost = HostDiscoveryEndpoint(hostName)

        Console.WriteLine("Press <Enter> to exit")

        Console.ReadLine()

        proxyHost.Close()

    End Using

End Sub

26.          按 Ctrl+Shift+B 生成解决方案

               

任务 2 –修改 DiscoveryChat Application 以使用托管发现

已实现了发现代理,现在需要修改聊天应用程序来使用该代理。

1.            打开 DiscoveryChat 项目中的 SimpleChat.cs 文件 (C#) 或 SimpleChat.vb (Visual Basic)。选择文件并按 F7 可以打开代码视图。

2.            找到 ManagedDiscovery 方法。如果 Managed Discovery 单选按钮已选中,表明 UI 已经调用了该方法。由于您已经实现了临时发现的处理程序,那么,唯一还需要做的就是按照以下代码所示添加代码,以实现此托管发现。

(代码片段 – 服务发现 – ManagedDiscovery 方法 CSharp)

C#

private void ManagedDiscovery()

{

try

    {

        // Create an endpoint for the proxy

        DiscoveryEndpoint proxyEndpoint =

            new DiscoveryEndpoint(

                new NetTcpBinding(),

                new EndpointAddress(proxyAddressText.Text));

        // Create the DiscoveryClient with a proxy endpoint

        // for managed discovery

        this.discoveryClient = new DiscoveryClient(proxyEndpoint);

        // Same handlers as ad hoc discovery

        this.discoveryClient.FindCompleted +=

            new EventHandler<FindCompletedEventArgs>(this.OnFindCompleted);

        this.discoveryClient.FindProgressChanged +=

            new EventHandler<FindProgressChangedEventArgs>(this.OnFindProgressChanged);

        // Setup the form for discovery

        this..ShowDiscoveryInProgress(true);

        this..discoveryClient.FindAsync(new FindCriteria(typeof(ISimpleChatService)));

    }

    catch (UriFormatException)

    {

        MessageBox..Show(

            Resources.InvalidUriMessage,

            this..Text,

            MessageBoxButtons.OK,

            MessageBoxIcon..Information,

            MessageBoxDefaultButton..Button1,

            (MessageBoxOptions)0);

    }

}

(代码片段 – 服务发现 – ManagedDiscovery 方法 VB)

Visual Basic

Private Sub ManagedDiscovery()

    Try

        ' Create an endpoint for the proxy

        Dim proxyEndpoint As New DiscoveryEndpoint(New NetTcpBinding(), New EndpointAddress(proxyAddressText.Text))

        ' Create the DiscoveryClient with a proxy endpoint

        ' for managed discovery

        Me.discoveryClient = New DiscoveryClient(proxyEndpoint)

        ' Setup the form for discovery

        Me..ShowDiscoveryInProgress(True)

        Me.discoveryClient.FindAsync(New FindCriteria(GetType(ISimpleChatService)))

    Catch e1 As UriFormatException

        MsgBox(My.Resources.InvalidUriMessage, MsgBoxStyle.Information, Me.Text)

    End Try

End Sub

3.            找到 InitializeManagedDiscovery 方法,并将托管单选按钮Checked 属性设置为 True。

C#

private void InitializeManagedDiscovery()

{

    this.managedRadioButton.Checked = true;

}

Visual Basic

Private Sub InitializeManagedDiscovery()

    Me.managedRadioButton.Checked = True

End Sub

4.            按 Ctrl+Shift+B 生成解决方案。

               

下一步

练习 4:验证

               

练习 4:验证

现在将对聊天应用程序进行测试,看看客户端是否可以检测到公告。

1.            右键单击 DiscoveryProxy 项目并单击 Set as StartUp project。

2.            按下 F5,在调试器中启动 DiscoveryProxy 控制台。

 

图 15

运行中的 DiscoveryProxy Service

注意: Windows 防火墙可能会提示您允许代理和/或聊天客户端访问以使用网络。允许该操作是安全的。

3.            切换回 Visual Studio Solution Explorer,右键单击 DiscoveryChat 项目,指向 Debug 并单击 Start New Instance,启动 DiscoveryChat 应用程序的两个实例。

4.            切换到 DiscoveryChat 应用程序的某个实例,按如下方式设置聊天:

a.            User Name:Fred

b.            单击 Sign in

5.            切换到另一个 DiscoveryChat 实例并根据以下步骤设置聊天:

a.            User Name:Wilma

b.            单击 Sign in

6.            当 Wilma 登录后,她的窗口会通过托管发现代理找到 Fred。Fred 的聊天窗口应通过 Wilma 的窗口检测到联机公告,并自动添加她。

 

图 16

执行中的托管发现

7.            单击 Wilma 聊天窗口中的 Sign Out 按钮,验证脱机公告是否可正常工作。这应该在 Fred 的窗口将 Wilma 从可用用户列表中移除。

8.            尝试从两个应用程序登录和注销。每个应用程序都能接收到对方的联机/脱机公告。

 

图 17

执行中的 DiscoveryProxy Service

9.            按 Shift+F5 停止调试

               

下一步

练习 5:旧式发现

               

练习 5:旧式发现

在前面的练习中,您可能发现托管发现中实际上没有提供什么新功能。除非您拥有不同子网中的聊天客户端,否则很难看出托管发现中的改进功能。如果确实如此,那么您现在可以看到其他聊天客户端。到目前为止,只有在聊天客户端使用了公告来公开它的联机状态时,发现代理才会看到该聊天客户端。

托管发现的另一个优势在于,发现代理可以通过其他方法来获取服务器实例列表。可以从数据库去读一个可用服务列表,一个配置文件,或使用自定义协议来与服务实例通信。

在本实验解决方案中,我们将使用另一个名为 LegacyChat 的聊天客户端项目。此聊天客户端将使用与 DiscoveryChat 应用程序相同的 ISimpleChat 界面,但它不支持 WS-Discovery,而是使用自定义协议与代理服务器进行通信。 

问题在于,基于发现的新聊天客户端将无法发现旧的聊天客户端,而旧的聊天客户端也无法发现新的聊天客户端。在本练习中,您将使用旧客户端所用的自定义协议来创建发现元数据,将这两个客户端桥接在一起。完成后,两个聊天应用程序都能够使用代理中的托管发现来找到对方。

任务 1 –实现旧式聊天代理

1.            打开 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Ex5-LegacyDiscovery 路径下 begin 文件夹中的 Begin.sln,选择您希望使用的语言(C# 或 VB)。也可以继续使用上一个练习完成时获得的解决方案。

    注意

Visual Studio 2010 必须在已提升的模式下运行。为此,右键单击 Visual Studio 2010 图标并选择 Run as Administrator。

2.            现有的旧协议使用 DataContract,因此,您需要添加对 System.Runtime.Serialization 命名空间的引用。为此,执行以下步骤。

a.            右键单击 DiscoveryProxy 项目,然后单击 Add Reference。

b.            在 .NET 选项卡中选择 System.Runtime.Serialization 命名空间,并单击 Ok。

3.            添加 Assets 文件夹中的 LegacyChat 项目。为此,右键单击 Begin 解决方案,指向 Add 并单击 Existing project。然后,浏览到 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Assets\LegacyChat 文件夹,选择您希望使用的语言(C# 或 VB)。最后,选择 LegacyChat 项目并单击 Open。

4.            右键单击 LegacyChat 项目,然后单击 Add Reference。在 Projects 选项卡中选择 DiscoveryProxy 项目并单击 Ok。

5.            将 ILegacyChatProxy.cs 文件 (C#) 或 ILegacyChatProxy.vb (Visual Basic) 从 Assets 文件夹添加到 DiscoveryProxy 项目。为此,右键单击 DiscoveryProxy 项目,指向 Add 并单击 Existing Item。然后,浏览到 %TrainingKitInstallFolder%\Labs\WCFServiceDiscovery\Source\Assets\LegacyChatProxy 文件夹,选择您希望使用的语言(C# 或 VB)。最后,选择文件并单击 Add。

 CSharp.jpg

图 18

添加提供的 ILegacyChatProxy.cs 文件 (C#)

 VisualBasic.jpg

图 19

添加提供的 ILegacyChatProxy.vb 文件 (Visual Basic)

6.            按 Ctrl+Shift+B 生成解决方案。

7.            (对于纯 Visual Basic 用户)在项目级别导入以下命名空间,以便在练习过程中使用。为此,右键单击 DiscoveryProxy 项目并选择 Properties。打开 References 页面,在 Imported namespaces 部分,选择以下命名空间:

a.            System.Xml

b.            DiscoveryProxy.Contracts

注意:对于 C# 用户,需要时将向所需的每个文件添加具体的 using 指令。

8.            旧客户端使用 ILegacyChatProxy 接口注册代理服务器。DiscoveryProxy 服务器必须实现此接口。右键单击 DiscoveryProxy 项目,指向 Add 并单击 Class。在 Name 框中键入 LegacyChatProxy 并单击 Add。执行以下步骤,设置此类。

a.            (对于纯 C# 用户)添加以下 using 指令。

C#

using System.ServiceModel;

using System.ServiceModel.Discovery;

using System.Xml;

using System.Xml.Linq;

using Microsoft.Samples.Discovery;

using Microsoft.Samples.Discovery.Contracts;

using DiscoveryProxy.Contracts;

b.            添加 ServiceBehavior 属性到类签名,如以下代码所示。

C#

[ServiceBehavior(

    InstanceContextMode = InstanceContextMode.Single,

    ConcurrencyMode = ConcurrencyMode..Multiple)]

class LegacyChatProxy

Visual Basic

<ServiceBehavior(

    InstanceContextMode = InstanceContextMode.Single,

    ConcurrencyMode:=ConcurrencyMode.Multiple)>

Public Class LegacyChatProxy

c.             使 LegacyChatProxy 类继承自 ILegacyChatProxy 接口。

C#

class LegacyChatProxy :ILegacyChatProxy

Visual Basic

Public Class LegacyChatProxy

    Implements ILegacyChatProxy

注意:ILegacyChatProxy 接口声明三个操作:Register、Unregister 和 GetUsers,它们将通过本练习实现。

9.            旧聊天客户端发现协议调用 Register 方法,通知服务器一个新的聊天客户端可用。要显示旧聊天客户端,您需要创建 EndpointDiscoveryMetadata 并将其添加到缓存。实现 Register 方法,如以下代码所示。 

(代码片段 – 服务发现 – Register 方法 CSharp)

C#

public void Register(Uri chatUri, string name)

{

    // Create endpoint metadata from the legacy API

    var metadata = new EndpointDiscoveryMetadata()

    {

        Address = new EndpointAddress(chatUri),

        ContractTypeNames =

        {

            new XmlQualifiedName("SimpleChat", "http://sample.microsoft.com/wcf4")

        },

        Extensions =

        {

            new XElement("root",new XElement("Name", name))

        },

        ListenUris =

        {

            chatUri

        }

    };

    metadata.WriteLine("Adding legacy");

    // Add it to the cache

    ChatDiscoveryProxy.Cache.Add(metadata);

}

(代码片段 – 服务发现 – Register 方法 VB)

Visual Basic

Public Sub Register(ByVal chatUri As System.Uri, ByVal name As String) Implements ILegacyChatProxy.Register

    ' Create endpoint metadata from the legacy API

    Dim metadata = New EndpointDiscoveryMetadata() With {.Address = New EndpointAddress(chatUri)}

    metadata.ContractTypeNames.Add(New XmlQualifiedName("SimpleChat", "http://sample.microsoft.com/wcf4"))

    metadata.Extensions.Add(<root><Name><%= name %></Name></root>)

    metadata.ListenUris.Add(chatUri)

    metadata.WriteLine("Adding legacy")

    ' Add it to the cache

    ChatDiscoveryProxy.Cache.Add(metadata)

End Sub

10.          当旧聊天客户端注销时,您必须从发现缓存删除元数据。为此,按照以下代码所示实现 Unregister 方法。

(代码片段 – 服务发现 – Unregister 方法 CSharp)

C#

public void Unregister(Uri chatUri)

{

    if (chatUri == null)

    {

        throw new ArgumentNullException("chatUri");

    }

    Console.WriteLine("Removing legacy {0}", chatUri.ToString());

    ChatDiscoveryProxy.Cache.Remove(chatUri);

}

(代码片段 – 服务发现 – Unregister 方法 VB)

Visual Basic

Public Sub Unregister(ByVal chatUri As System.Uri) Implements ILegacyChatProxy.Unregister

    If chatUri Is Nothing Then Throw New ArgumentNullException("chatUri")

    Console.WriteLine("Removing legacy {0}", chatUri.ToString())

    ChatDiscoveryProxy.Cache.Remove(chatUri)

End Sub

11.          旧聊天客户端还包含一个 GetUsers 方法,它可以返回 ChatUser 实例的列表。您需要实现此接口,以搜索端点元数据并返回聊天用户列表。实现 GetUsers 方法,如以下代码所示。

(代码片段 – 服务发现 – GetUsers 方法 CSharp)

C#

public List<ChatUser> GetUsers()

{

    List<ChatUser> users = new List<ChatUser>();

    FindCriteria criteria = new FindCriteria(typeof(ISimpleChatService));

    // Query to find the matching endpoints

    var query = from service in ChatDiscoveryProxy.Cache

                where criteria.IsMatch(service)

                select service;

    Console.WriteLine("GetUsers returning list to legacy chat");

    foreach (var serviceMetadata in query)

    {

        ChatUser user = new ChatUser();

        XElement peerNameElement = serviceMetadata.Extensions.Elements("Name").FirstOrDefault();

        if (peerNameElement != null)

        {

            user.Username = peerNameElement.Value;

        }

        else

        {

            user.Username = serviceMetadata.Address.ToString();

        }

        user.Address = serviceMetadata.Address.Uri.ToString();

        users.Add(user);

    }

    return users;

}

(代码片段 – 服务发现 – GetUsers 方法 VB)

Visual Basic

Public Function GetUsers() As List(Of ChatUser) Implements ILegacyChatProxy.GetUsers

    Dim users As New List(Of ChatUser)()

    Dim criteria As New FindCriteria(GetType(ISimpleChatService))

    ' Query to find the matching endpoints

    Dim query = From service In ChatDiscoveryProxy.Cache

                Where criteria.IsMatch(service)

                Select service

    Console.WriteLine("GetUsers returning list to legacy chat")

    For Each serviceMetadata In query

        Dim user As New ChatUser()

        Dim peerNameElement = serviceMetadata.Extensions.<Name>

        If peerNameElement IsNot Nothing Then

            user.Username = peerNameElement.Value

        Else

            user.Username = serviceMetadata.Address.ToString()

        End If

        user.Address = serviceMetadata.Address.Uri.ToString()

        users.Add(user)

    Next serviceMetadata

    Return users

End Function

12.          现在,您需要修改 Program 类 (C#) 或 Module1 模块 (Visual Basic),以承载旧发现服务。打开 DiscoveryProxy 项目中的 Program.cs 文件 (C#) 或 Module1.vb 文件 (Visual Basic)。

13.          (对于纯 C# 用户)添加以下 using 指令。

C#

using Microsoft.Samples.Discovery.Contracts;

using DiscoveryProxy.Contracts;

14.          按照以下代码所示添加 HostLegacyEndpoint 方法。

(代码片段 – 服务发现 – HostLegacyEndpoint 方法 CSharp)

C#

private static ServiceHost HostLegacyEndpoint(string hostName)

{

    Uri proxyAddress = new Uri("net.tcp://" +

        hostName + ":8001/legacyproxy");

    ServiceHost legacyHost = new

        ServiceHost(new LegacyChatProxy());

    legacyHost.AddServiceEndpoint

                (

                    typeof(ILegacyChatProxy),

                    new NetTcpBinding(),

                    proxyAddress

                );

    legacyHost.Open();

    Console..WriteLine(

        "Legacy Proxy {0}",proxyAddress);

    return legacyHost;

}

(代码片段 – 服务发现 – HostLegacyEndpoint 方法 VB)

Visual Basic

Private Function HostLegacyEndpoint(ByVal hostName As String) As ServiceHost

    Dim proxyAddress As New Uri("net.tcp://" & hostName & ":8001/legacyproxy")

    Dim legacyHost As New ServiceHost(New LegacyChatProxy())

    legacyHost.AddServiceEndpoint(GetType(ILegacyChatProxy), New NetTcpBinding(), proxyAddress)

    legacyHost.Open()

    Console.WriteLine("Legacy Proxy {0}", proxyAddress)

    Return legacyHost

End Function

15.          修改 Main 方法,以同时打开旧主机。

C#

static void Main(string[] args)

{

Console.Title = "DiscoveryProxy Service";

Console.WriteLine("DiscoveryProxy Console Host");

string hostName = Dns.GetHostName();

using (ServiceHost proxyHost = HostDiscoveryEndpoint(hostName))

    using (ServiceHost legacyHost = HostLegacyEndpoint(hostName))

    {

Console.WriteLine("Press <Enter> to exit");

Console.ReadLine();

proxyHost.Close();

        legacyHost.Close();

    }

}

Visual Basic

Sub Main()

Console.Title = "DiscoveryProxy Service"

Console.WriteLine("DiscoveryProxy Console Host")

Dim hostName = Dns.GetHostName()

Using proxyHost As ServiceHost = HostDiscoveryEndpoint(hostName)

        Using legacyHost As ServiceHost = HostLegacyEndpoint(hostName)

Console.WriteLine("Press <Enter> to exit")

Console.ReadLine()

proxyHost.Close()

            legacyHost.Close()

        End Using

End Using

End Sub

16.          按 Ctrl+Shift+B 生成解决方案。

               

下一步

练习 5:验证

               

练习 5:验证

1.            右键单击 DiscoveryProxy 项目并单击 Set as StartUp project。

2.            按 F5 调试解决方案。将启动 DiscoveryProxy 应用程序。

 

图 20

DiscoveryProxy 控制台主机

3.            切换回 Visual Studio Solution Explorer,右键单击相应项目,指向 Debug 并单击 Start New Instance,以启动一个 DiscoveryChat 应用程序实例以及一个 LegacyChat 应用程序实例。

4.            按如下方式设置聊天,登录到 Discovery Chat 窗口。

a.            User Name:Fred

b.            单击 Sign In

5.            登录到 Legacy Chat 窗口。为此,执行以下步骤。

a.            在命令提示符中,键入 login 并按回车键。

b.            在 Username 命令提示符中,键入 Wilma 并按回车键。

6.            现在应可以在用户列表中看到 Fred。跟 Fred 打个招呼。为此,执行以下步骤。

a.            在 Wilma 命令提示符中,键入 Say Fred Hello 并按回车键。

7.            在 Discovery Chat 应用程序中,应该会打开一个新的聊天窗口。执行以下步骤回复对方的消息。

a.            发送消息文本:Hello

b.            单击 Send

 

图 21

与新发现聊天应用程序聊天的旧聊天应用程序

8.            从旧聊天客户端中注销。为此,执行以下步骤。

a.            在 Wilma 命令提示符中,键入 bye 并按回车键。

b.            您应可以看到您已注销的消息。

 

图 22

DiscoveryProxy 主机从旧聊天客户端收到消息

9.            切换回 Visual Studio 并按 Shift+F5 停止调试。

               

下一步

总结

               

总结

在本实验中,您了解了如何在临时模式中发现服务,以及如何扩展发现消息以提供其他信息。在其他用户登录时,您使用公告来获得通知。同时,您还了解到无论服务是否位于同一网络中,只要使用发现代理对服务进行了注册,就可以通过托管发现找到这些服务。最后,您学习了如何使用旧客户端所用的自定义协议创建发现元数据,从而桥接聊天客户端与旧客户端。

    反馈

您对本实验有何看法?您对新的 Windows Communication Foundation 4 有何看法?您的反馈非常重要;它可以帮助我们为您构建最佳的产品。请花一点时间提供反馈。将您的评论发送到 wfwcfhol@microsoft.com