2015-11-23



alvinashcraft
shared this story
from Simple Talk RSS Feed.

SignalR isn't just for web applications. It can also provide the basic real-time
communications for a
connected Windows Universal Application or even iOS and Android
applications. Christos Matskas demonstrates how to get started with creating
applications across a range of platforms that require real-time communication using SignalR

SignalR is a
powerful, open source library that allows developers to quickly and easily
implement real-time communications across different platforms. Although SignalR
is predominantly used in web applications, the library is versatile enough to be
used in mobile apps as well. This means that you can implement native real-time
connected applications without requiring the use of a web view. SignalR provides
client libraries for .NET, JavaScript, Windows Phone, Windows Universal
applications, iOS and Android.:p>

There are two
ways to add SignalR to cross-platform mobile applications. For .NET developers,
there is the option to “write-once deploy-everywhere, leveraging the power of C#
and Xamarin. Xamarin is a 3rd party library that takes advantage of the .NET
framework and provides a unified SDK that binds directly to the native libraries
in order develop mobile applications. Because Xamarin works on top of the .NET
framework, it means that all that's needed in order to use SignalR is to add the
necessary .dll to the solution, usually through NuGet.

If you don't
want to go down the route of Xamarin, there are also native ports of the SignalR
library both in Objective-C and Java. For iOS development you can use

this
port (SignalR-ObjC),
and for Android development there’s

this port
(SignalR/java-client). There is,
however, a small caveat when using these ports: because these two libraries are
not officially supported by the SignalR community, there is a chance that they
may be out of date or have features missing.

In this article,
we are going to develop a sample Windows Universal Application (store and
mobile) and implement basic real-time communications between the application and
a back-end service using SignalR.

The
Server setup

To demonstrate the real-time communication framework, we are going to implement
a sample stock price service.  Clients that are connected through SignalR will
receive real-time stock prices when these are updated on the server. The server
will use just a mock service to generate dummy prices for the purpose of this
demo. In a production environment, this data would be coming from a real API.

The SignalR
hub will be installed on a WebAPI 2.0 running on top of ASP.NET 4.5. The hub
will only have one public method which will be used to broadcast the data to all
connected clients.

On a new,
empty WebAPI solution in Visual Studio, open the NuGet console manager and run
the following command:

Install-Package
Microsoft.AspNet.SignalR

This will
install SignalR along with the necessary dependencies such as Owin (ASP.NET's
middleware framework). Owin is used to bootstrap SignalR and route all incoming
SignalR requests to the hub.

By default,
SignalR hubs are configured to reject incoming request from external
domains. This means that if your SignalR hub (WebAPI) is running under the
<a href="https://contoso.com" rel="nofollow">https://contoso.com</a> and clients are trying to connect from <a href="https://mydomain.com" rel="nofollow">https://mydomain.com</a>,
then the requests will fail with an HTTP 500 error.  To fix this problem, we
can configure Owin to accept cross-domain requests (CORS).  Go back to the NuGet
console manager and run the following command:

Install-Package
Microsoft.Owin.Cors

With the
dependencies installed, we can now start configuring the hub. Right-click on the
WebAPI project and add a new class. Name the class
Startup.cs and paste the following code:

using
Microsoft.AspNet.SignalR;

using
Microsoft.Owin.Cors;

using
Owin;

namespace
SignalRService

{

public

class

Startup

{

public

void
Configuration(IAppBuilder
app)

{

app.Map("/signalr",
map =>

{

map.UseCors(CorsOptions.AllowAll);

var
hubConfiguration =

new

HubConfiguration
{ };

map.RunSignalR(hubConfiguration);

});

}

}

}

This code
bootstraps SignalR and configures CORS in order to allow cross-domain clients to
connect. At the moment all external clients can hit the hub because we defined
CorsOption.AllowAll. Unless you wish
to create a truly open API, it is advisable that you restrict access to the
service.

Before we
create the hub, we need a mock service to feed stock data to the clients. Add a
new class to the project and name it
StockService. Paste the following code in it:

using
Microsoft.AspNet.SignalR;

using
Newtonsoft.Json;

using
System;

