A great portion of mobile applications consumes data from HTTP services. This is usually achieved as a pull scenario in which apps initiate the data flow from the server. In many cases pushing data to the client is a more natural and potentially much better solution. In this blog post I will explore how ASP.NET SignalR can help XAML developers simplify the task of creating and consuming push services over HTTP. I will also show how to leverage MVVM pattern to create a user experience that is driven by incoming data. The example will be built for Windows Phone 8 and will use MVVM Light library.
What do I mean by 'reactive app'?
MVVM and data-binding is by its very nature reactive. Any changes made to the view model should automatically propagate to the data-bound view. Thus being said, in many scenarios the true source of such change comes from the model that resides on remote server (eg. because a new entity has been added). Very often we want to be able to push this change across the wire so that it triggers relevant view updates. This is where SignalR can be leveraged - it can help us extend 'reactivity' beyond the network boundary.
In traditional scenarios apps that consume HTTP services will usually pull data from the server. This may happen on demand - e.g. when opening a new screen that presents a list of customers, or in regular intervals to check for any updates. There is nothing inherently bad with this approach and it suits a large number of cases. Sometimes, however constant polling for changes is undesired and can be sub optimal from performance perspective. Consider for example a financial app that should provide real-time like updates to the user (eg. commodity quotes) - in this case it feels natural to have data pushed to us from the service as new updates arrive.
From technical point of view SignalR may still use polling, but from logical point of view it creates an abstraction over the actual mechanism (be it long polling, web sockets or anything else) which makes our apps independent from it. This is a huge benefit.
Let's see how we can use SignalR to write a simple 'financial' app.
Push service with ASP.NET SignalR
We will use SingalR hubs feature to create a push service. Create an Empty ASP.NET Web Application in Visual Studio and NuGet install or reference SignalR.
Install-Package Microsoft.AspNet.SignalR.SystemWeb -Version 1.0.1 protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHubs(); }
The example will operate on a simple model - a Quote class representing individual.. 'quote' (e.g. a currency exchange rate).
public class Quote { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public decimal PriceChange { get; set; } }
The hub will simply push quote updates to all interested parties.
[HubName("Quote")] public class QuoteHub : Hub { public void UpdateQuote(Quote quote) { Clients.All.updateQuote(quote); } }
For the sake of example we will generate some random data and push quote updates in regular intervals. This code is not that important, but may help if you want an example on how to generate sample data in SignalR projects.
internal class DataStreamThread { private const int UpdateIntervalInMilliseconds = 4000; private object randomLock = new object(); private Dictionary<string, decimal> quotes = new Dictionary<string, decimal>() { {"EUR/USD", 1.320m}, {"GBP/USD", 1.550m}, {"AUD/USD", 1.032m}, {"PLN/USD", 0.3168m}, {"USD/JPY", 99.045m}, {"Gold", 1467.2m}, {"Silver ", 24.02m}, }; public void Start() { Random random = new Random(); int counter = 0; foreach (KeyValuePair<string, decimal> quote in quotes) { counter++; int id = counter; ThreadPool.QueueUserWorkItem(_ => { var hubContext = GlobalHost .ConnectionManager.GetHubContext<QuoteHub>(); Quote q = new Quote() { Id = id, Name = quote.Key, Price = quote.Value, PriceChange = 0, }; while (true) //sic { double randomChange; lock(randomLock) { randomChange = (random.NextDouble() - 0.42) / 100; } decimal change = Math .Round((decimal)randomChange * q.Price, 6); q.Price += change; q.PriceChange = change; try { hubContext.Clients.All.updateQuote(q); } catch (Exception ex) { System.Diagnostics .Trace.TraceError("Error thrown while updating clients: {0}", ex); } var intervalAdj = random.Next(-700, 700); Thread.Sleep(UpdateIntervalInMilliseconds + intervalAdj); } }); } } }
That's all we need on the server side. The most important line - hubContext.Clients.All.updateQuote(q); will push sample data to all hub clients.
Windows Phone 8 XAML application
As a next step let's write a XAML client that will present this data. I will use a Windows Phone 8 app as an example, but Windows 8 (or WPF and Silverlight) will be conceptually similar.
Here is how we want it to look like. The quotes should update automatically, ideally with some kind of subtle animation.
Image may be NSFW.
Clik here to view.
Start by adding a new Windows Phone 8 XAML application and installing MVVM Light via NuGet. Instead of reusing the Quote class (by adding as link to WP8) we will create a separate model on the client side in order to implement INotifyPropertyChanged.
public class Quote : INotifyPropertyChanged { private string _name; private decimal _price; private decimal _priceChange; public int Id { get; set; } public string Name { get { return _name; } set { if (value == _name) return; _name = value; OnPropertyChanged(); } } public decimal Price { get { return _price; } set { if (value == _price) return; _price = value; OnPropertyChanged(); } } public decimal PriceChange { get { return _priceChange; } set { if (value == _priceChange) return; _priceChange = value; OnPropertyChanged(); OnPropertyChanged("ChangePercentage"); } } public double ChangePercentage { get { return (double) (_priceChange / (_price - _priceChange)) * 100; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
Now let's think about how our view model(s) should receive update notifications from SignalR hub. We could open connection directly in the view model, get hub proxy instance and subscribe to updateQuote event. Something like that:
public MainViewModel() { _connection = new HubConnection(EndpointAddress); _hubProxy = _connection.CreateHubProxy(StockHubName); _hubProxy.On<Quote>("updateQuote", q => UpdateQuoteHandler); _connection.Start(); }
This would work and maybe is not that bad, but... it doesn't seem that doing all this is something our view model should be concerned about. What it really cares about is data updates and connection state changes. How it happens and what is the underlying technology should be owned by another class.
Also if we had more than one view model using the same hub (e.g. list->detail scenario) we would need to duplicate the code and connections.
Let's use MVVM Light's messaging capabilities to propagate updates in a decoupled way (we could have also used an interface that exposes events).
This also means we will introduce more abstractions and complexity, so bear that in mind especially when working on small applications.
public class ConnectionStateChangedMessage { public ConnectionState OldState { get; set; } public ConnectionState NewState { get; set; } } public class QuoteUpdatedMessage { public Quote Quote { get; set; } }
Now we need a class that will connect to SignalR hub and push the updates.
public interface IDispatcher { void Dispatch(Action action); } public interface IConnectedDataProvider { Task StartAsync(); void Stop(); } public class SignalRDataProvider : IConnectedDataProvider { private HubConnection _connection; private IHubProxy _hubProxy; private const string EndpointAddress = "http://192.168.1.18/Piotr.XamlSignalR.Service/"; private const string StockHubName = "Quote"; private const string QuoteUpdateName = "updateQuote"; private readonly IMessenger _messenger; private readonly IDispatcher _dispatcher; public SignalRDataProvider(IMessenger messenger, IDispatcher dispatcher) { _messenger = messenger; _dispatcher = dispatcher; _connection = new HubConnection(EndpointAddress); _hubProxy = _connection.CreateHubProxy(StockHubName); _hubProxy.On<Quote>(QuoteUpdateName, p => _dispatcher .Dispatch(() => UpdateQuote(p))); _connection.StateChanged += _connection_StateChanged; } void _connection_StateChanged(StateChange stateChange) { ConnectionState oldState = ConnectionStateConverter .ToConnectionState(stateChange.OldState); ConnectionState newState = ConnectionStateConverter .ToConnectionState(stateChange.NewState); var msg = new ConnectionStateChangedMessage() { NewState = newState, OldState = oldState, }; _dispatcher.Dispatch(() => _messenger .Send<ConnectionStateChangedMessage>(msg)); } public Task StartAsync() { return _connection.Start(); } private void UpdateQuote(Quote quote) { var msg = new QuoteUpdatedMessage() { Quote = quote }; _messenger.Send<QuoteUpdatedMessage>(msg); } public void Stop() { _connection.Stop(); } }
Please forgive me interface/class name ;)
The rationale behind providing IDispatcher interface is to be able to provide platform specific implementations (WP8, Windows 8, WPF, etc.). SignalR action handler can start from a non-UI thread and we need to be able to marshal execution to the UI thread.
Oh and remember - the WP8 emulator runs as a virtual machine, so make sure you don't use localhost as the endpoint address.
Now we can instantiate this class (e.g. in Application object) and start the connection.
private readonly IConnectedDataProvider _dataProvider = new SignalRDataProvider(Messenger.Default, new PhoneDispatcher()); ///... private async void Application_Launching(object sender, LaunchingEventArgs e) { await _dataProvider.StartAsync(); } private void Application_Closing(object sender, ClosingEventArgs e) { _dataProvider.Stop(); } ///...
This will ensure that update events are being sent and can be consumed by the view model... which leads us to the view model itself.
public class MainViewModel : ViewModelBase { private ConnectionState _connectionState; public ConnectionState ConnectionState { get { return _connectionState; } set { if (_connectionState == value) return; _connectionState = value; RaisePropertyChanged("ConnectionState"); RaisePropertyChanged("IsConnected"); } } public bool IsConnected { get { return ConnectionState == ConnectionState.Connected; } } public MainViewModel() { Items = new ObservableCollection<Quote>(); MessengerInstance .Register<QuoteUpdatedMessage>(this, UpdateQuoteHandler); MessengerInstance .Register<ConnectionStateChangedMessage>(this, ConnectionStateChangedHandler); } private void ConnectionStateChangedHandler(ConnectionStateChangedMessage msg) { ConnectionState = msg.NewState; } private void UpdateQuoteHandler(QuoteUpdatedMessage msg) { var quote = msg.Quote; var match = Items.FirstOrDefault(q => q.Name == quote.Name); if (match != null) { match.Price = quote.Price; match.PriceChange = quote.PriceChange; } else { Items.Add(quote); } } public ObservableCollection<Quote> Items { get; private set; } public override void Cleanup() { MessengerInstance.Unregister(this); base.Cleanup(); } }
It is quite simple and hopefully readable. MessengerInstance is a property provided by ViewModelBase and can be mocked for the sake of unit tests. Now, we need to write XAML view that will present data to the customer. For brevity I will only include most important parts here, you can have a look at full markup in the source code provided.
<ListBox x:Name="QuoteList" Opacity="0.5" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"> <ListBox.ItemTemplate> <DataTemplate> <Grid x:Name="TemplateContainer"> <!-- (...) --> <i:Interaction.Triggers> <ec:DataTrigger Binding="{Binding PriceChange, Converter={StaticResource DecimalToDoubleConverter}}" Comparison="GreaterThan" Value="0.0"> <ec:GoToStateAction StateName="Up" TargetObject="{Binding ElementName=TemplateContainer}"/> </ec:DataTrigger> <ec:DataTrigger Binding="{Binding PriceChange, Converter={StaticResource DecimalToDoubleConverter}}" Comparison="LessThan" Value="0.0"> <ec:GoToStateAction StateName="Down" TargetObject="{Binding ElementName=TemplateContainer}"/> </ec:DataTrigger> <ec:DataTrigger Binding="{Binding PriceChange, Converter={StaticResource DecimalToDoubleConverter}}" Comparison="Equal" Value="0.0"> <ec:GoToStateAction StateName="NoChange" TargetObject="{Binding ElementName=TemplateContainer}"/> </ec:DataTrigger> </i:Interaction.Triggers> <Grid> <!-- Item layout goes here --> </Grid> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Because we want to have a strong order of elements on the list we use CollectionViewSource to do the sorting.
<phone:PhoneApplicationPage.Resources> <CollectionViewSource x:Key="ItemsViewSource" Source="{Binding Items}"> <CollectionViewSource.SortDescriptions> <scm:SortDescription PropertyName="Id"/> </CollectionViewSource.SortDescriptions> </CollectionViewSource> </phone:PhoneApplicationPage.Resources>
As you can see we didn't have to write a single line of C# in the view's code-behind. Sorting, updates and visual state transitions will all happen thanks to XAML.
Unfortunately I ran out of time to include a Windows 8 sample, but the code has been built in such a way that creating a Win8 application should be a simple task as we can reuse most parts (including view model, SignalR data provider).
ASP.NET SignalR bridges the gap between reactive world of MVVM and HTTP services that reside on a remote server.
The complete project is available on bitbucket and below you can see the end result. Of course the data is completely unrealistic :)