Silverlight duplex communication, part 1

Somebody recently asked me about duplex communications with Silverlight clients (i.e. pushing data out to the client application) and it made me realise that I needed to brush up on some of the basic detail. So I put together a little learning project, and it's something I'd like to share.

Note this has been built with the RC of VS 2010 and SL 4.0.

Network Communications in Silverlight

Mike Taulty has put together a great series of Channel 9 videos where he does a whistle-stop tour of the various network options available within SL 4. Simply put, the main ones are:

  • WCF over HTTP
  • WCF over TCP
  • TCP sockets
  • UDP sockets
  • WebRequest over HTTP

My application will use the two WCF options to implement a real-time chat application, although currently only the duplex HTTP binding is implemented.  Here is a screenshot of it in action, showing two browser windows communicating in real-time:

 image

Architecture of my Sample Application

The Silverlight portion of the application is pretty simple and uses the NavigationFrame (introduced in SL 3) and if you've played with the Silverlight Navigation Application template then you should be fairly familiar with the project layout. The important view is PollingDuplexHttpClient.xaml.

When using the MVVM pattern (which I am) there are a number of ways of hooking the ViewModel into the view (reference). I'm using a simple XAML-based approach to set the DataContext of the Page to the ViewModel:

   1: <navigation:Page.DataContext>
   2:   <app:DuplexHttpClientViewModel />
   3: </navigation:Page.DataContext>

The ViewModel class itself derives from an abstract base class DuplexChatClientViewModelBase. The sole purpose of the concrete ViewModel classes is to specify the actual WCF binding to use (net.tcp or http). And the primary purpose of the base ViewModel class is to co-ordinate the services which do the real work.

There’s a couple of aspects to the application which I think are interesting.  I hope to be posting some further detail on these in future.

Doing the Duplex Communication

The WCF communication is handled in the asynchronous manner typical of Silverlight, and is contained in the PushDataReceiver class.  There are four separate chains of execution in this class:

  • EnsureStarted –> OnOpenCompleteFactory –> CompleteOpenFactory –> OnOpenCompleteChannel –> CompleteOpenChannel
  • Send –> OnSendComplete –> CompleteSend
  • Receive –> OnReceiveComplete –> CompleteReceive
  • Close –> OnCloseComplete –> CompleteClose

Obviously the first and last of these are only called once, whereas the Send and Receive chains are more frequently executed.  The sending operation does not need much explanation:

   1: public void Send(Message message)
   2: {
   3:     IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
   4:  
   5:     if (resultChannel.CompletedSynchronously)
   6:         CompleteOnSend(resultChannel);
   7: }
   8:  
   9: private void OnSend(IAsyncResult result)
  10: {
  11:     if (result.CompletedSynchronously) return;
  12:     CompleteOnSend(result);
  13: }
  14:  
  15: private void CompleteOnSend(IAsyncResult result)
  16: {
  17:     var sendingChannel = (IDuplexSessionChannel) result.AsyncState;
  18:     sendingChannel.EndSend(result);
  19: }

The receiving operation is similar in structure, with the exception that it is recursive and it needs to actually do something with the incoming message.  It is first initiated by the CompleteOpenChannel method

   1: private void ReceiveLoop(IDuplexSessionChannel receivingChannel)
   2: {
   3:     if (receivingChannel.State == CommunicationState.Opened)
   4:     {
   5:         IAsyncResult result = receivingChannel.BeginReceive(new AsyncCallback(OnReceiveComplete), receivingChannel);
   6:         if (result.CompletedSynchronously) CompleteReceive(result);
   7:     }
   8: }
   9:  
  10: private void OnReceiveComplete(IAsyncResult result)
  11: {
  12:     if (result.CompletedSynchronously) return;
  13:     CompleteReceive(result);
  14: }
  15:  
  16: private void CompleteReceive(IAsyncResult result)
  17: {
  18:     var receivingChannel = (IDuplexSessionChannel) result.AsyncState;
  19:  
  20:     Message receivedMessage = receivingChannel.EndReceive(result);
  21:  
  22:     if (receivedMessage != null)
  23:         uiThread.Post(ServiceLocator.Processor.ProcessData, receivedMessage); // Run on UI Thread.
  24:     ReceiveLoop(receivingChannel);
  25: }

Line 33 in the above code snippet is where the recursion happens to ensure that we always wait for incoming data.  Please note that exception handling code has been omitted here for the sake of clarity … it is present in the code download!  ;-)

