Saturday, March 8, 2014

ASP.NET SignalR

Have you ever encounter a system requirement that require you to create a web page that needs to be consistently updated in real time? For example, a stock market watcher. As we know, stock prices are changing from time to time during the trading period. How can we create an efficient web page which able to show the latest price quickly and real time without requiring user to keep clicking a refresh button? I would like to introduce ASP.NET SignalR to solve the problem.

Concept

My stock market watcher has 1 page only. The page allow user to view and update stock prices. Users open the Default.aspx page to view the stock prices. One of the users update the data. The service will trigger the SignalR hub to ask all the clients to refresh their own UI.

So, instead of using polling strategy by having every individual client to refresh their UI in a timely interval. They get triggered to refresh their UI when there is data change and necessary.



So, before the browser can receive the signal from SignalR hub, it needs to be registered with the SignalR hub first. The registration will create a web socket connection between browser and the server. And that, SignalR require a web socket supported browser to best work with such as IE10+. However, it still actually works with older version of browser. check out the list of supported platforms.

Implementation

I will skip some of the basic details like creating ASP.NET web application project and creating the two web pages and UI design, and straight to the point where how to get SignalR works.

First, nuget the SignalR component library or visit the SignalR site to download it manually.


Now, code your web application to start initializing the SignalR hub with the following code:

[assembly: OwinStartup(typeof(StockSample.UI.Web.Hubs.Startup))]
namespace StockSample.UI.Web.Hubs
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var hubConfiguration = new HubConfiguration();
            hubConfiguration.EnableDetailedErrors = true;
            hubConfiguration.EnableJavaScriptProxies = true;

            app.MapSignalR("/signalr", hubConfiguration);
        }
    }

}

In my Default.aspx, I have a grid view and it support edit function. Once I am done designing my UI and ensure the app is functioning, I add the following Javascript references:

<script src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.0.2.min.js"></script>
<script src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>

Then, code the following to let the browser register itself with the hub during page load:

<script type="text/javascript">
    //Get the hub proxy, this is autogenerated during SignalR startup
    var stockHubProxy = $.connection.stockHub;

    //The refresh method is to let server to trigger
    stockHubProxy.client.refresh = function (stock) {

        //Just some tracing purposes
        console.log('Refresh method triggered successfully!');
        console.log(stock);

        //Refresh the gridview with latest data
        $("#stocksGrid tr").each(function (i) {
            if ($(this)[0].id == stock.ID) {
                $(this).find('td:eq(1)').text(stock.Symbol);
                $(this).find('td:eq(2)').text(stock.Company);
                $(this).find('td:eq(3)').text(stock.Last);
                $(this).find('td:eq(4)').text(stock.Min);
                $(this).find('td:eq(5)').text(stock.Max);
                $(this).find('td:eq(6)').text(stock.Change);
                $(this).find('td:eq(7)').text(stock.Volume);

                //highlight the row that has data changed then slowly fade
                $(this).attr('style', 'background-color: yellow;');
                $(this).animate({ backgroundColor: 'white'}, 3000);
            }
        });
    };

    //This event fire when the hub has started
    $.connection.hub.start().done(function () {
        //Just to trace whether your browser successfully connected to SignalR hub
        console.log('Connected to SignalR hub, connection ID = ' + $.connection.hub.id);
    })


</script>

So, notice my stockHubProxy.client.refresh method? This method later will be used in the server code to trigger the Javascript to refresh the data in my grid view. The code below is how I trigger all the clients refresh their UI from server.

//Trigger hub
var context = GlobalHost.ConnectionManager.GetHubContext<StockHub>();

context.Clients.All.refresh(stock);

One awesome thing about SignalR is the stock object that I passed to hub clients with the above C# code. I do not need to manually serialize the C# object into something like JSON or XML, I am able to get the object with value directly and use it in Javascript.

That's it! Amazingly it is easy to use SignalR. In order to verify it is working as expected, I open up 2 browsers, one is IE11 and another is Chrome 33+. I updated one of the value from IE, then the value change immediately in the Chrome.


Summary

Surprisingly SignalR is easy to implement. If you want to have a web page which is able to real time always showing the latest data, what would you do? Web application is always stateless, no way to keep data in memory and display it like a windows form application. I suppose what most people would do is to create a Javascript timer which call a web service in a timely interval to check for any data change. So, which do you think is more efficient? Having a always alive web socket connection or every 3 seconds call a web service with Javascript?

For a quick start, this is the simplest SignalR implementation that I have done with one way from server trigger clients to perform some action. It also support the other way, from client trigger server to execute some methods or logic. You can explore further for more complicated implementation base on the API documents and sample that you can find here.

If you are interested with my complete source code, feel free to download it from here.




8 comments:

  1. Hi Seng,

    We are using SignalR in ASP.Net MVC5 application for live tiles update.
    We are using SQL Dependency for updating counts in live tiles on data change in the table.
    This is working and we are getting the Dependency change event whenever there is a data change in the physical table.
    However we have observed multiple poolings happening for a single change when there are multiple clients open.
    And the count of Polling (Calls) are getting increased for each change in the database.
    We are expecting each client to be called once for a single change.

    Below are is the code snippet.

    1. In document.Ready we are creating the proxy. Hub name is given as ServerHub
    // Proxy created on the fly
    var server = $.connection.ServerHub;
    // Declare a function on the server hub so the server can invoke it
    server.client.displayStatus = function () { getData(); };
    // Start the connection
    $.connection.hub.start()
    2. In getData we are making the API Call
    3. In the api call we have the code for SQL Dependency Start, Stop and registering OnChange event.
    SqlDependency.Stop(ConfigurationManager.ConnectionStrings["DBConnectionKey].ConnectionString);
    SqlDependency.Start(ConfigurationManager.ConnectionStrings["BConnectionKey"].ConnectionString);
    .. // SQL Command creation
    SqlDependency dependency = new SqlDependency(command);
    dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
    4. In the on change event ServerHub.Show is called
    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
    if (e.Type == SqlNotificationType.Change)
    ServerHub.Show();
    }

    5. Below is the show method in ServerHub class
    public static void Show()
    {
    IHubContext context = GlobalHost.ConnectionManager.GetHubContext();
    context.Clients.All.displayStatus();
    }


    Can you please advice us in resolving this issue.

    Thanks in advance.

    ReplyDelete
    Replies
    1. Also I tired removing the event handler. Still it is not working.

      Delete
    2. Hi Sridhar,

      Base on your code snippet, your hub is actually triggering all the registered clients to make call to the API that will create multiple SqlDepedency. It is better to create another new API method for the getData() method use to get the latest data from the database and then display in the UI.

      The SqlDepedency Start, Stop, OnChange event handling should be done once only when your API service is starting up.

      Delete
    3. "The SqlDepedency Start, Stop, OnChange event handling should be done once only when your API service is starting up" - doesn't MSDN example http://goo.gl/lMUarp contradict that?

      I'm trying to clarify it with G+ help here https://plus.google.com/u/1/+VladimirKelman/posts/QuBj7oeEvA6

      Delete
  2. Hi,
    Correct me if I am wrong... In real time we have N number of stocks, so we query it from SQL and add it to Dictionary ? .So do i need to use same approach or any other way to handle that situations?
    Thanks,
    Manjunath

    ReplyDelete
  3. Sylvester please show hopw to do this using a data sourcecontrol with db data

    ReplyDelete

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...