C# – Introduction to SignalR

Mobile apps have a native support for pushing notifications from server to clients on many platforms such as Android, IOS or Windows Phone. I discussed about this feature in Android while ago in Google Cloud Messaging, ASP.NET Web Api and Android client post. Because it’s an interesting feature, we also want to have it in desktop or web application which can be done easily with SignalR, a new ASP.NET library for real-time web application. On SignalR’s homepage http://signalr.net/, you’ll find a lot of examples and codes for demonstrating how it works. In this post, I just want to show how to host SignalR on a WPF application (outside of IIS) and how SignalR basically works. The demo has a server-client model but the clients can also “broadcast” his message to other clients and the server can notify all clients with his notification.

1. Prerequisites

– I use many different libraries for this demo, so please read the prerequisites carefully before get starting.
– The latest version of SignalR requires .NET Framework 4.5 so you need at least Visual Studio 2012 for using it. For someone who is still outdated :), Microsoft has now a community edition for Visual Studio, you can download it here http://go.microsoft.com/fwlink/?LinkId=517284 (Visual Studio 2013 Community Edition).
– In the demo, SignalR server will be hosted in WPF application. For building a luxurious UI, I use Syncfusion Community Edition WPF library, you can also get it free here https://www.syncfusion.com/products/communitylicense .
– Server in WPF application uses MVVM pattern with supporting from Caliburn.Micro, read this post C# – WPF MVVM DataBinding 2014 if you have no idea how Caliburn.Micro works.
– One of clients in this demo is a web application built on AngularJS. I don’t have any post about AngularJS in my blog now but I suggest you a very good book for starting with AngularJS Pro AngularJS (Expert’s Voice in Web Development)

2. Server

2.1 Setup project and MVVM pattern

– Create a WPF Application project and name it as IntroductionToSignalR, be sure that .NET Framework 4.5 is target framework

.NET Framework 4.5

– Setup MVVM pattern with Caliburn.Micro as discussed here C# – WPF MVVM DataBinding 2014

2.2 Use Syncfusion

– Install Syncfusion Community Edition.
– Add reference to Syncfusion.Core and Syncfustion.Shared.Wpf

Syncfusion

– Make a simple GUI for notifying all clients and writing protocol of all messages between server and clients

<syncfusion:ChromelessWindow x:Class="IntroductionToSignalR.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        Title="MainView" Height="560" Width="590" WindowStartupLocation="CenterScreen"       
        xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
        TitleTextAlignment="Left"
        ShowIcon="False"
        
    >
    <syncfusion:ChromelessWindow.Resources>
        <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/Syncfusion.Shared.WPF;component/SkinManager/MetroStyle.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <Style TargetType="Button" x:Key="HDButtonStyle" BasedOn="{StaticResource MetroButtonStyle}">
            <Setter Property="Width" Value="150"></Setter>
            <Setter Property="Height" Value="30"></Setter>
        </Style>
        </ResourceDictionary>
    </syncfusion:ChromelessWindow.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"></ColumnDefinition>
            <ColumnDefinition Width="1*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <StackPanel>
            <TextBlock Margin="0,10,0,0" HorizontalAlignment="Center" Text="Command"></TextBlock>

            <Button Style="{StaticResource HDButtonStyle}" IsEnabled="{Binding IsServerRunning}" Name="Ping" Margin="0,10,0,0" >Ping</Button>
            <Button Style="{StaticResource HDButtonStyle}" IsEnabled="{Binding IsServerRunning}" Name="SendDefaultMessage" Margin="0,10,0,0"  >Send default message</Button>
            <Button Style="{StaticResource HDButtonStyle}" IsEnabled="{Binding IsServerRunning}" Name="SendDefaultObject" Margin="0,10,0,0"  >Send default object</Button>           
        </StackPanel>
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="35"></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock Margin="0,10,0,0" HorizontalAlignment="Center" Text="Protocol"></TextBlock>
            <TextBox IsReadOnly="True" Grid.Row="1" Name="Protocol"></TextBox>
        </Grid>
    </Grid>
</syncfusion:ChromelessWindow>

UI of server

– We would like to use MetroStyle instead of default theme

public partial class MainView : ChromelessWindow
{
	public MainView()
	{
		InitializeComponent();
		SkinStorage.SetVisualStyle(this, "Metro");
	}

	...
}

2.3 Use SignalR as selfhost

– Add reference to Nuget package Microsoft.AspNet.SignalR.SelfHost

SignalR Nuget reference