Now all this seems very clear and straightforward, except for the fact that the server has no way of knowing that we are actually listening.  And this is why the service exposes an InitiateDuplex method.  From the CompleteOpenChannel method, we call Send against this service method.  It is this method which obtains a reference to the back channel to the SilverLight client.

I plan to run some deeper analysis into what’s actually happening on the wire.  Check back here soon for some details.

The WCF Service

There’s some surprising stuff going on in the service implementation here - a timer to keep channels alive, recording of IP addresses and timestamps, etc – but the most important bits are in the service contract (which is attributed to have a CallbackContract) and the InitiateDuplex method.  Here the code is building up a list of clients that will be sent any incoming message:

   1: public void InitiateDuplex(Message receivedMessage)
   2: {
   3:     lock (clients)
   4:     {
   5:         localClient = OperationContext.Current.GetCallbackChannel<IChatRoomClient>();
   6:         clients.Add(localClient);
   7:     }
   8: }

The code which uses the list of back channels is the DispatchToClientsmethod, which gets called whenever a client pushes a message to the SendMessage service method:

   1: private void DispatchToClients(ChatData data)
   2: {
   3:     var clientsToRemove = new List<IChatRoomClient>();
   4:     lock (clients)
   5:     {
   6:         foreach (IChatRoomClient client in clients)
   7:         {
   8:             try
   9:             {
  10:                 //Send data to the client
  11:                 if (client != null)
  12:                 {
  13:                     Message chatMsg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
  14:                                                             "Silverlight/IChatRoomService/Receive",
  15:                                                             data,
  16:                                                             serializer);
  17:  
  18:                     chatMsg.Headers.Add(MessageHeader.CreateHeader("Type", "", "DataWrapper"));
  19:                     client.BeginReceive(chatMsg, EndSend, client);
  20:                 }
  21:             }
  22:             catch (Exception)
  23:             {
  24:                 // Exception caught when trying to send message to client so remove them from client list.
  25:                 clientsToRemove.Add(client);
  26:             }
  27:         }
  28:  
  29:         foreach (IChatRoomClient client in clientsToRemove)
  30:         {
  31:             clients.Remove(client);
  32:         }
  33:     }
  34: }

FYI a lot of this code comes fairly directly from Dan Wahlin’s post about duplex communications.

Caveat: the server delivers message to the listed clients one-by-one.  This means that delivery will slow down with the number of connected clients.

Getting Running

If you want to get this running on your machine you will need Visual Studio 2010 RC.  The steps you need to follow are pretty simple to enable HTTP communications:

  • Download the code
  • Build everything in Visual Studio and hit F5 to start the SilverlightTestBed.Web project and the WCF service.

Future Posts

I plan to post a few more articles about this application:

  • Analysing HTTP traffic and communication patterns
  • Enabling net.tcp communication
  • Analysing what that looks like on the wire
  • Refactoring the code to use Rx (Reactive Extensions for .Net)
April 13 2010

WPF/Silverlight MVVM Design Data - create once and share with Unit Tests

Getting data appearing in design view in Blend is key to being able to manipulate the your layout and styles without needing to run up your app (to quote Marcin, ‘nudge, nudge, nudge instead of nudge-F5-waaaait, nudge-F5-waaaaait’)

As soon as a colleague pointed out to me VS2010 now supports Blend’s d:DataContext I brought forward getting things ‘working in in design view’, as I realised here was a way to quickly and easily wire up a test rig of data in C# that I could share between my unit tests, a mock session of my WPF/Silverlight app and design view in both Blend and VS2010 (rather than xaml test data for UI, C# or for tests/runtime test UI). Down to it …

Working backwards, here’s my end result (Bob and Alice are a List<Person> bound to a ListView). Doesn’t look like much, but it works with all xaml controls including DataGrids/TreeViews etc, so you should quickly start to see the power in simplicity.

image

Key XAML:

<UserControl ... xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:md="clr-namespace:MvvmDemo" mc:Ignorable="d"
        d:DesignHeight="600" d:DesignWidth="800"
        d:DataContext="{x:Static md:DesignData.PersonListViewModel}"
        x:Name="view" DataContext="{Binding ElementName=view, Path=ViewModel}">
... <TextBox Text="{Binding Filter.PersonName}"
... <DatePicker SelectedDate="{Binding Filter.BirthDate}"
... <ListBox ItemsSource="{Binding FilteredPeople}">

By the way, this is very cool: the VS2010 Properties window now has navigation of my ViewModel object hierarchy, as long as I’m bound to my real DataContext in xaml. Still prefer the speed of resharper smart auto-completion though (ctrl+alt+space).

image

