ASP.NET SignalR Hubs API Guide - .NET Client (SignalR 1.x)
by Patrick Fletcher, Tom Dykstra
This document provides an introduction to using the Hubs API for SignalR version 2 in .NET clients, such as Windows Store (WinRT), WPF, Silverlight, and console applications.
The SignalR Hubs API enables you to make remote procedure calls (RPCs) from a server to connected clients and from clients to the server. In server code, you define methods that can be called by clients, and you call methods that run on the client. In client code, you define methods that can be called from the server, and you call methods that run on the server. SignalR takes care of all of the client-to-server plumbing for you.
SignalR also offers a lower-level API called Persistent Connections. For an introduction to SignalR, Hubs, and Persistent Connections, or for a tutorial that shows how to build a complete SignalR application, see SignalR - Getting Started.
Overview
This document contains the following sections:
- Client Setup
- How to create the Hub proxy
How to define methods on the client that the server can call
- How to call server methods from the client
- How to handle connection lifetime events
- How to handle errors
- How to enable client-side logging
WPF, Silverlight, and console application code samples for client methods that the server can call
For a sample .NET client projects, see the following resources:
- gustavo-armenta / SignalR-Samples on GitHub.com (WinRT, Silverlight, console app examples).
- DamianEdwards / SignalR-MoveShapeDemo / MoveShape.Desktop on GitHub.com (WPF example).
- SignalR / Microsoft.AspNet.SignalR.Client.Samples on GitHub.com (Console app example).
For documentation on how to program the server or JavaScript clients, see the following resources:
Links to API Reference topics are to the .NET 4.5 version of the API. If you’re using .NET 4, see the .NET 4 version of the API topics.
Client setup
Install the Microsoft.AspNet.SignalR.Client NuGet package (not the Microsoft.AspNet.SignalR package). This package supports WinRT, Silverlight, WPF, console application, and Windows Phone clients, for both .NET 4 and .NET 4.5.
If the version of SignalR that you have on the client is different from the version that you have on the server, SignalR is often able to adapt to the difference. For example, when SignalR version 2.0 is released and you install that on the server, the server will support clients that have 1.1.x installed as well as clients that have 2.0 installed. If the difference between the version on the server and the version on the client is too great, SignalR throws an InvalidOperationException
exception when the client tries to establish a connection. The error message is “You are using a version of the client that isn't compatible with the server. Client version X.X, server version X.X
”.
How to establish a connection
Before you can establish a connection, you have to create a HubConnection
object and create a proxy. To establish the connection, call the Start
method on the HubConnection
object.
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
3: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
4: await hubConnection.Start();
[!NOTE] For JavaScript clients you have to register at least one event handler before calling the
Start
method to establish the connection. This is not necessary for .NET clients. For JavaScript clients, the generated proxy code automatically creates proxies for all Hubs that exist on the server, and registering a handler is how you indicate which Hubs your client intends to use. But for a .NET client you create Hub proxies manually, so SignalR assumes that you will be using any Hub that you create a proxy for.
The sample code uses the default “/signalr” URL to connect to your SignalR service. For information about how to specify a different base URL, see ASP.NET SignalR Hubs API Guide - Server - The /signalr URL.
The Start
method executes asynchronously. To make sure that subsequent lines of code don’t execute until after the connection is established, use await
in an ASP.NET 4.5 asynchronous method or .Wait()
in a synchronous method. Don’t use .Wait()
in a WinRT client.
[!code-csharpMain]
1: await connection.Start();
[!code-cssMain]
1: connection.Start().Wait();
The HubConnection
class is thread-safe.
Cross-domain connections from Silverlight clients
For information about how to enable cross-domain connections from Silverlight clients, see Making a Service Available Across Domain Boundaries.
How to configure the connection
Before you establish a connection, you can specify any of the following options:
- Concurrent connections limit.
- Query string parameters.
- The transport method.
- HTTP headers.
- Client certificates.
How to set the maximum number of concurrent connections in WPF clients
In WPF clients, you might have to increase the maximum number of concurrent connections from its default value of 2. The recommended value is 10.
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
3: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
4: ServicePointManager.DefaultConnectionLimit = 10;
5: await hubConnection.Start();
For more information, see ServicePointManager.DefaultConnectionLimit.
How to specify query string parameters
If you want to send data to the server when the client connects, you can add query string parameters to the connection object. The following example shows how to set a query string parameter in client code.
[!code-csharpMain]
1: var querystringData = new Dictionary<string, string>();
2: querystringData.Add("contosochatversion", "1.0");
3: var connection = new HubConnection("http://contoso.com/", querystringData);
The following example shows how to read a query string parameter in server code.
[!code-csharpMain]
1: public class StockTickerHub : Hub
2: {
3: public override Task OnConnected()
4: {
5: var version = Context.QueryString["contosochatversion"];
6: if (version != "1.0")
7: {
8: Clients.Caller.notifyWrongVersion();
9: }
10: return base.OnConnected();
11: }
12: }
How to specify the transport method
As part of the process of connecting, a SignalR client normally negotiates with the server to determine the best transport that is supported by both server and client. If you already know which transport you want to use, you can bypass this negotiation process. To specify the transport method, pass in a transport object to the Start method. The following example shows how to specify the transport method in client code.
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
3: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
4: await hubConnection.Start(new LongPollingTransport());
The Microsoft.AspNet.SignalR.Client.Transports namespace includes the following classes that you can use to specify the transport.
- LongPollingTransport
- ServerSentEventsTransport
- WebSocketTransport (Available only when both server and client use .NET 4.5.)
- AutoTransport (Automatically chooses the best transport that is supported by both the client and the server. This is the default transport. Passing this in to the
Start
method has the same effect as not passing in anything.)
The ForeverFrame transport is not included in this list because it is used only by browsers.
For information about how to check the transport method in server code, see ASP.NET SignalR Hubs API Guide - Server - How to get information about the client from the Context property. For more information about transports and fallbacks, see Introduction to SignalR - Transports and Fallbacks.
How to specify HTTP headers
To set HTTP headers, use the Headers
property on the connection object. The following example shows how to add an HTTP header.
[!code-csharpMain]
1: hubConnection = new hubConnection("http://www.contoso.com/");
2: connection.Headers.Add("headername", "headervalue");
3: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
4: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
5: await connection.Start();
How to specify client certificates
To add client certificates, use the AddClientCertificate
method on the connection object.
[!code-csharpMain]
1: hubConnection = new hubConnection("http://www.contoso.com/");
2: hubConnection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
3: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
4: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
5: await connection.Start();
How to create the Hub proxy
In order to define methods on the client that a Hub can call from the server, and to invoke methods on a Hub at the server, create a proxy for the Hub by calling CreateHubProxy
on the connection object. The string you pass in to CreateHubProxy
is the name of your Hub class, or the name specified by the HubName
attribute if one was used on the server. Name matching is case-insensitive.
Hub class on server
[!code-csharpMain]
1: public class StockTickerHub : Hub
Create client proxy for the Hub class
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
3: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
4: await hubConnection.Start();
If you decorate your Hub class with a HubName
attribute, use that name.
Hub class on server
[!code-csharpMain]
1: [HubName("stockTicker")]
2: public class StockTickerHub : Hub
Create client proxy for the Hub class
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("stockTicker");
3: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
4: Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
5: await hubConnection.Start();
The proxy object is thread-safe. In fact, if you call HubConnection.CreateHubProxy
multiple times with the same hubName
, you get the same cached IHubProxy
object.
How to define methods on the client that the server can call
To define a method that the server can call, use the proxy’s On
method to register an event handler.
Method name matching is case-insensitive. For example, Clients.All.UpdateStockPrice
on the server will execute updateStockPrice
, updatestockprice
, or UpdateStockPrice
on the client.
Different client platforms have different requirements for how you write method code to update the UI. The examples shown are for WinRT (Windows Store .NET) clients. WPF, Silverlight, and console application examples are provided in a separate section later in this topic.
Methods without parameters
If the method you’re handling does not have parameters, use the non-generic overload of the On
method:
Server code calling client method without parameters
[!code-csharpMain]
1: public class StockTickerHub : Hub
2: {
3: public void NotifyAllClients()
4: {
5: Clients.All.Notify();
6: }
7: }
WinRT Client code for method called from server without parameters (see WPF and Silverlight examples later in this topic)
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
3: stockTickerHub.On("notify", () =>
4: // Context is a reference to SynchronizationContext.Current
5: Context.Post(delegate
6: {
7: textBox.Text += "Notified!\n";
8: }, null)
9: );
10: await hubConnection.Start();
Methods with parameters, specifying the parameter types
If the method you’re handling has parameters, specify the types of the parameters as the generic types of the On
method. There are generic overloads of the On
method to enable you to specify up to 8 parameters (4 on Windows Phone 7). In the following example, one parameter is sent to the UpdateStockPrice
method.
Server code calling client method with a parameter
[!code-csharpMain]
1: public void BroadcastStockPrice(Stock stock)
2: {
3: context.Clients.Others.UpdateStockPrice(stock);
4: }
The Stock class used for the parameter
[!code-csharpMain]
1: public class Stock
2: {
3: public string Symbol { get; set; }
4: public decimal Price { get; set; }
5: }
WinRT Client code for a method called from server with a parameter (see WPF and Silverlight examples later in this topic)
[!code-csharpMain]
1: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
2: // Context is a reference to SynchronizationContext.Current
3: Context.Post(delegate
4: {
5: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
6: }, null)
7: );
Methods with parameters, specifying dynamic objects for the parameters
As an alternative to specifying parameters as generic types of the On
method, you can specify parameters as dynamic objects:
Server code calling client method with a parameter
[!code-csharpMain]
1: public void BroadcastStockPrice(Stock stock)
2: {
3: context.Clients.Others.UpdateStockPrice(stock);
4: }
The Stock class used for the parameter
[!code-csharpMain]
1: public class Stock
2: {
3: public string Symbol { get; set; }
4: public decimal Price { get; set; }
5: }
WinRT Client code for a method called from server with a parameter, using a dynamic object for the parameter (see WPF and Silverlight examples later in this topic)
[!code-csharpMain]
1: stockTickerHubProxy.On("UpdateStockPrice", stock =>
2: // Context is a reference to SynchronizationContext.Current
3: Context.Post(delegate
4: {
5: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
6: }, null)
7: );
How to remove a handler
To remove a handler, call its Dispose
method.
Client code for a method called from server
[!code-csharpMain]
1: var updateStockPriceHandler = stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
2: Context.Post(delegate
3: {
4: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
5: }, null)
6: );
Client code to remove the handler
[!code-cssMain]
1: updateStockPriceHandler.Dispose();
How to call server methods from the client
To call a method on the server, use the Invoke
method on the Hub proxy.
If the server method has no return value, use the non-generic overload of the Invoke
method.
Server code for a method that has no return value
[!code-csharpMain]
1: public class StockTickerHub : Hub
2: {
3: public void JoinGroup(string groupName)
4: {
5: Groups.Add(Context.ConnectionId, groupName);
6: }
7: }
Client code calling a method that has no return value
[!code-csharpMain]
1: stockTickerHubProxy.Invoke("JoinGroup", hubConnection.ConnectionID, "SignalRChatRoom");
If the server method has a return value, specify the return type as the generic type of the Invoke
method.
Server code for a method that has a return value and takes a complex type parameter
[!code-csharpMain]
1: public IEnumerable<Stock> AddStock(Stock stock)
2: {
3: _stockTicker.AddStock(stock);
4: return _stockTicker.GetAllStocks();
5: }
The Stock class used for the parameter and return value
[!code-csharpMain]
1: public class Stock
2: {
3: public string Symbol { get; set; }
4: public decimal Price { get; set; }
5: }
Client code calling a method that has a return value and takes a complex type parameter, in an ASP.NET 4.5 async method
[!code-csharpMain]
1: var stocks = await stockTickerHub.Invoke<IEnumerable<Stock>>("AddStock", new Stock() { Symbol = "MSFT" });
2: foreach (Stock stock in stocks)
3: {
4: textBox.Text += string.Format("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
5: }
Client code calling a method that has a return value and takes a complex type parameter, in a synchronous method
[!code-csharpMain]
1: var stocks = stockTickerHub.Invoke<IEnumerable<Stock>>("AddStock", new Stock() { Symbol = "MSFT" }).Result;
2: foreach (Stock stock in stocks)
3: {
4: textBox.Text += string.Format("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
5: }
The Invoke
method executes asynchronously and returns a Task
object. If you don’t specify await
or .Wait()
, the next line of code will execute before the method that you invoke has finished executing.
How to handle connection lifetime events
SignalR provides the following connection lifetime events that you can handle:
Received
: Raised when any data is received on the connection. Provides the received data.ConnectionSlow
: Raised when the client detects a slow or frequently dropping connection.Reconnecting
: Raised when the underlying transport begins reconnecting.Reconnected
: Raised when the underlying transport has reconnected.StateChanged
: Raised when the connection state changes. Provides the old state and the new state. For information about connection state values see ConnectionState Enumeration.Closed
: Raised when the connection has disconnected.
For example, if you want to display warning messages for errors that are not fatal but cause intermittent connection problems, such as slowness or frequent dropping of the connection, handle the ConnectionSlow
event.
[!code-csharpMain]
1: hubConnection.ConnectionSlow += () => Console.WriteLine("Connection problems.");
For more information, see Understanding and Handling Connection Lifetime Events in SignalR.
How to handle errors
If you don’t explicitly enable detailed error messages on the server, the exception object that SignalR returns after an error contains minimal information about the error. For example, if a call to newContosoChatMessage
fails, the error message in the error object contains “There was an error invoking Hub method 'contosoChatHub.newContosoChatMessage'.
” Sending detailed error messages to clients in production is not recommended for security reasons, but if you want to enable detailed error messages for troubleshooting purposes, use the following code on the server.
[!code-csharpMain]
1: var hubConfiguration = new HubConfiguration();
2: hubConfiguration.EnableDetailedErrors = true;
3: RouteTable.Routes.MapHubs(hubConfiguration);
To handle errors that SignalR raises, you can add a handler for the Error
event on the connection object.
[!code-csharpMain]
1: hubConnection.Error += ex => Console.WriteLine("SignalR error: {0}", ex.Message);
To handle errors from method invocations, wrap the code in a try-catch block.
[!code-csharpMain]
1: try
2: {
3: IEnumerable<Stock> stocks = await stockTickerHub.Invoke<IEnumerable<Stock>>("GetAllStocks");
4: foreach (Stock stock in stocks)
5: {
6: Console.WriteLine("Symbol: {0} price: {1}", stock.Symbol, stock.Price);
7: }
8: }
9: catch (Exception ex)
10: {
11: Console.WriteLine("Error invoking GetAllStocks: {0}", ex.Message);
12: }
How to enable client-side logging
To enable client-side logging, set the TraceLevel
and TraceWriter
properties on the connection object.
[!code-csharpMain]
1: var hubConnection = new HubConnection("http://www.contoso.com/");
2: hubConnection.TraceLevel = TraceLevels.All;
3: hubConnection.TraceWriter = Console.Out;
4: IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
5: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
6: await hubConnection.Start();
WPF, Silverlight, and console application code samples for client methods that the server can call
The code samples shown earlier for defining client methods that the server can call apply to WinRT clients. The following samples show the equivalent code for WPF, Silverlight, and console application clients.
Methods without parameters
WPF client code for method called from server without parameters
[!code-csharpMain]
1: stockTickerHub.On<Stock>("notify", () =>
2: Dispatcher.InvokeAsync(() =>
3: {
4: SignalRTextBlock.Text += string.Format("Notified!");
5: })
6: );
Silverlight client code for method called from server without parameters
[!code-csharpMain]
1: stockTickerHub.On<Stock>("notify", () =>
2: // Context is a reference to SynchronizationContext.Current
3: Context.Post(delegate
4: {
5: textBox.Text += "Notified!";
6: }, null)
7: );
Console application client code for method called from server without parameters
[!code-csharpMain]
1: stockTickerHubProxyProxy.On("Notify", () => Console.WriteLine("Notified!"));
Methods with parameters, specifying the parameter types
WPF client code for a method called from server with a parameter
[!code-csharpMain]
1: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
2: Dispatcher.InvokeAsync(() =>
3: {
4: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
5: })
6: );
Silverlight client code for a method called from server with a parameter
[!code-csharpMain]
1: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
2: // Context is a reference to SynchronizationContext.Current
3: Context.Post(delegate
4: {
5: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
6: }, null)
7: );
Console application client code for a method called from server with a parameter
[!code-csharpMain]
1: stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock =>
2: Console.WriteLine("Symbol {0} Price {1}", stock.Symbol, stock.Price));
Methods with parameters, specifying dynamic objects for the parameters
WPF client code for a method called from server with a parameter, using a dynamic object for the parameter
[!code-csharpMain]
1: stockTickerHubProxy.On("UpdateStockPrice", stock =>
2: Dispatcher.InvokeAsync(() =>
3: {
4: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
5: })
6: );
Silverlight client code for a method called from server with a parameter, using a dynamic object for the parameter
[!code-csharpMain]
1: stockTickerHubProxy.On("UpdateStockPrice", stock =>
2: // Context is a reference to SynchronizationContext.Current
3: Context.Post(delegate
4: {
5: textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
6: }, null)
7: );
Console application client code for a method called from server with a parameter, using a dynamic object for the parameter
[!code-csharpMain]
1: stockTickerHubProxy.On("UpdateStockPrice", stock =>
2: Console.WriteLine("Symbol {0} Price {1}", stock.Symbol, stock.Price));
|