通过本地消息传递,您可以在单台计算机上运行的多个 Silverlight 插件之间创建通信通道。您通常在单个网页中承载这些插件,并且使用本地消息传递来协调其行为。这样,您可以创建将多个基于 Silverlight 的应用程序与基于其他技术的内容结合在一起的复杂布局。

例如,假定您要为基于 HTML 的网站提供基于 Silverlight 的导航系统。您可以使用 Silverlight 来创建菜单栏和侧栏,并且使用本地消息传递来保持其状态同步。另一个方案就是创建具有跨越侧栏广告的动画效果的基于 Silverlight 的横幅广告,并且使用本地消息传递来协调过渡。您还可以使用本地消息传递在网页中的基于 Silverlight 的应用程序和浏览器外运行的其他应用程序之间建立通信。

本主题在以下几节中介绍本地消息传递:

  • 配置本地消息接收器和发送器

  • 发送消息和接收响应

  • 示例关系图

  • 高级方案和疑难解答

配置本地消息接收器和发送器

要建立本地消息传递通道,请在一个应用程序中创建一个 LocalMessageReceiver 对象,在另一个应用程序中创建相应的 LocalMessageSender 对象。发送器始终发送第一个消息,不过接收器可能响应,并且实现双向通信。您还可以在两个应用程序中都创建发送器和接收器对象,以便其中一个可以发送第一个消息。

在您创建接收器时,必须为其提供或者在全局范围内唯一的名称,或者在接收应用程序的宿主域内唯一的名称。如果您仅指定名称,则默认情况下其范围限于域。在您创建发送器时,必须标识相应的接收器。如果您仅指定名称,则发送器将认为接收器与其承载在同一域中,并且正在使用域范围的名称。

名称范围确保接收器将只接收其期望的消息。如果您不想将应用程序限制为特定的域,则可以使用全局范围。但是,这样做会增加名称冲突的可能性,除非您选择可能唯一的名称。如果您对在域中承载的所有基于 Silverlight 的应用程序都十分熟悉,则域范围使您能够选择更简单的接收器名称。

下面的代码示例演示一个基本的配置,在该配置中接收应用程序和发送应用程序承载在同一域中。

 
C#复制代码
// In the receiving application:
LocalMessageReceiver messageReceiver = new LocalMessageReceiver("receiver");

// In the sending application:
LocalMessageSender messageSender = new LocalMessageSender("receiver");

您可以创建多个接收器和发送器对象。每个接收器都可以接收来自任意数目的发送器的消息。但是,每个发送器只能将消息发送到在其构造函数中标识的接收器。此外,您可以将接收器配置为只接收来自指定发送器域的消息,或者配置为接收来自任何域的消息。

下面的示例演示一个更复杂的配置,在该配置中接收应用程序和发送应用程序可以承载在同一域中或者不同的域中。

 
C#复制代码
// In the receiving application:
LocalMessageReceiver messageReceiver =
    new LocalMessageReceiver("receiver",
    ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain);

// In the sending application:
LocalMessageSender messageSender = new LocalMessageSender(
    "receiver", LocalMessageSender.Global);

在该示例中,在全局范围向接收器提供一个名称,并且接收器可以接收来自任何域的消息。发送器也指定全局范围。全局范围很有用,这样,发送器无需知道接收器的域。但是,在您使用全局范围时,应仔细选择接收器名称,以便避免可能的冲突。有关更多信息,请参见本主题后面的"高级方案和疑难解答"一节。

在创建接收器和发送器对象后,为 LocalMessageReceiver..::..MessageReceived 和 LocalMessageSender..::..SendCompleted 事件添加处理程序以便完成配置。这些事件在下一节中讨论。

在完全配置好接收器后,调用 LocalMessageReceiver..::..Listen 方法。此方法注册接收器的标识并使其能够接收 MessageReceived 事件。如果已经存在用相同名称和名称范围注册的接收器,此方法还引发 ListenFailedException。例如,如果您使用一个硬编码的接收器名称并且用户将您的宿主网页同时加载到多个浏览器窗口或选项卡中,将可能会引发上述异常。有关更多信息,请参见"高级方案和疑难解答"一节。

在调用 Listen 方法后不能修改接收器配置。接收器将继续接收消息,直到调用其 Dispose 方法。

下面的代码示例演示完整的配置详细信息,在该配置中接收应用程序和发送应用程序承载在同一域中。

 
C#复制代码
// In the receiving application:
LocalMessageReceiver messageReceiver = new LocalMessageReceiver("receiver");
messageReceiver.MessageReceived += new 
    EventHandler<MessageReceivedEventArgs>(receiver_MessageReceived);
