Note – these posts are put together after a short time with Silverlight 4 as a way of providing pointers to some of the new features that Silverlight 4 has to offer. I’m posting these from the PDC as Silverlight 4 is announced for the first time so please bear that in mind when working through these posts.
For whatever reason ( most likely it’s old age 🙂 ), I find it very hard to remember what you can/can’t do in Silverlight’s networking stack and so before I started to look at what Silverlight 4 has to offer I wanted to try and recap ( if only for myself ) what Silverlight 3 can already do.
Silverlight 3 Capabilities ( Mike’s Review – “Not Exactly Guaranteed to be Complete or Correct” )
Starting at the bottom of the networking stack and working upwards and remembering that all network support in Silverlight is asynchronous;
Sockets
Silverlight 3 supports opening a socket to a server somewhere. Regardless of whether the request goes to the site-of-origin or cross-domain Silverlight will first attempt to download a cross-domain security policy file from the server on port 943. If it can’t get that file or if that file disallows access to the specific client then it’s game over.
If the security system does allow communication then you get to open a ( IPV4/IPV6 ) socket ( TCP is the only protocol supported here ) to a port in the range 4502-4534 and write asynchronous socket code. I wrote about this a little back when Silverlight 2 was in beta ( so it might be a little out of date ).
Plain Old Http
In terms of calling out across the network, there’s a couple of HTTP classes to help with that in Silverlight 3. You have the simpler WebClient class and the more functional HttpWebRequest class.
- WebClient doesn’t expose too much but is good for a lot of cases – you’ve got UploadStringAsync/DownloadStringAsync which work with strings and then OpenWriteAsync/OpenReadAsync which provide access to streams to write/read. It does GET for download and POST for upload. You can get notifications as data is downloaded/uploaded and you can alter the Encoding for text. You can also get to Headers but quite a lot of these are restricted from your access.
- HttpWebRequest/HttpWebResponse expose more. You can get to both the request and the response stream. A bunch of headers are exposed directly such as Accept and Method on the request and Content-Type and Content-Length on the response. There’s then a Headers collection again for additional headers but, again, a lot of these are restricted. There’s also a CookieContainer property for dealing with cookies but that takes us on to a fork in the road which I’ll talk about in the following text…
It’s worth saying that all of this access is subject to cross-domain security policy in that they’ll always work back to the site-of-origin but if you call some other server, Silverlight will first attempt to download a cross-domain security policy file from the domain that hosts the resource and if that’s not there or doesn’t allow the access then the request fails. It’s also subject to cross-scheme security in that there are restrictions about jumping from HTTPS to HTTP.
In Silverlight 2 these classes were reasonably restricted because they were piggy-backing onto the network stack exposed by the browser and that limits the functionality – for example, the only verbs available were GET and POST which makes use of RESTful services tricky without tunnelling the other verbs ( PUT, DELETE, HEAD ). As another example, the browser is controlling cookies and so you can’t do anything there either which can be tricky. A final example is that there’s limited access to the HTTP status codes you get back from the server which has implications for HTTP/REST and also for SOAP calls.
In Silverlight 3 a second network stack was introduced – the client network stack. This allows access to the other HTTP verbs, makes cookies available and offers status codes. You can control whether you’re using the browser stack or the network stack by using the HttpWebRequest.RegisterPrefix method to register which network stack will be used for a particular prefix (e.g. “http://” or “http://www.microsoft.com”) or you can use WebRequestCreator.ClientHttp.Create() and WebRequestCreator.BrowserHttp.Create() to do this on a one-off basis.
There are still limitations around this client HTTP stack with perhaps the primary one that comes to mind being the lack of ability to set authentication headers for basic/digest authentication over HTTPS or for Windows authentication.
That needs a little more explanation – if I put the Silverlight application or some of its resources behind a site which uses integrated authentication then at the point where the browser goes off and asks for either the application or where the application code ( via WebClient or HttpWebRequest ) goes to grab some resource using the browser’s network stack then the browser will throw a dialog asking me to authenticate with the site and that’ll work whether I’m running the code in-browser or out-of-browser ( although in the out-of-browser case I tend to worry a bit because I feel that there may be times when I want to try and clear any cached stuff that the browser is sending and it’s not-so-obvious how you do that without launching a separate browser window ).
However, if I switch to the client networking stack ( which is the more complete stack ) and then request a resource sitting behind [basic/digest/Windows] authentication it won’t work because that stack will not throw up a dialog asking me to authenticate and nor is there any programmatic way to set the credentials to use. Given that the client network stack is the more functional ( especially for RESTful service access ) it’s limiting not to have that feature.
Additionally, if the browser is navigated to some page that does form-based authentication storing a session cookie and then moves on to a page that loads a Silverlight application then any requests made by that Silverlight application using the browser network stack will transfer the cookie and so will be authenticated. However, any requests using the client network stack will not share those cookies and there’s no way to get at them and so, consequently, that kind of Silverlight application would have to ask the user to authenticate again in order to populate its own cookie on the client network stack.
Just to illustrate that. If I’ve got a little website as below;
where I have default.aspx linking to TestPage.html which launches my silverlight XAP ( ClientBin/SilverlightApplication40.xap ) and my XAP contains a little XAML UI;
<UserControl x:Class="SilverlightApplication40.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock x:Name="myTextBlock" /> <Button Grid.Row="1" Content="Get Secure Content" Click="OnGetSecureContent" /> </Grid> </UserControl>and some code which causes the client networking stack to attempt to load the SecretPage.htm file dynamically;
private void OnGetSecureContent(object sender, RoutedEventArgs e) { // Use the client networking stack. HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp); WebClient client = new WebClient(); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential("username", "password", "localhost"); client.DownloadStringCompleted += (s, args) => { if (args.Error == null) { myTextBlock.Text = args.Result; } else { myTextBlock.Text = "FAIL!"; } }; client.DownloadStringAsync(new Uri("http://localhost/WebSite2/Secure/SecretPage.htm", UriKind.Absolute)); }then if I have ASP.NET forms authentication switched on at the Secure folder level then when I visit default.aspx I see my link to my Silverlight app;
and if I follow that hyperlink to TestPage.html ( which is behind ASP.NET forms auth ) I get;
and if I follow that authentication through I get to my Silverlight app;
and clicking on the “Get Secure Content” button gets me to an “interesting place”;
the HTML that has been returned in response to my request for SecretPage.htm is not actually the contents of that page. The request has been redirected to the ASP.NET login page and it’s the HTML of that page that has been returned to me.
Why? Because the browser network stack has stored an authentication cookie but I’m making my request on the client network stack which doesn’t see that cookie and there’s no way to get the cookie from the one place to the other so the Silverlight code is going to have to re-authenticate the user probably by calling the ASP.NET membership services as a web service call made from the client networking stack.
So, in Silverlight 3 there’s some juggling around here based on the idea that the client network stack is generally the more functional and yet it’s missing one or two clever tricks from the browser equivalent plus it can’t share some of the information that the browser stack already has available to it.
WCF
WCF in Silverlight 3 is a subset of what you see in the full .NET Framework and a subset of what you might call the “client” side of WCF. You get;
- Transport – HTTP and HTTPS which sit on top of either the client or browser network stack – depending on what is in play.
- Protocols – SOAP – support is there for SOAP1.1, 1.2 and WS-Addressing 1.0 along with a subset of WS-Security that allows you to do “TransportWithMesssageCredential” type security where you put a user name and password into a message header and send it over HTTPS. If you’re using the browser network stack then SOAP faults are tricky to deal with. If you switch to the client network stack then you can pick up SOAP faults but you’re back into the areas of the client network stack that have limitations.
- Encoding – there’s support for text encoding ( open standards ) and binary encoding ( proprietary ) but not support for MTOM ( open standards binary ).
- Serialization – DataContractSerializer is there as is the XmlSerializer but these have limitations over what you’d see in the full .NET Framework. There’s also a DataContractJsonSerializer present but I think that’s for custom scenarios.
- Syndication – there’s the classes for working specifically with RSS and AtomFeeds ( SyndicationFeed and friends ).
- Finally – there’s also the PollingDuplex transport which provides the duplex programming model of WCF over an HTTP connection which calls the server on a “long-poll” holding a connection open for a while to see if there is any data for the client and then closing it and connecting again on the poll interval.
I’m sure I missed a bunch of stuff or didn’t write it down correctly but that’s my brief picture of what’s present in Silverlight 3. Moving on…
Silverlight 4 Capabilities
Trusted/Elevated Applications – Relaxed Networking Sandbox
The first thing that occurs to me around Silverlight 4 is that in an Trusted Silverlight 4 application ( i.e. one that’s running elevated out of the brower ) a lot of the security restrictions go away.
Examples;
- I can use WebClient to go download over HTTP without worrying about whether there’s a cross-domain policy or not. For example, hitting content on Microsoft.com;
WebClient client = new WebClient(); client.DownloadStringCompleted += (a, b) => { }; client.DownloadStringAsync(new Uri("http://www.microsoft.com"));
- similarly, I can do the same thing with HttpWebRequest …
HttpWebRequest request = (HttpWebRequest)WebRequest.Create( new Uri("http://www.microsoft.com")); request.BeginGetResponse(iar => { try { HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(iar); using (StreamReader reader = new StreamReader(response.GetResponseStream())) { string text = reader.ReadToEnd(); } response.Close(); } catch { } }, null);
and that looks to work fine for me regardless of whether I’m using the browser’s networking stack or the client networking stack.
So, if you’ve got a trusted application then the networking restrictions on HTTP access here fall away and you can go and grab whatever resources you want from whatever location you want.
What about sockets? At the time of writing they still require a cross-domain security policy file on port 943 – not sure whether this will change after the beta ( personally, I’d love to see it change but it’s just “fingers crossed” at this point ).
Authentication with the Client Networking Stack
As discussed in my earlier review, the client networking stack is really “where it’s at” in terms of offering full HTTP verbs and response codes and so on but it’s slightly hamstrung right now by its lack of things like a means for authentication headers. In version 4, that gets resolved.
So, if I make myself a little website and Silverlight app;
where that SecretPage.htm simply contains the HTML for “Hello World” and then I write a little Silverlight UI using the new WebBrowser control ( which is only applicable to out-of-browser scenarios – I’ve posted previously on this );
<UserControl x:Class="SilverlightApplication40.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <WebBrowser x:Name="myWebBrowser" /> <Button Grid.Row="1" Content="Get Secure Content" Click="OnGetSecureContent" /> </Grid> </UserControl>
and some code that runs behind it using WebClient on the Client networking stack;
private void OnGetSecureContent(object sender, RoutedEventArgs e) { // Use the client networking stack. HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp); WebClient client = new WebClient(); client.DownloadStringCompleted += (s, args) => { if (args.Error == null) { myWebBrowser.NavigateToString(args.Result); } else { myWebBrowser.NavigateToString("<html><body>FAIL!</body></html>"); } }; client.DownloadStringAsync(new Uri("http://localhost/WebSite2/Secure/SecretPage.htm", UriKind.Absolute)); }
where you can see that I’m just trying to access the content of that Secure/SecretPage.htm using the Client networking stack and drop it into the WebBrowser control should we be successful in loading it.
In IIS, if I set that folder called Secure to disallow all methods of authentication ( including anonymous and forms ) other than Windows authentication;
then when I click the button on my client to access the resource I see;
and if I log in correctly then my code will run and populate the WebBrowser with the resultant HTML;
Neat. I can do the same trick if I enable Basic/Digest authentication – all works fine ( I admit I didn’t try digest as IIS wanted me to join my VPC image to a domain but I assume it’s fine ) and so authentication over that client network stack is working fine.
It’d be fairly common to avoid that dialog altogether by actually supplying the credentials from the calling code ( probably after getting them from the user via Silverlight UI rather than a browser prompt ) and I can do that;
private void OnGetSecureContent(object sender, RoutedEventArgs e) { // Use the client networking stack. HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp); WebClient client = new WebClient(); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential("username", "password", "localhost"); client.DownloadStringCompleted += (s, args) => { if (args.Error == null) { myWebBrowser.NavigateToString(args.Result); } else { myWebBrowser.NavigateToString("<html><body>FAIL!</body></html>"); } }; client.DownloadStringAsync(new Uri("http://localhost/WebSite2/Secure/SecretPage.htm", UriKind.Absolute)); }
and that works a treat and I get straight through to my authentication content without having to go through browser prompt for credentials and the same would be true if I was working with HttpWebRequest rather than with WebClient.
Multicast Enhancements
I spotted two new classes in System.Net.Sockets – namely UdpSingleSourceMulticastClient and UdpAnySourceMulticastClient and so I thought I’d give them a whirl.
UDP multicasting is not something that I know a “whole lot” about so bear that in mind. However, the support here essentially allows you subject to security policy to have Silverlight clients join a UDP multicast group and so you can fire UDP packets into that multicast group ( subject to understanding all the stuff about UDP being sessionless and so unreliable with a much greater need for application level ACKs and so on which I won’t do in my code here ) and have Silverlight clients pick them up which is pretty exciting stuff in you’re thinking about running Silverlight applications on a LAN.
In order to make this work, the UDP sockets need security policy just like TCP sockets do in Silverlight so you’re going to need a different security policy server ( as far as I know at the time of writing ).
I haven’t looked at the exact details of this yet but in order to get your UDP stuff to work for a particular multi-cast group address like 239.0.0.1 you’re going to need to have a policy server listening on UDP Port 9430 (IPV4/V6) for UDP packets from Silverlight clients asking for a policy.
At the time of writing, the request for permission and the response to that request both look to be a chunk of binary ( rather than, say, serialized XML ) – whether this is a temporary thing in the preview I’m not sure but I’d imagine that by the time I publish this there will be a sample policy responder made available for people to use/alter to make it a lot easier.
However, the point is that there is an initial step to ascertain Security policy before the Silverlight networking stack allows the client to join a UDP multicast group just like there is with TCP sockets. Once that’s out of the way, code can start to run and interact.
I tried to put together an example of a Silverlight client that launches, takes a name ( to identify itself ) and then starts publishing some piece of data every 5 seconds or so to a multicast group so that all others in the group pick it up and display it. Something like this;
<UserControl x:Class="SilverlightApplication44.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBlock Margin="5" VerticalAlignment="Center" Text="This application's unique identifier:" /> <TextBox Margin="5" Text="{Binding AppId, Mode=TwoWay, FallbackValue=Not Set}" MinWidth="192" /> </StackPanel> <dg:DataGrid Margin="5" Grid.Row="1" ItemsSource="{Binding ReceivedMessages}" /> <StackPanel Orientation="Horizontal" Grid.Row="2"> <Button Content="Start Receiving" Margin="5" Click="OnStartReceiving" /> <Button Content="Start Sending" Margin="5" Click="OnStartSending" /> </StackPanel> </Grid> </UserControl>
with some code behind it;
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Net.Sockets; using System.Text; using System.Windows.Threading; using System.Collections.ObjectModel; namespace SilverlightApplication44 { public partial class MainPage : UserControl { public ObservableCollection<DisplayMessage> ReceivedMessages { get; set; } public string AppId { get; set; } public MainPage() { InitializeComponent(); ReceivedMessages = new ObservableCollection<DisplayMessage>(); AppId = "Not set"; this.Loaded += (s, e) => { this.DataContext = this; }; } void OnStartReceiving(object sender, RoutedEventArgs e) { CreateClient(() => { ReceiveMessage(); }); } void ReceiveMessage() { byte[] buffer = new byte[256]; client.BeginReceiveFromGroup(buffer, 0, buffer.Length, result => { IPEndPoint sourceEndPoint; // may throw... int read = client.EndReceiveFromGroup(result, out sourceEndPoint); // todo - avoid our own messages DisplayMessage message = DisplayMessage.FromBytes(buffer); message.SourceIPEndPoint = sourceEndPoint.ToString(); AddMessageToReceivedList(message); // and receive again ReceiveMessage(); }, null); } void AddMessageToReceivedList(DisplayMessage message) { Dispatcher.BeginInvoke(() => { ReceivedMessages.Add(message); }); } void OnStartSending(object sender, RoutedEventArgs e) { CreateClient(() => { if (timer == null) { timer = new DispatcherTimer(); timer.Interval = new TimeSpan(0, 0, 0, 5); timer.Tick += OnTimerTick; timer.Start(); } }); } void OnTimerTick(object sender, EventArgs e) { Message message = new Message() { AppId = this.AppId, MessageText = String.Format("Sent message at {0}", DateTime.Now.ToLongTimeString()) }; byte[] buffer = message.ToBytes(); client.BeginSendToGroup(buffer, 0, buffer.Length, result => { client.EndSendToGroup(result); // may throw }, null); } void CreateClient(Action action) { if (client == null) { client = new UdpAnySourceMulticastClient(hardcodedIpAddress, hardcodedIpPort); client.BeginJoinGroup(result => { client.EndJoinGroup(result); // may well throw Dispatcher.BeginInvoke(action); }, null); } else { action(); } } DispatcherTimer timer; UdpAnySourceMulticastClient client; readonly IPAddress hardcodedIpAddress = IPAddress.Parse("239.255.0.1"); const int hardcodedIpPort = 32768; } }
and one or two extra classes;
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.IO; namespace SilverlightApplication44 { public class Message { public string AppId { get; set; } public string MessageText { get; set; } public byte[] ToBytes() { MemoryStream stream = new MemoryStream(); using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(AppId); writer.Write(MessageText); writer.Close(); } return (stream.GetBuffer()); } } public class DisplayMessage : Message { public string SourceIPEndPoint { get; set; } public static DisplayMessage FromBytes(byte[] bytes) { DisplayMessage message = new DisplayMessage(); MemoryStream stream = new MemoryStream(bytes); using (BinaryReader reader = new BinaryReader(stream)) { message.AppId = reader.ReadString(); message.MessageText = reader.ReadString(); } return (message); } } }
by way of explanation – what’s going on there is;
- When someone first clicks a button we first asynchronously use the UdpAnySourceMulticast client to attempt to join a multicast group on address 239.255.0.1 and port 32768 ( my policy server serves up a policy that allows this )
- On the press of the “start receiving” button we asynchronously try and receive a message from the multi-cast group. When one arrives, we deserialize from binary and put it into a data-bound collection and then receive asynchronously again.
- On the press of the “start sending” button we start a 5-second timer. Each timer tick we make a message with the DateTime.Now stuck inside it, serialize it into binary and send it out to the multi-cast group.
and here’s 4 instances of the same application running on my desktop where they’ve all joined the multi-cast group and are both sending messages to the multi-cast group and receiving them;
Note – in order for that code to work I didn’t just need 4 instances of the Silverlight app – I also needed a UDP policy server as discussed earlier, if you’re trying this kind of scenario and see your request to join a multi-cast group take a long time before throwing an exception then it’s likely that you’re lacking a policy server or the policy server interaction isn’t allowing the code to proceed.
Pretty neat? I could see this being used by a whole bunch of LAN based business applications.
WCF Enhancements
Having sockets support in Silverlight since V2 is great and somewhat surprising really as it’s quite a low level API and so it’s great to see Silverlight 4 adding a higher level programming model on top of that with WCF’s additions of a new TCP transport.
In order to test that kind of functionality you need some kind of server application to talk to so I quickly knocked up a console application with WCF ( I didn’t give it a lot of thought tbh so apologies if it’s a bit rough-and-ready ). Here’s the console app;
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Runtime.Serialization; using System.Timers; namespace ConsoleApplication6 { [DataContract] public class StockInfo { [DataMember] public string StockSymbol { get; set; } [DataMember] public decimal StockPrice { get; set; } } [ServiceContract] public interface IStockCallback { [OperationContract(IsOneWay=true)] void StocksUpdated(List<StockInfo> newStockInfo); } [ServiceContract(CallbackContract = typeof(IStockCallback))] public interface IStockService { [OperationContract(IsOneWay=true)] void Subscribe(); [OperationContract(IsOneWay = true)] void Unsubcribe(); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class StockService : IStockService { public StockService() { stocks = new List<StockInfo>() { new StockInfo() { StockSymbol = "MSFT", StockPrice = 28.0m }, new StockInfo() { StockSymbol = "AAPL", StockPrice = 194.0m }, new StockInfo() { StockSymbol = "GOOG", StockPrice = 548.0m } }; callbacks = new List<IStockCallback>(); timer = new Timer(5000); timer.Elapsed += OnTimerTick; } void OnTimerTick(object sender, ElapsedEventArgs e) { lock (stocks) { foreach (StockInfo stock in stocks) { stock.StockPrice += 0.5m; } } List<IStockCallback> copy; lock (callbacks) { copy = new List<IStockCallback>(callbacks); } foreach (IStockCallback callback in copy) { callback.StocksUpdated(stocks); } } public void Subscribe() { IStockCallback callback = OperationContext.Current.GetCallbackChannel<IStockCallback>(); lock (callbacks) { callbacks.Add(callback); } } public void Unsubcribe() { IStockCallback callback = OperationContext.Current.GetCallbackChannel<IStockCallback>(); lock (callbacks) { callbacks.Remove(callback); } } public void Start() { timer.Start(); } public void Stop() { timer.Stop(); } List<IStockCallback> callbacks; List<StockInfo> stocks; Timer timer; } class Program { static void Main(string[] args) { StockService service = new StockService(); ServiceHost host = new ServiceHost(service); host.Open(); service.Start(); Console.WriteLine("Listening..."); Console.ReadLine(); service.Stop(); host.Close(); } } }
and I configured that up to listen on port 4502 because I figure that’ll line up with the Silverlight security policy for sockets. I also went for “no security” on the grounds as I don’t think ( i.e. not 100% sure on this one ) that the Silverlight bits support security on top of this;
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="ConsoleApplication6.StockService" behaviorConfiguration="serviceBehvaviourConfig"> <endpoint address="net.tcp://localhost:4502/StockService" binding="netTcpBinding" bindingConfiguration="tcpEndpointConfig" contract="ConsoleApplication6.IStockService"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="serviceBehvaviourConfig"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:4503/Metadata"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="tcpEndpointConfig"> <security mode="None"/> </binding> </netTcpBinding> </bindings> </system.serviceModel> </configuration>
I ran up the application so that it’d serve metadata and then created a new Silverlight 4 project and did “Add Service Reference” to my metadata. That is;
and, to be honest, I wasn’t really expecting that it’d work but Visual Studio whirred and out popped a bunch of proxy code in my project and a configuration file that looked like;
<configuration> <system.serviceModel> <bindings> <customBinding> <binding name="NetTcpBinding_IStockService"> <binaryMessageEncoding /> <tcpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" /> </binding> </customBinding> </bindings> <client> <endpoint address="net.tcp://localhost:4502/StockService" binding="customBinding" bindingConfiguration="NetTcpBinding_IStockService" contract="ProxyCode.IStockService" name="NetTcpBinding_IStockService" /> </client> </system.serviceModel> </configuration>
Hmmm – intriguing! I added a reference to System.ServiceModel.NetTcp as I think that’s where the tcpTransport lives and wrote a little UI inside of Silverlight ( this should really be a chart 🙂 ) to display the stock data from my service;
<UserControl x:Class="SilverlightApplication43.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <dg:DataGrid ItemsSource="{Binding StockPrices}" FontSize="16"> </dg:DataGrid> <StackPanel Grid.Row="1" Orientation="Horizontal"> <Button Margin="5" Content="Start" Click="OnStart" /> <Button Margin="5" Content="Stop" Click="OnStop" /> </StackPanel> </Grid> </UserControl>
and put a little code behind it;
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using SilverlightApplication43.ProxyCode; using System.ServiceModel; using System.ComponentModel; using System.Collections.ObjectModel; namespace SilverlightApplication43 { public partial class MainPage : UserControl, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public MainPage() { InitializeComponent(); stocks = new ObservableCollection<StockInfo>(); this.Loaded += (s, e) => { this.DataContext = this; }; } public ObservableCollection<StockInfo> StockPrices { get { return (stocks); } private set { stocks = value; FirePropertyChanged("StockPrices"); } } void FirePropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } void OnStart(object sender, RoutedEventArgs args) { sink = new StockServiceSink(this.Dispatcher); sink.StocksUpdatedEvent += OnStocksUpdated; proxy = new StockServiceClient(new InstanceContext(sink)); proxy.SubscribeAsync(); } void OnStop(object sender, RoutedEventArgs args) { sink.StocksUpdatedEvent -= OnStocksUpdated; proxy.UnsubcribeCompleted += OnUnsubscribed; } void OnStocksUpdated(object sender, PayloadEventArgs<ObservableCollection<StockInfo>> e) { // Because StockInfo already implements INotifyChanged we'll attempt to // update what we already have rather than just replace. foreach (StockInfo newStock in e.Payload) { StockInfo existingStock = stocks.SingleOrDefault( s => s.StockSymbol == newStock.StockSymbol); if (existingStock == null) { stocks.Add(newStock); } else { existingStock.StockPrice = newStock.StockPrice; } } } void OnUnsubscribed(object sender, AsyncCompletedEventArgs args) { proxy.UnsubcribeCompleted -= OnUnsubscribed; proxy.CloseCompleted += (s,e) => { proxy = null; }; proxy.CloseAsync(); } ObservableCollection<StockInfo> stocks; StockServiceSink sink; StockServiceClient proxy; } }
with a couple of little helper classes;
public class PayloadEventArgs<T> : EventArgs { public PayloadEventArgs() { } public PayloadEventArgs(T payload) { this.Payload = payload; } public T Payload { get; set; } } public class StockServiceSink : IStockServiceCallback { public event EventHandler<PayloadEventArgs<ObservableCollection<StockInfo>>> StocksUpdatedEvent; public StockServiceSink(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public void StocksUpdated(ObservableCollection<StockInfo> newStockInfo) { if (StocksUpdatedEvent != null) { dispatcher.BeginInvoke(() => { StocksUpdatedEvent(this, new PayloadEventArgs<ObservableCollection<StockInfo>>(newStockInfo)); }); } } Dispatcher dispatcher; }
Now to get this to work I need two things running on my machine. I need my Console application to provide the other end of the TCP service but I also ( crucially ) need a security policy server running on port 943 because before Silverlight is prepared to talk to my WCF server it will want to talk to a security policy server. So, I got one of those ( there’s one here for example although I used a different one ) and launched up;
- The Silverlight app ( running in the browser )
- The console service
- The policy server
and all worked quite nicely and I’ve got a UI display stocks that are updating over WCF on TCP with callback contracts – here’s the Silverlight app on top of the 2 console windows;
Setting the Referer Header
In previous versions, Silverlight did not set the “Referer” header when it was making web requests which caused some back-end server code some problems in identifying where requests where coming from. Silverlight 4 sets the header as this Fiddler trace below shows for my simple request via WebClient;
Sharing Authentication Cookies Between the Browser and Client Networking Stacks
I talked a little about this in the example in the “review” section. We have the browser with an authentication cookie on the browser network stack that isn’t shared with Silverlight when it uses the client network stack. It’s not always a problem because you might be able to do what you need to do without using the client network stack at all but if you do need to go down this route ( e.g. to get HTTP status codes or PUT/DELETE verbs ) then as far as I know at the time of writing you’ll still have to re-authenticate the Silverlight client as I don’t think there’s a way of sharing the cookie from the browser network stack.