Condividi tramite


Communication Between Local Silverlight-Based Applications

Microsoft Silverlight will reach end of support after October 2021. Learn more.

Local messaging enables you to create communication channels between multiple Silverlight plug-ins running on a single computer. You typically host the plug-ins in a single Web page and use local messaging to coordinate their behavior. This enables you to create complex layouts that combine multiple Silverlight-based applications with content based on other technologies.

For example, suppose you want to provide a Silverlight-based navigation system for an otherwise HTML-based Web site. You can use Silverlight to create a menu bar and a sidebar, and use local messaging to keep their states synchronized. Another scenario is to create a Silverlight-based banner ad with an animated effect that crosses over to a sidebar ad, using local messaging to coordinate the transition. You can also use local messaging to establish communication between a Silverlight-based application in a Web page and another one running outside the browser.

This topic describes local messaging in the following sections:

  • Configuring Local Message Receivers and Senders

  • Sending Messages and Receiving Responses

  • Example Diagram

  • Advanced Scenarios and Troubleshooting

Configuring Local Message Receivers and Senders

To establish a local messaging channel, create a LocalMessageReceiver object in one application and a corresponding LocalMessageSender object in another application. The sender always sends the first message, although the receiver can respond, enabling two-way communication. You can also create both sender and receiver objects in two applications so that either one can send the first message.

When you create the receiver, you must give it a name that is unique either globally, or within the receiving application's host domain. If you specify a name only, it is scoped to the domain by default. When you create the sender, you must identify the corresponding receiver. If you specify a name only, the sender will assume that the receiver is hosted in the same domain, and is using a domain-scoped name.

Name scoping ensures that the receiver will receive only the messages intended for it. You can use the global scope if you do not want to restrict your applications to particular domains. However, this increases the possibility of name conflicts unless you choose names that are likely to be unique. Domain scoping enables you to choose simpler receiver names if you are familiar with all the Silverlight-based applications hosted on your domain.

The following code example shows a basic configuration where the receiving and sending applications are hosted in the same domain.

' In the receiving application:
Dim messageReceiver As New LocalMessageReceiver("receiver")

' In the sending application:
Dim messageSender As New LocalMessageSender("receiver")
// In the receiving application:
LocalMessageReceiver messageReceiver = new LocalMessageReceiver("receiver");

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

You can create multiple receiver and sender objects. Each receiver can receive messages from any number of senders. Each sender, however, can send messages only to the receiver identified in its constructor. Additionally, you can configure the receiver to receive messages only from specified sender domains, or from any domain.

The following example shows a more complex configuration where the receiving and sending applications can be hosted in the same domain or in different domains.

' In the receiving application:
Dim messageReceiver As New LocalMessageReceiver("receiver", _
    ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain)

' In the sending application:
Dim messageSender As New LocalMessageSender( _
    "receiver", LocalMessageSender.Global)
// In the receiving application:
LocalMessageReceiver messageReceiver =
    new LocalMessageReceiver("receiver",
    ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain);

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

In this example, the receiver is given a name in the global scope, and can receive messages from any domain. The sender also specifies the global scope. The global scope is useful so that the sender does not require knowledge of the receiver's domain. However, when you use the global scope, you should choose receiver names carefully to avoid the possibility of conflicts. For more information, see the Advanced Scenarios and Troubleshooting section later in this topic.

After you create the receiver and sender objects, add handlers for the LocalMessageReceiver.MessageReceived and LocalMessageSender.SendCompleted events to complete the configuration. These events are described in the next section.

When the receiver is fully configured, call the LocalMessageReceiver.Listen method. This method registers the receiver's identity and enables it to receive MessageReceived events. This method also throws a ListenFailedException if there is already a receiver registered with the same name and name scope. This can happen, for example, if you use a hard-coded receiver name and a user loads your host Web page into more than one browser window or tab at the same time. For more information, see the Advanced Scenarios and Troubleshooting section.

You cannot modify the receiver configuration after you call the Listen method. The receiver will continue receiving messages until you call its Dispose method.

The following code example shows the complete configuration details where the receiving and sending applications are hosted in the same domain.

' In the receiving application:
Dim messageReceiver As New LocalMessageReceiver("receiver")
AddHandler messageReceiver.MessageReceived, _
    AddressOf receiver_MessageReceived