– SignalR is built on OWIN (Open Web Interface for .NET). OWIN defines an abstraction between .NET web servers and web applications and decouples the web application from the server, which makes OWIN ideal for self-hosting a web application in your own process, outside of IIS.

– So add reference to Nuget package Microsoft.Owin.Cors and add a Startup class for specifing components for the application pipeline

[assembly: OwinStartup(typeof(IntroductionToSignalR.Startup))]
public class Startup
{
	public void Configuration(IAppBuilder app)
	{
		app.Map("/signalr", x =>
			{
				x.UseCors(CorsOptions.AllowAll);
				HubConfiguration hubConfiguration = new HubConfiguration();
				hubConfiguration.EnableDetailedErrors = true;                 
				x.RunSignalR(hubConfiguration);
			});
	}
}

There are different ways to connect Startup class with the runtime, depending on hosting server (OwinHost, IIS, and IIS-Express). You connect the Startup class with the hosting runtime using one of the these approaches: Naming Convention, OwinStartup Attribute, AppSetting element in the configuration file. In code listing above, I use OwinStartup attribute to set up Startup class.
By default, the route URL which clients connect to your Hub is “/signalr” (also defined in code listing above). If somehow this base URL is not usable for SignalR; for example, you have a public folder in your project named signalr and you can’t change the name of that folder. So you can change the base URL by replacing “/signalr” with your desired URL.
The CorsOptions.AllowAll specifies a policy that allows all headers, all methods, any origin and supports credentials.

– Add a Hub class to provide methods that communicate with SignalR connections that connected to a Hub.

public class HDHub : Hub
{
	public void Ping()
	{
		WriteProtocol(string.Format("Client {0} send a ping message", Context.ConnectionId));

		Clients.Others.Ping();
	}

	public void SendMessage(string name,string message)
	{
		if (string.IsNullOrWhiteSpace(name))
			name = Context.ConnectionId;
		WriteProtocol(string.Format("Client {0} send a message {1}", name,message));
		
		Clients.Others.SendMessage(name,message);
	}

	public void SendObject(string name, HDObject hdObject)
	{
		if (string.IsNullOrWhiteSpace(name))
			name = Context.ConnectionId;
		WriteProtocol(string.Format("Client {0} send an object {1}", name, hdObject.DumpToString<HDObject>("hdObject")));
		Clients.Others.SendObject(name,hdObject);
	}

	private void WriteProtocol(string message)
	{
		Application.Current.Dispatcher.Invoke(() =>
		((MainView)Application.Current.MainWindow).WriteProtocol(message));
	}

	public override Task OnConnected()
	{
		WriteProtocol(string.Format("Client {0} get connected", Context.ConnectionId));
		return base.OnConnected();
	}
}

The Hub provides 3 methods for communicating: Ping, SendMessage and SendObject. Each time when a connection triggers a method, server will log who invokes the method and notify all other clients about this message.

3. Console client

– Create a Console Application and add reference to Nuget package Microsoft.AspNet.SignalR.Client.
– The client connects to server when it starts, listens to notifications and broadcasts message to other clients by sending message to Hub.

static void Main(string[] args)
{
	string url="http://localhost:26128";
	Console.WriteLine("Client starts. Connecting to " + url + " ...");
	Console.WriteLine("Press C to cancel");
	Console.WriteLine("Press P to ping");
	Console.WriteLine("Press M to send default message");
	Console.WriteLine("Press O to send default object");

	HubConnection hubConnection = new HubConnection(url);
	IHubProxy hubProxy = hubConnection.CreateHubProxy("HDHub");
	hubProxy.On("Ping", () => Console.WriteLine("A ping message"));
	hubProxy.On<string, string>("SendMessage", (name,message) => Console.WriteLine(string.Format("Client {0} want to say {1}",name,message)));
	hubProxy.On<string, HDObject>("SendObject", (name, hdObject) => Console.WriteLine(string.Format("Client {0} sends an object {1}", name,  hdObject.DumpToString<HDObject>("hdObject"))));
	hubConnection.Start().ContinueWith((t1) =>
	{
		if (t1.IsFaulted)
			Console.WriteLine("Server is not available");

		while (true)
		{
			string key = Console.ReadLine();
			if (key.Equals("c", StringComparison.OrdinalIgnoreCase))
				break;
			else if (key.Equals("p", StringComparison.OrdinalIgnoreCase))
			{
				Console.WriteLine("Console client sends a ping message");
				hubProxy.Invoke("Ping").ContinueWith(t2 =>
				{
					if (t2.IsFaulted)
						Console.WriteLine("Error when sending ping");
				}).Wait();
			}
			else if (key.Equals("m",StringComparison.OrdinalIgnoreCase))
			{
				Console.WriteLine("Console client sends a default message");
				hubProxy.Invoke("SendMessage","","Hi there").ContinueWith(t2 =>
				{
					if (t2.IsFaulted)
						Console.WriteLine("Error when sending message");
				}).Wait();
			}
			else if (key.Equals("o",StringComparison.OrdinalIgnoreCase))
			{
				Console.WriteLine("Console client sends a default object");
				hubProxy.Invoke("SendObject", "", new HDObject() { Id = 1, Name = "Console Client" }).ContinueWith(t2 =>
					{
						if (t2.IsFaulted)
							Console.WriteLine("Error when sending object");
					}).Wait();
			}
		}
	}).Wait();
   

}