try
{
    messageReceiver.Listen();
}
catch (ListenFailedException)
{
    MessageBox.Show(
        "Cannot receive messages." + Environment.NewLine +
        "There is already a receiver with the name 'receiver'.",
        "LocalMessageReceiver", MessageBoxButton.OK);
}

// In the sending application:
LocalMessageSender messageSender = new LocalMessageSender("receiver");
messageSender.SendCompleted += new 
    EventHandler<SendCompletedEventArgs>(sender_SendCompleted);

发送消息和接收响应

要发送消息,发送应用程序调用 SendAsync 方法,传入最大 40 KB 的 String 消息。如果该消息被成功接收,则在接收应用程序中将发生 LocalMessageReceiver..::..MessageReceived 事件,并且该消息可用于 MessageReceivedEventArgs..::..Message 属性中。

接收应用程序可以通过设置 MessageReceivedEventArgs..::..Response 属性,发送来自 MessageReceived 事件处理程序的响应。为了接收该响应,发送应用程序可以处理 LocalMessageSender..::..SendCompleted 事件并获取 SendCompletedEventArgs..::..Response 属性。

下面的代码示例显示一个简单的消息和响应交换。

 
C#复制代码
// In the sending application:
private void SendMessage(LocalMessageSender messageSender)
{
    MessageBox.Show("Sending message \"message\".",
        "LocalMessageSender", MessageBoxButton.OK);
    messageSender.SendAsync("message");
}

// In the receiving application:
private void receiver_MessageReceived(object sender,
    MessageReceivedEventArgs e)
{
    MessageBox.Show("Message \"" + e.Message +
        "\" received. Sending response \"response\".",
        "LocalMessageReceiver", MessageBoxButton.OK);
    e.Response = "response";
}

// In the sending application:
private void sender_SendCompleted(object sender, 
    SendCompletedEventArgs e)
{
    MessageBox.Show("Response \"" + e.Response + "\" receieved.",
        "LocalMessageSender", MessageBoxButton.OK);
}

发送器无需处理 SendCompleted 事件。但如果需要,则无论是否已成功接收消息,并且无论接收器是否已发送响应,该事件都会发生。但是,如果未接收该消息,则 AsyncCompletedEventArgs..::..Error 属性(由 SendCompletedEventArgs 类继承)将被设置为 SendFailedException 实例。例如,如果指定的接收器名称尚未注册,或者接收器未配置为从发送器的域接收消息,就可能会发生上述情况。

示例关系图

下面的关系图汇总了在前面几节中描述的行为。


该关系图阐释以下示例对象和交互:

  • domain1 上的 LocalMessageReceiver 对象命名为 receiver1,使用域名范围,并且只能从 domain1 接收消息。此接收器在步骤 1 中成功调用其 Listen 方法。

  • domain1 上的第二个 LocalMessageReceiver 尝试使用相同的名称和名称范围,这导致 Listen 方法调用引发 ListenFailedException。

  • domain1 上的两个 LocalMessageSender 对象配置为将消息发送到 domain1 上的 receiver1。这些发送器在步骤 2 调用 SendAsync,这导致在步骤 3 在接收器上发生 MessageReceived 事件,后随步骤 4 在发送器上发生的 SendCompleted 事件。步骤 2、3、4 可多次发生。对于每个发送器和每个 SendAsync 方法调用,每个消息和响应周期都是不同的,并且不一定同时发生。

  • 第三个 LocalMessageSender 对象配置为将消息发送到 domain2 上的 receiver1。但是,在 domain2 上没有 receiver1,因此,SendAsync 方法调用导致 SendCompleted 事件发生,并且 Error 属性设置为 SendFailedException。如果此发送器配置为将消息发送到 domain1 上的 receiver1,也可能会发生这一失败。这是因为发送器位于 domain2,但 receiver1 配置为仅从 domain1 接收消息。

高级方案和疑难解答

在大多数常见情况中,本地消息传递使用起来很简单。但是,有一些情况需要编写额外的代码,或者不支持本地消息传递。本节介绍了以下任务:

  • 在 SendCompleted 事件处理程序中标识消息。

  • 避免跨域问题。

  • 发送复杂消息。

  • 当应用程序启动时发送消息。

  • 避免与单个 Web 应用程序的多个实例发生名称冲突。

在 SendCompleted 事件处理程序中标识消息

在某些情况下,您将需要从 LocalMessageSender 对象发送多个消息,并且将 SendCompleted 事件与相应的消息进行匹配。但是,不保证 SendCompleted 事件与 SendAsync 方法调用采用相同的顺序发生。此外,如果消息不唯一,则您无法使用消息文本来标识特定的消息。