using
System.Collections.Concurrent;

using
System.Threading;

namespace
SignalRService

{

public

class

StockService

{

private

static

Timer
timer;

private

static

Random
random;

public

ConcurrentDictionary<string,

decimal>
Stocks;

private

static

int
interval = 5000;

public
StockService()

{

random =

new

Random();

timer =

new

Timer(UpdateStockPrices,

null,
interval, interval);

Stocks =

new

ConcurrentDictionary<string,

decimal>();

Stocks.TryAdd("GOOG",

new

decimal(2.50));

Stocks.TryAdd("MSFT",

new

decimal(3.15));

Stocks.TryAdd("APPL",

new

decimal(4.57));

}

private

void
UpdateStockPrices(object
state)

{

foreach
(var
stock

in
Stocks.Keys)

{

Stocks[stock] = random.NextDecimal();

}

BroadcastStockPriceChanges();

}

public

void
BroadcastStockPriceChanges()

{

var
hub =

GlobalHost.ConnectionManager.GetHubContext<StocksHub>();

hub.Clients.All.sendStockData(JsonConvert.SerializeObject(Stocks));

}

}

}

The service
is pretty basic. It creates a timer which is used to push stock data at a 5
second interval. There are a couple of extension methods that are used to
generate random decimals. Add a new class to your project and name it
Extensions. Paste the following code:

using
System;

namespace
SignalRService

{

public

static

class

Extensions

{

public

static

int
NextInt32(this

Random
rng)

{

unchecked

{

int
firstBits = rng.Next(0, 1 << 4) << 28;

int
lastBits = rng.Next(0, 1 << 28);

return
firstBits | lastBits;

}

}

public

static

decimal
NextDecimal(this

Random
rng)

{

byte
scale = (byte)rng.Next(29);

bool
sign = rng.Next(2) == 1;

return

new

decimal(rng.NextInt32(),

rng.NextInt32(),

rng.NextInt32(),

sign,

scale);

}

}

}

With the
service in place, the last step in creating the service process is adding the
SignalR hub to relay data to and from the clients. Right-click on the WebAPI
project and add a new class. Name it
StocksHub and paste the following code:

using
Microsoft.AspNet.SignalR;

namespace
SignalRService

{

public

class

StocksHub
:

Hub

{

private

readonly

StockService
stockService;

public
StocksHub()

{

stockService =

new

StockService();

}

}

}

I understand
your surprise. The hub is, in fact, empty. We only use it to instantiate the
StockService. This is the beauty of
SignalR. The hub can act as the conduit -passing information back and forth
between the client and some arbitrary back-end service. This is all we need to
do to get a functional hub. Next, we'll look into what's required to allow a
Windows Universal application to receive data from the SignalR hub.

The Client Setup

The steps
used to enable SignalR on the Windows Universal apps are the exact same steps
you would follow if you were working on a Xamarin application. Open up Visual
Studio and create a new Windows Universal app. In the end, the solution should
consist of 3 projects:

Windows Phone Application

Windows Desktop Application

Shared project

We will use
the shared project to add the common logic, such as the SignalR client and
configuration settings. The individual projects will have only the UI specific
logic necessary to display the data retrieved through SignalR.

We'll start
by adding the SignalR packages. Open the NuGet console manager and run the
following command:

Install-Package
Microsoft.AspNet.SignalR.Client

Next, open up
App.xaml.cs and add the following
public property and private method in the class:

public

SignalRClient
Client {

get;

set;
}

private

async

Task
StartHub()

{

Client =

new

SignalRClient();

await
Client.Connect();
}

Change the
OnLaunched() method to be
async by changing the method
declaration like this:

protected override async void
OnLaunched(LaunchActivatedEventArgs e)

and add the
following line anywhere in the body of the method:

await StartHub();

The above
code will start up the client and connect to the remote hub in order to start
receiving data. Next, since we're working with XAML, we can take advantage of
data binding so that we can update the UI asynchronously and automatically when
new data is received. In the shared project, create a new class and name it
PageData. Add the following code:

using
System.ComponentModel;

namespace
SignalRApp

