Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET SignalR Basis Step by Step (Part 2)

0.00/5 (No votes)
2 Sep 2013 2  
Introducing Hub, scaling out and extensibility in SignalR

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)
        {
            // Case-insensitive when the server RPC the client's methods
            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 () {
            // Connect Hubs without the generated proxy
            var timeHubProxy = $.connection.PascalCasedMyDateTimeHub;
            var chatHubProxy = $.connection.myChatHub;
            // Register client function to be called by server
            chatHubProxy.client.appendNewMessage = function (clientName, message) {
                addMsg(clientName + ": " + message);
            };
            // Start the hub connection
            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 () {
    // Connect Hubs without the generated proxy
    var connection = $.hubConnection("/myhubs");
    var timeHubProxy = connection.createHubProxy("PascalCasedMyDateTimeHub");
    var chatHubProxy = connection.createHubProxy("myChatHub");
    // Register client function to be called by server
    chatHubProxy.on("appendNewMessage", function (clientName, message) {
        addMsg(clientName + ": " + message);
    });
    // Start the hub connection
    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)
{
    // Almost the same usage as JavaScript without generated proxy
    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.SqlServerSQL 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.SqlServer 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here