A simple solution for the Receiver Scope
There is a (bad) situation you can run into with this solution.
If you open more than one product page at the same time you'll get an error.
It tells you that a receiver with this name already exists.
The reason:
Receiver names have to be unique
And since the receiver sits in our master page we get two instances of it if we open more than one product page at the same time on the same client (or the same product page more than once).
The Receiver has two different constructors. One (which we used in this sample) takes only a name and the other allows us to give a namescope and a list (IEnumerable) of entries.
This looks like a solution for our problem - but it isn't because the namescope allows only two values: Global and Domain (default if we give only a name). With Global we can receive from multiple domains - but we have to take more care about the name. Otherwise our receiver could use the same name as a (running at the same time) receiver from a different domain.
Anyhow - since there is no "Same Page, Same Request, Same..." setting this wont help us.
There may be many approaches to solve our problem, but the Silverlight Messaging doesn't offer one out of the box.
The approach: our Receiver must get a unique name (on every browser window / tab) - and the Sender must know this name.
Something with the ASPX Session seems to be a good idea - but if you open the same page again from the browser (ctrl+N for an example) uses the same Session.
But if you remember how a page comes to the browser the solution is easy.
Every time you click a link (or use ctrl+N) a Request is sent to the server. The server checks what you are asking about (URL) assigns Session and other things and then it serves you with the content.
Such a request reaches the server at a specific time. Although I could imagine very seldom situations when two request from the same client reach the server at exactly the same time (assume a farm which distributes the request to different servers) it is very unlikely that this happens.
The solution: I choose is to use the time of the request as a (relative) unique marker.
We have access to this "Request-Time" via Context.Timestamp (DateTime).
The rest is easy - first we pass this information as a parameter to the Silverlight controls (Sender and Receiver).
Receiver (Masterpage):
| 1 |
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="200px" height="200px"> |
| 2 |
<param name="source" value="ClientBin/DataDisplay.xap" /> |
| 3 |
<param name="onError" value="onSilverlightError" /> |
| 4 |
<param name="background" value="#f0f0f0" /> |
| 5 |
<param name="minRuntimeVersion" value="3.0.40624.0" /> |
| 6 |
<param name="autoUpgrade" value="true" /> |
| 7 |
<param name="InitParams" value='<%= string.Format("TheID={0:x}", Context.Timestamp.Ticks)%>' /> |
| 8 |
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none"> |
| 9 |
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none" /> |
| 10 |
</a> |
| 11 |
</object> |
| 12 |
|
The change is in line 8 where we pass the Ticks of the Timestamp formatted as hexadecimal number (to have it a bit shorter).
And for the Senders (on every ProdX page):
| 1 |
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="300px" height="24px"> |
| 2 |
<param name="source" value="ClientBin/DataSelector.xap" /> |
| 3 |
<param name="onError" value="onSilverlightError" /> |
| 4 |
<param name="background" value="White" /> |
| 5 |
<param name="minRuntimeVersion" value="3.0.40624.0" /> |
| 6 |
<param name="autoUpgrade" value="true" /> |
| 7 |
<param name="InitParams" value='Product=THREE, <%= string.Format("TheID={0:x}", Context.Timestamp.Ticks)%>' /> |
| 8 |
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none"> |
| 9 |
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none" /> |
| 10 |
</a> |
| 11 |
</object> |
Here you find the changed code at line 7.
Next extract the parameters in the Sender Control (DataSelector):
| private void Application_Startup(object sender, StartupEventArgs e) { |
| if(e.InitParams != null) { |
| if(e.InitParams.ContainsKey("Product")) { |
| Resources.Add("Product", e.InitParams["Product"]); |
| } |
| else { |
| Resources.Add("Product", "NONE"); |
| } |
| |
| if(e.InitParams.ContainsKey("TheID")) { |
| Resources.Add("TheID", e.InitParams["TheID"]); |
| } |
| else { |
| Resources.Add("TheID", "0"); |
| } |
| |
| } |
| else { |
| Resources.Add("Product", "NONE"); |
| Resources.Add("TheID", "0"); |
| } |
| this.RootVisual = new MainPage(); |
| } |
And the Receiver (DataDisplay):
| private void Application_Startup(object sender, StartupEventArgs e) { |
| if(e.InitParams != null) { |
| if(e.InitParams.ContainsKey("TheID")) { |
| Resources.Add("TheID", e.InitParams["TheID"]); |
| } |
| else { |
| Resources.Add("TheID", "0"); |
| } |
| } |
| else { |
| Resources.Add("TheID", "0"); |
| } |
| this.RootVisual = new MainPage(); |
| } |
Again we assign a default when we get no TheID parameter.
The rest is very easy - we add this string "TheID" to the name of the receiver.
On the sender side it looks like this:
| public MainPage() { |
| InitializeComponent(); |
| m_lS = new LocalMessageSender("DataDisplay" + App.Current.Resources["TheID"].ToString()); |
| m_lS.SendCompleted += new EventHandler<SendCompletedEventArgs>(m_lS_SendCompleted); |
| } |
| |
The difference to the previous version is in the constructor where we add "TheID" to the base name "DataDisplay".
And last not least the things on the receiver:
| public MainPage() { |
| InitializeComponent(); |
| m_lRCV = new LocalMessageReceiver("DataDisplay" + App.Current.Resources["TheID"].ToString()); |
| m_lRCV.MessageReceived += new EventHandler<MessageReceivedEventArgs>(m_lRCV_MessageReceived); |
| } |
| |
This is the same as with the sender.
Since there is no guaranty that we always a unique timestamp let's handle this also.
The error (same name exists) doesn't occur when we create the receiver - it occurs when we start listening. So we have to handle this there:
| private void UserControl_Loaded(object sender, RoutedEventArgs e) { |
| rcInfo.Visibility = Visibility.Collapsed; |
| tbNoData.Visibility = Visibility.Visible; |
| try { |
| m_lRCV.Listen(); //throws an error if receiver exists |
| } |
| catch { |
| tbNoData.Text = "Please reload page"; |
| } |
| } |
If the receiver exists we ask the user to reload the page - that's it.
Manfred
And since this is now the new final page - again:
If you want to leave a comment you can do this at
my blog about the sample
Links:
download the telerik trial version of RadControls for Silverlight 3
download the project from the telerik Code Library (look for Silverlight Communication)
documentation on MSDN
Video about the LocalConnection APImade by
Tim Heuer
That video inspired me to build this sample
Visit my blog
Follow me on twitter