Anyway, back to the xaml … note the d:DataContext is bound to a static DesignData class & property. Here you could either inject a fake/mock repository and have the ViewModel do its thing, or stop it from loading and populate it in the DesignData class. Both are good techniques in unit testing and design data, and can be used interchangeably. Here I demo both (the viewmodel loads existing people from the repository, hence in design the fake, and I populate the form filter fields).

public static class DesignData
{
    static DesignData()
    {
        Container.Register<IPersonRepository, FakePersonRepository>();
    }

    public static PersonListViewModel PersonListViewModel
    {
        get
        {
            return new PersonListViewModel
                       {
                           Filter = new PersonFilter
                                        {
                                            PersonName = "Ca",
                                            BirthDate = DateTime.Now.AddYears(-20)
                                        }
                       };
        }
    }
}

I inject & reuse the FakeRepository in my TestUI exe (a separate exe so as not to pollute my release code with accidental fake data). 

And just for good measure, here’s a kind of contrived test – the fake data doesn’t really effect the test but you get the idea: write once, use everywhere - same as the rest of your code.

[TestMethod]
public void search_on_name_should_filter()
{
   // ARRANGE 
   _sut = new PersonListViewModel();
   var people = new FakePersonRepository().GetPeople();
   _sut.People.AddRange(people);
   people = new List<Person> { new Person {Name = "Carol"},
                        new Person {Name = "Carlos"},
                        new Person {Name = "Charlie"} };
   _sut.People.AddRange(people);
    string search = "Ca";
    _sut.PersonName = search;

   // ACT 
   _sut.Search();

   // ASSERT
   Assert.IsFalse(_sut.FilteredPeople.Any(p => !p.Name.StartsWith(search)));
   Assert.IsTrue(_sut.FilteredPeople.Any(p => p.Name.StartsWith(search)));
}

Full code here:

image

Technorati Tags: ,,
April 1 2010

WPF Wait Indicator (aka Spinner)

UPDATE: New version of the spinner is available here:

Yet another leftover form Nym’s fancy downloader project is the “spinning doughnut of wait”. The downloader uses BackgroundWorker to get the initial list of videos available for download as doing so on the UI thread could cause the application to freeze for way too long. When we first put the BackgroundWorker in place, there was an “uneasy” period of time when the user would be staring at an empty screen with no visual clues as to what was going on. So I came up with a concept of a “spinner” show below (not very original, I know):

image 

The idea here is to display a spinning “doughnut” which gives the user a clue that something is indeed going on. As this is quite a common issue with background processing, I though that  wrapping the spinner in a reusable user control may be useful. The logic behind the control is simple: as soon as it becomes visible it starts spinning and stops when it’s being hidden. Parent control may then control visibility of the spinner (through data trigger etc) and that’s all that is required to use it. This behaviour is implemented through a bit of code behind as changes in visibility are advertised through IsVIsibleChanged event which is a standard non-routed .NET event. This sadly means that there is no way to react to it from the XAML event triggers and a piece of code was required. It is important to note here that it is necessary to stop the animation when it is no longer required. If you leave it running, it will keep spinning in the background, despite of the fact that it is not visible, consuming processor resources in the process. Get a couple of those running off screen and you will soon notice the difference in your computer’s performance.

Code Snippet
  1. public partial class Spinner : UserControl
  2. {
  3.     private Storyboard _storyboard;
  4.  
  5.     public Spinner()
  6.     {
  7.         InitializeComponent();
  8.  
  9.         this.IsVisibleChanged += OnVisibleChanged;
  10.     }
  11.  
  12.     private void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
  13.     {
  14.         if ( IsVisible )
  15.         {
  16.             StartAnimation();
  17.         }
  18.         else
  19.         {
  20.             StopAnimation();
  21.         }
  22.     }
  23.  
  24.     private void StartAnimation()
  25.     {
  26.         _storyboard = (Storyboard)FindResource("canvasAnimation");
  27.         _storyboard.Begin(canvas, true);
  28.     }
  29.  
  30.     private void StopAnimation()
  31.     {
  32.         _storyboard.Remove(canvas);
  33.     }
  34. }

 

I have tested a couple of approaches to animate the spinner but settled on a version which simply rotates the canvas on which the rectangles are drawn. This means that I have just one thing to animate and when compared with phase-shift-animating opacities of sixteen individual rectangles it substantially easier :) It is also substantially cheaper (processor wise) when compared to animating opacities. The XAML of the spinner is shown below and I hope it is self explanatory. In order to achieve on/off effect for each rectangle I settled on key frame animation which is “jerky” by design, but it means that rectangles maintain their position but become lighter/darker as the doughnut spins thus creating the impression of them being momentarily turned on and then dimming slowly down. Think cheap 80’s disco effect :)

