Introduction
Part 1 introduced the basic knowledge of SignalR and how to use PersistentConnection
in a demo. In this article, I will introduce how to use Hub
, how to scale out SignalR services and how to extend SignalR capability by your own code. For more details, please refer to SignalR official web site: http://signalr.net.
To use the downloaded examples, please refetch corresponding SignalR packages from NuGet because I deleted these packages before uploading to reduce the zip file size.
Hub
Hub
is mainly targeting server-to-client and client-to-server RPC. It is implemented based on PersistentConnection
. You can create multiple Hubs in one connection if you need. There is no difference in performance whether you define all your methods in one Hub or in multiple Hubs.
Let's create an empty web application and define a simple Hub which broadcasts a client's message to all connected clients:
public class MyChatHub : Hub
{
public async Task BroadcastMessage(string callerName, string message)
{
await Clients.All.appendnewmessage(callerName, message);
}
}
The BroadcastMessage
method is defined for the client-side code to make a RPC call to the server. And BroadcastMessage
makes a RPC call to the appendNewMessage
method of all clients . As the code comment indicates, case is insensitive when the server makes a RPC call to the client.
Note that the client-side should use camel-cased names - myChatHub
and broadcastMessage
to refer to MyChatHub
and BroadcastMessage
. This rule shows respect to JavaScript naming convention. But you can still force the client-side code to use Pascal-cased names. Actually, you can force the client-side code to use any case-sensitive names as you want. Let's create another simple Hub which returns the current server time:
[HubName("PascalCasedMyDateTimeHub")]
public class MyDateTimeHub : Hub
{
[HubMethodName("PascalCasedGetServerDateTime")]
public async Task<DateTime> GetServerDateTime()
{
return await Task.Run<DateTime>(() => DateTime.Now);
}
}
By using HubNameAttribute
and HubMethodNameAttribute
, you can specify any client-side names for your Hub and Hub methods.
To register my Hubs, I use the following code piece in Global.asax.cs:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
}
}
Here I specify "/myhubs
" as the root URL for all my Hubs with the default configuration. You have to map all your Hubs to one root URL in one web application. MapHubs
is an extended method defined in SignalR. If you do not specify arguments for MapHubs
, it will map all your Hubs to "/signalr
". But I still recommend to use your own URL to avoid uncertainty and potential name conflict.
There are 2 ways to access server Hubs in client-side JavaScript code: using an auto-generated proxy or not using it.
My first example is to using the auto-generated proxy. Actually, I need not do anything at the server side. I just need to add one line in the web page:
<head>
......
<script type="text/javascript" src="myhubs/hubs"></script>
</head>
"myhubs/hubs
" tells the server to send the auto-generated proxy - a JavaScript file in stream. The URL must be combined by your Hub URL you have mapped and the word - "hubs
". If you use the default mapped URL - "/signalr
", the auto-generated proxy URL should be "signalr/hubs
".
The following code piece from index.html indicates how to use the server Hubs:
$(function () {
var timeHubProxy = $.connection.PascalCasedMyDateTimeHub;
var chatHubProxy = $.connection.myChatHub;
chatHubProxy.client.appendNewMessage = function (clientName, message) {
addMsg(clientName + ": " + message);
};
addMsg("Connecting Hub...");
$.connection.hub.start().done(function () {
addMsg("Hub connected.");
$("#refreshServerTime").click(function () {
timeHubProxy.server.PascalCasedGetServerDateTime().done(function (serverTime) {
$("#serverTime").text(serverTime);
});
});
$("#send").click(function () {
chatHubProxy.server.broadcastMessage($("#name").val(), $("#msg").val());
});
}).fail(function () {
addMsg("Could not connect to Hub.");
});
});
You can see the JavaScript code calls the server Hub's methods just like calling local methods with the auto-generated proxy. Note that I define the appendNewMessage
method at the client side. This method is called by the server and case is insensitive.
My second example uses the server Hubs without auto-generated proxy. It becomes more complicated because I have to use the Invoke
method as the following code from index_no_generated_proxy.html indicates:
$(function () {
var connection = $.hubConnection("/myhubs");
var timeHubProxy = connection.createHubProxy("PascalCasedMyDateTimeHub");
var chatHubProxy = connection.createHubProxy("myChatHub");
chatHubProxy.on("appendNewMessage", function (clientName, message) {
addMsg(clientName + ": " + message);
});
addMsg("Connecting Hub...");
connection.start().done(function () {
addMsg("Hub connected.");
$("#refreshServerTime").click(function () {
timeHubProxy.invoke("PascalCasedGetServerDateTime").done(function (serverTime) {
$("#serverTime").text(serverTime);
});
});
$("#send").click(function () {
chatHubProxy.invoke("broadcastMessage", $("#name").val(), $("#msg").val());
});
}).fail(function () {
addMsg("Could not connect to Hub.");
});
});
The argument - "/myhubs
" is not required to initialize the connection if you use the default map - "/signalr
".
You can also write a .NET client to call the server Hubs. The code in Program.cs is very similar with the JavaScript code without the auto-generated proxy:
static void Main(string[] args)
{
var hubConn = new HubConnection("http://localhost:6473/myhubs");
var timeHubProxy = hubConn.CreateHubProxy("PascalCasedMyDateTimeHub");
var chatHubProxy = hubConn.CreateHubProxy("myChatHub");
chatHubProxy.On("appendNewMessage", delegate(string name, string message)
{
Console.WriteLine("{0}: {1}", name, message);
});
hubConn.Start().Wait();
string inputLine;
while (!string.IsNullOrEmpty(inputLine = Console.ReadLine()))
{
Task<DateTime> t = timeHubProxy.Invoke<DateTime>("PascalCasedGetServerDateTime");
t.Wait();
Console.WriteLine((DateTime)t.Result);
chatHubProxy.Invoke("broadcastMessage", "dzy", inputLine).Wait();
}
}
A full URL is required to initialize the Hub connection. And note the way how appendNewMessage
is defined.
Scaling Out
SignalR does a great job to scale out itself in a web farm with the help of SQL Server, Redis or Windows Azure Service Bus. Different packages are available for these different technologies on NuGet as Part 1 introduces:
Microsoft.AspNet.SignalR.SqlServer
: SQL Server messaging backplane. Microsoft.AspNet.SignalR.Redis
: Redis messaging backplane. Microsoft.AspNet.SignalR.ServiceBus
: Windows Azure Service Bus messaging backplane.
Due to the limitation of the development environment, here I just give an example with SQL Server. It is very similar in using the other two technologies.
Let's take the previous Hub demo as an example. To enable scaling out with SQL Server, we can follow the steps below:
Create a new database with any name in SQL Server. Let's assume the database name is SignalRScaleOut
. SignalR will create necessary tables when the Hub demo runs for the first time.
Create a new login in SQL Server. Let's give the login name and password both 'signalr
' and grant it as db_owner
for the SignalRScaleOut
database. This step is not mandatory, but to create a specific user for this database is more secure. Don't forget to uncheck 'Enforce password policy' and 'Enforce password expiration' when creating a new login.
Configure Windows Firewall inbound rules to allow external connections to SQL Server.
Install the Microsoft.AspNet.SignalR.SqlServe
r package to the demo Hub server project through NuGet.
In Global.asax.cs, modify the Application_Start
method like the following code piece:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
string connectionString = "server=127.0.0.1;uid=signalr;pwd=signalr;database=SignalRScaleOut";
GlobalHost.DependencyResolver.UseSqlServer(connectionString);
RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
}
}
Here I configure SignalR to use SQL Server with the specified connection string for enabling scaling out. The IP address '127.0.0.1' should be changed the exact IP address of your SQL Server machine.
Now the scaling out work is done. You could copy the modified Hub server demo to 2 or more different machines which have IIS installed. You would find all your Hub servers share the same data when you run the applications in browsers and connect to different servers.
The performance will be better if you enable Service Broker in your SQL Server. To check whether Service Broker is enabled for your database, you could write the following query in Management Studio:
select [name],[service_broker_guid],[is_broker_enabled]
from [master].[sys].[databases]
In the returned dataset, check the value of the is_broker_enabled
column. '1' means enabled while '0' means disabled. The following SQL command enables Service Broker for the SignalRScaleOut
database:
ALTER DATABASE SignalRScaleOut SET ENABLE_BROKER
Extensibility
For easy extensibility, SignalR is designed following the dependency injection rule. You could replace the implementation of most SignalR components via GlobalHost.DependencyResolver
like the previous code piece indicates. For example, if I want to use my own JSON serializer, I can implement the IJsonSerializer
interface and register it like the following code piece:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
GlobalHost.DependencyResolver.Register(
typeof(IJsonSerializer),
() => new MyJsonSerializer());
RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
}
}
Note that although we can use our own JSON serializer implementation, we can still only use text serialization because SignalR has internally hardcoded to use TextReader and TextWriter.
GlobalHost.DependencyResolver
implements the IDependencyResolver
interface. So you can even replace it by your own implementation like the following code piece:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
GlobalHost.DependencyResolver = new MyDependencyResolver();
RouteTable.Routes.MapHubs("/myhubs", new HubConfiguration());
}
}
Summary
The basic introduction to SignalR finishes. For advanced topics, please visit the official SignalR web site - SignalR.Net.