{

public

class

PageData
:

INotifyPropertyChanged

{

private

double
_GOOG;

private

double
_MSFT;

private

double
_APPL;

public

event

PropertyChangedEventHandler
PropertyChanged;

public

double
GOOG

{

get
{

return
_GOOG; }

set

{

_GOOG =

value;

NotifyPropertyChanged("GOOG");

}

}

public

double
MSFT

{

get
{

return
_MSFT; }

set

{

_MSFT =

value;

NotifyPropertyChanged("MSFT");

}

}

public

double
APPL

{

get
{

return
_APPL; }

set

{

_APPL =

value;

NotifyPropertyChanged("APPL");

}

}

public

void
NotifyPropertyChanged(string
propertyName)

{

if
(PropertyChanged !=

null)

{

PropertyChanged(this,

new

PropertyChangedEventArgs(propertyName));

}

}

}

}

In a proper
application, we would opt in for proper MVVM framework that can eliminate most
of the above boiler plate code. Nonetheless, this is a demo app with limited
requirements so there's no reason to complicate the design and add unnecessary
dependencies. Finally, we need to create the SignalR client. Add a new class to
the shared project and name it
SignalRClient. Paste the code below:

using
Microsoft.AspNet.SignalR.Client;

using
Microsoft.AspNet.SignalR.Client.Transports;

using
System;

using
System.Threading.Tasks;

namespace
SignalRApp

{

public

class

SignalRClient

{

private

readonly

HubConnection
connection;

private

readonly

IHubProxy
proxy;

public

event

EventHandler<dynamic>
OnDataReceived;

public
SignalRClient()

{

connection =

new

HubConnection("http://localhost:9246");

proxy = connection.CreateHubProxy("StocksHub");

}

public

async

Task
Connect()

{

await
connection.Start(new

WebSocketTransport());

proxy.On("sendStockData",
(dynamic
data) =>

{

if
(OnDataReceived !=

null)

{

OnDataReceived(this,
data);

}

});

}

}

}

In the code
above, we create a proxy and then we define an event handler that listens for a
"sendStockData()" method. Notice how
this event name matches exactly the StockService method we defined in our hub on the server. This
instructs the client to raise an event every time the server calls that method.

At the point
of writing this, ‘Windows Universal apps’ seems to also suffer from a small bug
when connecting to a hub. The connection process is particularly slow due to
protocol failures. To resolve this problem, we need to define the transport
protocol during our attempt to instantiate the connection. This is achieved here
by requesting that the connection uses the Web Sockets protocol:

await connection.Start(new
WebSocketTransport());

Finally, it's
worth noting that when creating a new
HubConnection() we need to pass the hub URL, which is the same as the WebAPI
URL. Make sure you use the right URL otherwise your connection will fail.

The last step
is to update the UI in the phone and desktop apps to use the SignalR data. On
the Windows phone project, open the
MainWindow.xaml and replace the
<Grid></Grid> root element with the code below:

<Grid.RowDefinitions>

<RowDefinition
Height="150"></RowDefinition>

<RowDefinition
Height="*"></RowDefinition>

</Grid.RowDefinitions>

<TextBlock
Grid.Row="0"
Style="{StaticResource
HeaderTextBlockStyle}"
Margin="25,0,0,0">STOCK
PRICES</TextBlock>

<StackPanel
Grid.Row="1"
Margin="25,0,0,0">

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
BodyTextBlockStyle}"
Width="70">GOOG:</TextBlock>

<TextBlock
Style="{StaticResource
BodyTextBlockStyle}"
Text="{Binding
GOOG
}"></TextBlock>

</StackPanel>

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
BodyTextBlockStyle}"
Width="70">MSFT:</TextBlock>

<TextBlock

Style="{StaticResource
BodyTextBlockStyle}"
Text="{Binding
MSFT}"></TextBlock>

</StackPanel>

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
BodyTextBlockStyle}"
Width="70">APPL:</TextBlock>

<TextBlock
Style="{StaticResource
BodyTextBlockStyle}"
Text="{Binding
APPL}"></TextBlock>

</StackPanel>

</StackPanel>