Code Snippet
  1. <UserControl x:Class="FancyPDCDownload2009.Modules.Main.Views.Spinner"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  4.   <UserControl.Resources>
  5.         <SolidColorBrush x:Key="SpinnerRectangleBrush" Color="Blue"/>
  6.         <Style TargetType="Rectangle">
  7.             <Setter Property="RadiusX" Value="5"/>
  8.             <Setter Property="RadiusY" Value="5"/>
  9.             <Setter Property="Width" Value="50"/>
  10.             <Setter Property="Height" Value="20"/>
  11.             <Setter Property="Fill" Value="{StaticResource SpinnerRectangleBrush}"/>
  12.             <Setter Property="Canvas.Left" Value="220"/>
  13.             <Setter Property="Canvas.Top" Value="140"/>
  14.             <Setter Property="Opacity" Value="0.1"/>
  15.         </Style>
  16.  
  17.         <Storyboard x:Key="canvasAnimation">
  18.             <DoubleAnimationUsingKeyFrames
  19.                RepeatBehavior="Forever"
  20.                SpeedRatio="16"
  21.                Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)">
  22.                 <DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="22.5"/>
  23.                 <DiscreteDoubleKeyFrame KeyTime="0:0:2" Value="45"/>
  24.                 <DiscreteDoubleKeyFrame KeyTime="0:0:3" Value="67.5"/>
  25.                 <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="90"/>
  26.                 <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="112.5"/>
  27.                 <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="135"/>
  28.                 <DiscreteDoubleKeyFrame KeyTime="0:0:7" Value="157.5"/>
  29.                 <DiscreteDoubleKeyFrame KeyTime="0:0:8" Value="180"/>
  30.                 <DiscreteDoubleKeyFrame KeyTime="0:0:9" Value="202.5"/>
  31.                 <DiscreteDoubleKeyFrame KeyTime="0:0:10" Value="225"/>
  32.                 <DiscreteDoubleKeyFrame KeyTime="0:0:11" Value="247.5"/>
  33.                 <DiscreteDoubleKeyFrame KeyTime="0:0:12" Value="270"/>
  34.                 <DiscreteDoubleKeyFrame KeyTime="0:0:13" Value="292.5"/>
  35.                 <DiscreteDoubleKeyFrame KeyTime="0:0:14" Value="315"/>
  36.                 <DiscreteDoubleKeyFrame KeyTime="0:0:15" Value="337.5"/>
  37.                 <DiscreteDoubleKeyFrame KeyTime="0:0:16" Value="360"/>
  38.             </DoubleAnimationUsingKeyFrames>
  39.         </Storyboard>
  40.     </UserControl.Resources>
  41.     <Grid>
  42.       <TextBlock
  43.          HorizontalAlignment="Center"
  44.          VerticalAlignment="Center"
  45.          Text="Loading..."/>
  46.         <Canvas Height="300" Width="300"
  47.                 Background="Transparent"
  48.                 Name="canvas">
  49.         <!-- First quadrant -->
  50.           <Rectangle Opacity="1"/>
  51.             <Rectangle>
  52.           <Rectangle.RenderTransform>
  53.             <RotateTransform Angle="22.5" CenterX="-70" CenterY="10"/>
  54.           </Rectangle.RenderTransform>
  55.         </Rectangle>
  56.         <Rectangle >
  57.           <Rectangle.RenderTransform>
  58.             <RotateTransform Angle="45" CenterX="-70" CenterY="10"/>
  59.           </Rectangle.RenderTransform>
  60.         </Rectangle>
  61.         <Rectangle >
  62.           <Rectangle.RenderTransform>
  63.             <RotateTransform Angle="67.5" CenterX="-70" CenterY="10"/>
  64.           </Rectangle.RenderTransform>
  65.         </Rectangle>
  66.        
  67.         <!-- Second quadrant -->
  68.         <Rectangle >
  69.           <Rectangle.RenderTransform>
  70.             <RotateTransform Angle="90" CenterX="-70" CenterY="10"/>
  71.           </Rectangle.RenderTransform>
  72.         </Rectangle>
  73.         <Rectangle >
  74.           <Rectangle.RenderTransform>
  75.             <RotateTransform Angle="112.5" CenterX="-70" CenterY="10"/>
  76.           </Rectangle.RenderTransform>
  77.         </Rectangle>
  78.         <Rectangle Name="r7">
  79.           <Rectangle.RenderTransform>
  80.             <RotateTransform Angle="135" CenterX="-70" CenterY="10"/>
  81.           </Rectangle.RenderTransform>
  82.         </Rectangle>
  83.         <Rectangle >
  84.           <Rectangle.RenderTransform>
  85.             <RotateTransform Angle="157.5" CenterX="-70" CenterY="10"/>
  86.           </Rectangle.RenderTransform>
  87.         </Rectangle>
  88.        
  89.         <!-- Third quadrant -->
  90.         <Rectangle Opacity="0.2">
  91.           <Rectangle.RenderTransform>
  92.             <RotateTransform Angle="180" CenterX="-70" CenterY="10"/>
  93.           </Rectangle.RenderTransform>
  94.         </Rectangle>
  95.             <Rectangle Opacity="0.3">
  96.           <Rectangle.RenderTransform>
  97.             <RotateTransform Angle="202.5" CenterX="-70" CenterY="10"/>
  98.           </Rectangle.RenderTransform>
  99.         </Rectangle>
  100.             <Rectangle Opacity="0.4" >
  101.           <Rectangle.RenderTransform>
  102.             <RotateTransform Angle="225" CenterX="-70" CenterY="10"/>
  103.           </Rectangle.RenderTransform>
  104.         </Rectangle>
  105.             <Rectangle Opacity="0.5">
  106.           <Rectangle.RenderTransform>
  107.             <RotateTransform Angle="247.5" CenterX="-70" CenterY="10"/>
  108.           </Rectangle.RenderTransform>
  109.         </Rectangle>
  110.        
  111.         <!-- Fourth quadrant -->
  112.             <Rectangle Opacity="0.6">
  113.           <Rectangle.RenderTransform>
  114.             <RotateTransform Angle="270" CenterX="-70" CenterY="10"/>
  115.           </Rectangle.RenderTransform>
  116.         </Rectangle>
  117.             <Rectangle Opacity="0.7">
  118.           <Rectangle.RenderTransform>
  119.             <RotateTransform Angle="292.5" CenterX="-70" CenterY="10"/>
  120.           </Rectangle.RenderTransform>
  121.         </Rectangle>
  122.             <Rectangle Opacity="0.8">
  123.           <Rectangle.RenderTransform>
  124.             <RotateTransform Angle="315" CenterX="-70" CenterY="10"/>
  125.           </Rectangle.RenderTransform>
  126.         </Rectangle>
  127.             <Rectangle Opacity="0.9">
  128.           <Rectangle.RenderTransform>
  129.             <RotateTransform Angle="337.5" CenterX="-70" CenterY="10"/>
  130.           </Rectangle.RenderTransform>
  131.         </Rectangle>
  132.         <Canvas.RenderTransform>
  133.             <RotateTransform Angle="0" CenterX="150" CenterY="150"/>
  134.         </Canvas.RenderTransform>
  135.                     </Canvas>
  136.     </Grid>
  137. </UserControl>
       