Try

    messageReceiver.Listen()

Catch ex As ListenFailedException

    MessageBox.Show( _
        "Cannot receive messages." & Environment.NewLine & _
        "There is already a receiver with the name 'receiver'.", _
        "LocalMessageReceiver", MessageBoxButton.OK)

End Try

' In the sending application:
Dim messageSender As New LocalMessageSender("receiver")
AddHandler messageSender.SendCompleted, _
    AddressOf sender_SendCompleted
// 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);

Sending Messages and Receiving Responses

To send a message, the sending application calls the SendAsync method, passing in a String message with a maximum size of 40 kilobytes. If the message is successfully received, the LocalMessageReceiver.MessageReceived event occurs in the receiving application, and the message is available in the MessageReceivedEventArgs.Message property.

The receiving application can send a response from the MessageReceived event handler by setting the MessageReceivedEventArgs.Response property. To receive the response, the sending application can handle the LocalMessageSender.SendCompleted event and get the SendCompletedEventArgs.Response property.

The following code example shows a simple message and response exchange.

' In the sending application:
Private Sub SendMessage(ByVal messageSender As LocalMessageSender)

    MessageBox.Show("Sending message ""message"".", _
        "LocalMessageSender", MessageBoxButton.OK)
    messageSender.SendAsync("message")

End Sub