We also need
to add the necessary code to retrieve and bind the data to the UI View, so open
MainView.xaml.cs and paste the code
below:

using
Newtonsoft.Json;

using
Windows.UI.Core;

using
Windows.UI.Xaml.Controls;

using
Windows.UI.Xaml.Navigation;

namespace
SignalRApp

{

public

sealed

partial

class

MainPage
:

Page

{

private

PageData
pageData;

public
MainPage()

{

this.InitializeComponent();

this.NavigationCacheMode
=

NavigationCacheMode.Required;

pageData =

new

PageData();

this.DataContext
= pageData;

var
app =

App.Current

as

App;

app.Client.OnDataReceived += Client_OnDataReceived;

}

private

void
Client_OnDataReceived(object
sender,

dynamic
e)

{

Dispatcher.RunAsync(CoreDispatcherPriority.Normal,

() =>

{

var
result =

JsonConvert.DeserializeObject<PageData>(e);

pageData.APPL = result.APPL;

pageData.GOOG = result.GOOG;

pageData.MSFT = result.MSFT;

});

}

}

}

Since this is
a universal application, we need to apply similar code changes to the Windows
Store project. Go to the MainView.xaml
and paste the following code, which should replace the root
<Grid></Grid> element:

<Grid
Background="{ThemeResource
ApplicationPageBackgroundThemeBrush}">

<Grid.RowDefinitions>

<RowDefinition
Height="150"></RowDefinition>

<RowDefinition
Height="*"></RowDefinition>

</Grid.RowDefinitions>

<TextBlock
Grid.Row="0"
Style="{StaticResource
HeaderTextBlockStyle}"
Margin="25,20,0,0">STOCK
PRICES</TextBlock>

<StackPanel
Grid.Row="1"
Margin="25,0,0,0">

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
SubheaderTextBlockStyle}"
Width="120">GOOG:</TextBlock>

<TextBlock
Style="{StaticResource
SubheaderTextBlockStyle}"
Text="{Binding
GOOG
}"></TextBlock>

</StackPanel>

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
SubheaderTextBlockStyle}"
Width="120">MSFT:</TextBlock>

<TextBlock

Style="{StaticResource
SubheaderTextBlockStyle}"
Text="{Binding
MSFT}"></TextBlock>

</StackPanel>

<StackPanel
Orientation="Horizontal">

<TextBlock
Style="{StaticResource
SubheaderTextBlockStyle}"
Width="120">APPL:</TextBlock>

<TextBlock
Style="{StaticResource
SubheaderTextBlockStyle}"
Text="{Binding
APPL}"></TextBlock>

</StackPanel>

</StackPanel>

</Grid>

And the
equivalent code-behind to provide the data for the view:

using
Newtonsoft.Json;

using
Windows.UI.Core;

using
Windows.UI.Xaml.Controls;

namespace
SignalRApp

{

public

sealed

partial

class

MainPage
:

Page

{

private

PageData
pageData;

public
MainPage()

{

this.InitializeComponent();

pageData =

new

PageData();

this.DataContext
= pageData;

var
app =

App.Current

as

App;

app.Client.OnDataReceived += Client_OnDataReceived;

}

private

void
Client_OnDataReceived(object
sender,

dynamic
e)

{

Dispatcher.RunAsync(CoreDispatcherPriority.Normal,

() =>

{

var
result =

JsonConvert.DeserializeObject<PageData>(e);

pageData.APPL = result.APPL;

pageData.GOOG = result.GOOG;

pageData.MSFT = result.MSFT;

});

}

}

}

To test the
whole system, run the WebAPI first and then run the Windows Phone and Store apps
in the order you like. The end result should look like this:



Conclusion

In this article, we saw how easy it is to get started with real-time communication across
platforms using SignalR. It doesn't matter if you're targeting web, mobile or
desktop platforms. In the end, we've proved that SignalR requires minimal code
to provide something that in the past was clunky, unreliable and very hard to
implement. I would like to close with Arthur C. Clarke's quote - "Any
sufficiently advanced technology is indistinguishable from magic". SignalR
is advanced enough to make real-time communications seem like magic.

Show more