4. Web client

– Create a web application project and add reference to Microsoft.AspNet.SignalR.JS and angularjs
– Design a simple Web UI with AngularJS for receiving and sending message through SignalR connections

<!DOCTYPE html>
@{
    Layout = null;
}
<html ng-app="hdsignalr">
<body ng-controller="hdsignalrcontroller">
    <button ng-click="ping()">Ping</button>
    <button ng-click="sendMessage()">Send default message</button>
    <button ng-click="sendObject()">Send default object</button>
    <div ng-bind-html="protocol">        
    </div>

    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
    <script src="http://localhost:26128/signalr/hubs"></script>   
    <script src="~/Scripts/angular.min.js"></script>
    <script src="~/Scripts/angular-sanitize.min.js"></script>
    <script src="~/Scripts/Home/home.js"></script>
</body>
</html>

and the javascript

angular.module("hdsignalr", ["ngSanitize"])
.controller("hdsignalrcontroller", function($scope)
{
    $.connection.hub.url = "http://localhost:26128/signalr";
    var hub = $.connection.hDHub;
    $.connection.hub.start().done(function () { });
    $scope.protocol = "";

    hub.client.ping = function () {
        $scope.protocol += "A ping message<br/>";
        $scope.$apply();
    };

    hub.client.sendMessage = function (name, message) {
        $scope.protocol += "Client " + name + " want to say " + message + "<br/>";
        $scope.$apply();
    };

    hub.client.sendObject = function (name, hdObject) {
        $scope.protocol += "Client " + name + " sends an object with Id: " + hdObject.Id + " and Name:" + hdObject.Name + "<br/>";
        $scope.$apply();
    };

    $scope.ping = function () {
        hub.server.ping();
        $scope.protocol += "Web client sends a ping message<br/>";        
    };

    $scope.sendMessage = function () {
        hub.server.sendMessage("", "Hi there");
        $scope.protocol += "Web client sends a default message<br/>";
    };

    $scope.sendObject = function () {
        hub.server.sendObject("", { Id: 1, Name: "Web Client" });
        $scope.protocol += "Web client sends a default object<br/>";
    };
})

The link http://localhost:26128/signalr/hubs points to your generated proxies from SignalR. These generated proxies help you to
– Simplify the syntax of the code to connect.
– Write methods that serve calls
– Call methods on the server
Using generated proxies is only optional. You can call SignalR Hubs methods without using these generated proxies.

5. Result

Here is the result when the clients talk to each other.

5.1 Server notifies all clients

Server notifies all clients

5.2 Console client broadcasts message

Console client broadcasts message

5.3 Web client broadcasts message

Web client broadcasts message

6. Conclusions

SignalR supports not only real-time web application but also selfhost application. With SignalR we have the possibilities to communicate between different kind of clients : desktop, mobile or web client. It simplifies the protocol for communicating between participants on different platforms. If you have any use case which SignalR can come to play, just try it.
Source code: https://bitbucket.org/hintdesk/dotnet-introduction-to-signalr

One thought on “C# – Introduction to SignalR”

  1. Ok… I have a question that’s just about to become a real bur in my rear-end!
    What I’m trying to do is catch an event in a web service and broad cast it to all clients (wpf application users. I have the server built no problem and I’ve build client apps but my problem is when a message is received by another client, I need to catch that incoming message and do something with that data (assign it to a string). In my case, in each client that is running, through a desktop alert. I have all the code written but I’m stumped on how to receive the data from the incoming message to work with it. Can anyone help me? That being said, I love SignalR!!!

    Thanks all.

    Jim B.

Leave a Reply

Your email address will not be published. Required fields are marked *