' In the receiving application:
Private Sub receiver_MessageReceived(ByVal sender As Object, _
    ByVal e As MessageReceivedEventArgs)

    MessageBox.Show("Message """ & e.Message & _
        """ received. Sending response ""response"".", _
        "LocalMessageReceiver", MessageBoxButton.OK)
    e.Response = "response"

End Sub

' In the sending application:
Private Sub sender_SendCompleted(ByVal sender As Object, _
    ByVal e As SendCompletedEventArgs)

    MessageBox.Show("Response """ & e.Response & """ receieved.", _
        "LocalMessageSender", MessageBoxButton.OK)

End Sub
// 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);
}

The sender is not required to handle the SendCompleted event. However, if it does, the event occurs regardless of whether the message is successfully received, and regardless of whether the receiver sends a response. If the message is not received, however, the AsyncCompletedEventArgs.Error property (inherited by the SendCompletedEventArgs class) will be set to a SendFailedException instance. This can occur, for example, if the specified receiver name has not been registered or if the receiver is not configured to receive messages from the sender's domain.

Example Diagram

The following diagram summarizes the behaviors described in the previous sections.

Local Messaging

The diagram illustrates the following example objects and interactions:

  • A LocalMessageReceiver object on domain1 is named receiver1, uses domain name scoping, and can receive messages only from domain1. This receiver successfully calls its Listen method at step 1.

  • A second LocalMessageReceiver on domain1 attempts to use the same name and name scope, causing the Listen method call to throw a ListenFailedException.

  • Two LocalMessageSender objects on domain1 are configured to send messages to receiver1 on domain1. These senders call SendAsync at step 2, causing the MessageReceived event on the receiver at step 3, followed by the SendCompleted event on the sender at step 4. Steps 2, 3, and 4 can occur multiple times. Each message and response cycle is distinct for each sender and each SendAsync method call, and do not necessarily occur at the same time.

  • A third LocalMessageSender object is configured to send messages to receiver1 on domain2. However, there is no receiver1 on domain2, so the SendAsync method call causes the SendCompleted event to occur with the Error property set to a SendFailedException. The failure would also occur if this sender were configured to send messages to receiver1 on domain1. This is because the sender is on domain2, but receiver1 is configured to receive messages only from domain1.

Advanced Scenarios and Troubleshooting

Local messaging is simple to use in most common scenarios. However, there are some situations that require additional code, or are not supported. This section describes the following tasks:

  • Identifying messages in the SendCompleted event handler.

  • Avoiding cross-domain issues.

  • Sending complex messages.

  • Sending messages at application startup.

  • Avoiding name conflicts with multiple instances of a single Web application.

Identifying messages in the SendCompleted event handler

In some cases, you will need to send multiple messages from a LocalMessageSender object, and match the SendCompleted event with the corresponding message. However, the SendCompleted events are not guaranteed to occur in the same order as the SendAsync method calls. Additionally, if the messages are not unique, you cannot use the message text to identify the specific messages.

In this scenario, you can call the SendAsync method and pass a user state object along with the message. In the SendCompleted event handler, you can then get the value of the AsyncCompletedEventArgs.UserState property (inherited by the SendCompletedEventArgs class). The user state object can be any object of your choosing, such as a string or integer ID. Note, however, that the LocalMessageReceiver does not receive the user state object, so you cannot use it as part of the message.

Avoiding cross-domain issues

When the sending and receiving applications are hosted on different domains, there may be security issues that prevent local messaging, or require additional code.

First, local messaging is only possible between domains using the same URI scheme. For example, a sender hosted at an HTTP URI cannot send messages to a receiver hosted at an HTTPS URI.

Additionally, in Internet Explorer, the hosts of the sending and receiving applications must reside in the same security zone. You can disable this restriction, however, by setting the LocalMessageReceiver.DisableSenderTrustCheck property to true. This is useful, for example, to establish communication between a Web-hosted application in the Internet zone and an out-of-browser application in the Trusted zone. For more information about out-of-browser applications, see Out-of-Browser Support.

With cross-domain local messaging, you will sometimes need to take extra precautions to avoid name conflicts or message hijacking. Remember that the first application that registers a receiver name within a particular name scope will receive the messages intended for that identity. This could cause problems if an unknown application with the same identity is already open in another browser window or tab when your application is loaded.

If you use the global name scope, you should avoid using common receiver names. If security is an issue, you should consider using domain name scoping and restrict the domains that the receiver can receive from. Finally, you should avoid sending sensitive information through a local messaging channel unencrypted.

Sending complex messages

With local messaging, you can send any string up to 40 kilobytes in length. In many situations, the message will only need to be a simple notification or a simple set of values that the receiver can parse and interpret easily. However, within the 40 kilobyte limit, you can send arbitrarily complex messages, including serialized objects and encrypted messages.

Silverlight provides APIs for serializing and deserializing XML or JSON data. These are common data formats when working with Web services. Although local messaging takes place entirely on a single computer, you can use these data formats the same way you would with a Web service.

Working with XML data is particularly easy because Silverlight includes LINQ to XML. However, XML data consumes more space, so if you are serializing large objects that may exceed the 40 kilobyte limit, consider using JSON. For more information, see XML Data and Working with JSON Data.

Silverlight provides APIs for cryptography in the System.Security.Cryptography namespace. For more information, see Cryptographic Services in Silverlight.

Sending messages at application startup

Depending on their sizes, your sending and receiving applications might finish loading at different times. Even if both applications are small, there is no guarantee that the receiving application will be available when the sending application is ready to send. If you need to send a local message at application startup time, you must take this into consideration.

One way to work around this issue is to have the sending application send the message repeatedly until it is received. You can do this by resending the message in the SendCompleted event handler if the AsyncCompletedEventArgs.Error property is not null. This technique is used by the example code in How to: Implement Communication Between Local Silverlight-Based Applications.

Another approach is use the HTML Bridge feature to communicate through the host Web page, and notify the sending application when the receiving application has loaded. Of course, you could use the HTML Bridge as a communication channel instead of using local messaging. However, local messaging is typically more convenient after the sender has established that the receiver is available and ready to receive messages. Note also that the HTML Bridge feature requires a host HTML page, so it will not work with out-of-browser applications. For more information about the HTML Bridge, see HTML Bridge: Interaction Between HTML and Managed Code.

Avoiding name conflicts with multiple instances of a single Web application

In some cases, you might want to enable users to run more than one instance of your Web application at a time. In this case, you need additional code to ensure that your LocalMessageReceiver instances have unique names.

As mentioned previously, you can handle the ListenFailedException thrown by the LocalMessageReceiver.Listen method to determine whether a receiver name is already registered. This is useful if you want to simply disable local messaging for additional instances of your Web application. If you want additional instances to run normally, however, you must generate unique receiver names for those instances.

In this case, you can generate the receiver names in JavaScript, using the current date and time to ensure uniqueness. You can then retrieve the generated names in the sending and receiving applications through the HTML Bridge feature.

Version Notes

Silverlight for Windows Phone Silverlight for Windows Phone does not support local messaging.