The source code for the downloader including the spinner can be downloaded from here.

March 30 2010

Static reflection INotifyPropertyChanged

Every MVVM framework contains a ViewModelBase with an implementation of INotifyPropertyChanged. Mine's no different, but I wanted to have the flexibility to control when propertychanges are fired, and whether to do other stuff/fire other propertychanges at the same time, all strongly typed (for resharper renaming, navigation etc). Static reflection came into play, and I came up with a solution that gave me the ability to do this:

if ( SetProperty( ref this._company, value, () => this.Company ) )
{
	// update something else, fire other property changes etc ... 
	this.FirePropertyChanged( () => this.Salary );
}

To achieve this I used static reflection as thus

public bool SetProperty<T>( ref T field, T value, Expression<Func<T>> property )
{
	if ( ( Equals( field, default( T ) ) && Equals( value, default( T ) ) )
		  || ( !Equals( field, default( T ) ) && field.Equals( value ) ) )
	{
		return false;
	}
	field = value;
	if ( property != null )
	{
		this.FirePropertyChanged( property );
	}
	return true;
}

public void FirePropertyChanged<T>( Expression<Func<T>> property )
{
	if ( this.PropertyChanged != null )
	{
		this.PropertyChanged( this, new PropertyChangedEventArgs( property.GetPropertyName() ) );
	}
}

public static class StaticReflectionExtensions
{
	public static string GetPropertyName<T>( this Expression<Func<T>> property )
	{
		return ( ( (MemberExpression)property.Body ).Member ).Name;
	}
}
February 21 2010
Newer Posts Older Posts