在这种情况下,您可以调用 SendAsync 方法并随同消息传递一个用户状态对象。在 SendCompleted 事件处理程序中,您可以获取 AsyncCompletedEventArgs..::..UserState 属性(由 SendCompletedEventArgs 类继承)的值。该用户状态对象可以是您选择的任何对象,例如字符串或整数 ID。但要注意,LocalMessageReceiver 不接收该用户状态对象,因此您无法将其用作消息的一部分。

避免跨域问题

当发送应用程序和接收应用程序在不同的域上承载时,可能存在阻止本地消息传递的安全问题,或者要求附加代码。

首先,本地消息传递只能在使用相同 URI 方案的域之间发生。例如,在 HTTP URI 承载的发送器不能将消息发送到在 HTTPS URI 承载的接收器。

此外,在 Internet Explorer 中,发送应用程序和接收应用程序的宿主必须驻留在同一安全区域中。不过,可以通过将 LocalMessageReceiver..::..DisableSenderTrustCheck 属性设置为 true,禁用此限制。这一禁用在某些情况下很有用,例如,在 Internet 区域的 Web 承载的应用程序和信任区域的浏览器外应用程序之间建立通信。有关浏览器外应用程序的更多信息,请参见浏览器外支持。

对于跨域本地消息传递,有时候需要特别注意,以便避免名称冲突或消息截获。请记住,在特定名称范围内注册接收器名称的第一个应用程序将接收用于该标识的消息。如果在加载您的应用程序时具有相同标识的未知应用程序已在其他浏览器窗口或选项卡中打开,则上述情况可能会导致问题。

如果您使用全局名称范围,则应避免使用常见的接收器名称。如果安全存在问题,则应考虑使用域名范围并限制接收器可以从其接收的域。最后,您应该避免通过未加密的本地消息传递通道发送敏感信息。

发送复杂消息

使用本地消息传递,您可以发送最长 40 KB 的任何字符串。在许多情况下,消息只需是简单的通知,或者是接收器可以轻松地分析和解释的值的简单集合。但在这个 40 KB 的限制内,您可以发送任意复杂消息,包括序列化对象和加密的消息。

Silverlight 提供 API 以便对 XML 或 JSON 数据进行序列化和反序列化。它们是在使用 Web 服务时的常见数据格式。尽管本地消息传递完全在单台计算机上发生,但您仍可以采用与 Web 服务相同的方式使用这些数据格式。

使用 XML 数据特别容易,因为 Silverlight 包括 LINQ to XML。但是,XML 数据占用更多的空间,因此,如果您序列化可能超出 40 KB 限制的大对象,则考虑使用 JSON。有关更多信息,请参见 XML 数据和 Working with JSON Data(使用 JSON 数据)。

Silverlight 为 System.Security.Cryptography 命名空间中的加密提供 API。有关更多信息,请参见 Silverlight 中的加密服务。

当应用程序启动时发送消息

根据程序大小,您的发送应用程序和接收应用程序可能会在不同时间完成加载。即使这两个应用程序都很小,也不保证在发送应用程序可供发送时接收应用程序可用。如果您需要在应用程序启动时发送本地消息,则必须考虑这个情况。

解决这个问题的方法之一就是让发送应用程序反复发送消息,直到它被接收。如果 AsyncCompletedEventArgs.Error 属性不是 null,您可以通过在 SendCompleted 事件处理程序中重新发送该消息,进行上述的反复发送。这一方法在如何在基于 Silverlight 的本地应用程序之间实现通信的示例代码中使用。

另一种方法是使用 HTML Bridge 功能通过宿主网页进行通信,并且在接收应用程序已加载时通知发送应用程序。当然,您可以使用 HTML Bridge 作为通信通道,来代替使用本地消息传递。但是,在发送器已确定接收器可用并且可供接收消息后,本地消息传递通常更方便。还要注意,HTML Bridge 功能要求宿主 HTML 页,因此无法用于浏览器外应用程序。有关 HTML Bridge 的更多信息,请参见 HTML Bridge:HTML 和托管代码之间的交互。

避免与单个 Web 应用程序的多个实例发生名称冲突

在某些情况下,您可能要使用户能够同时运行您的 Web 应用程序的多个实例。在此情况下,您需要额外的代码来确保您的 LocalMessageReceiver 实例具有唯一名称。

如上所述,您可以处理 LocalMessageReceiver..::..Listen 方法引发的 ListenFailedException,以便确定接收器名称是否已注册。如果您只是要针对您的 Web 应用程序的附加实例禁用本地消息传递,则上述方法很有用。但是,如果您希望附加实例正常运行,则必须为这些实例生成唯一接收器名称。

在这种情况下,您可以在 JavaScript 中生成接收器名称,并且使用当前日期和时间来确保唯一性。然后,您可以通过 HTML Bridge 功能检索发送应用程序和接收应用程序中生成的名称。

posted on 2012-08-30 18:56  寒舞逸  阅读(484)  评论(0)    收藏  举报