Click here to Skip to main content
15,867,568 members
Articles / Web Development / HTML5

Using Task Factory to increase the performance of your web app plus using Websockets through SignalR to broadcast data from Server to Client

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
15 Dec 2014CPOL11 min read 33K   715   33   2
In this article I will demonstrate two things: First a very basic and simple code snippet to show how we can use task factory to make our web applications more responsive, the next part will be on how to use the SignalR to broadcast messages from the Web Server to all the clients connected.

Introduction

What made me think of writing this article is a project that I worked on 8 years ago. It was a stock trading web application, where each page contained three grids and sometimes more. To make the grids update with real time data we used AJAX polling: every two seconds each grid will send a request to the server to ask for new changes, and then update itself with the new data. Now I can think of many things that can be done in similar scenarios to boost performance and decrease the amount of data packets that will be sent across the network, and also decrease the pressure on the Web Server by eliminating the Polled requests coming from the clients and replacing them by a server broadcast.

Background

This article consists of two sections, one for showing how to use Task Factory to split the work done at the server side between different threads, and thus decreasing the amount of time needed to fetch data for the clients. The other section will show how to build a real time stock application that will receive updates from server broadcasts and render them to the client.

For the second part, I will be using the ASP.net SignalR Library, which allows the servers to push data to clients rather than waiting for the clients to request data. Now why websockets is mentioned in the title? This is because SignalR will use the new Webscocket transport if the conditions of Websocket are available ( IIS version, Web server OS, browser compatibility...), else it will revert back to the old methods of data transfer. Microsoft has built a sample that shows how to use the new SignalR, it is also a Stock application, and there is a very nice and detailed article about this subject at Introduction to SignalR. I used this article to learn all what I need, and my sample is built by using snippets from the code provided. However I added new features and rewrote the application in a simpler style. You must have Visual Studio 2012 or later.

Part 1:

In this part we will be building a small web application with one page that has three grids. These grids will be reading from methods that will simulate a 2 seconds delay. Begin by creating a new ASP.net Web forms application, name it WebParallelProgramming.

Image 1

From solution explorer, add a new project class library, name it DataLayer.

Image 2

Inside this library, create a new class and name it Stock. copy and paste the below code inside this class.

 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataLayer
{
    public class Stock
    {
        public string Name { get; set; }
        public string Symbol { get; set; }
        public double LastValue { get; set; }
        public double Change { get; set; }
        public string Currency { get; set; }
        public string ImageName { get; set; }

        public Stock(string name, string symbol, double lastValue, double change, string currency, string imageName)
        {
            this.Name = name;
            this.Symbol = symbol;
            this.LastValue = lastValue;
            this.Change = change;
            this.Currency = currency;
            this.ImageName = imageName;
        }

        public static List<Stock> GetPreciousMetals()
        {
            System.Threading.Thread.Sleep(2000);
            List<Stock> preciousMetals = new List<Stock>();

            preciousMetals.Add(new Stock("First Majestic Silver", "TSX FR", 5.23, 12.72, "CAD", "CA"));
            preciousMetals.Add(new Stock("Newmont", "NYSE NEM", 20.01, 4.87, "USD", "US"));
            preciousMetals.Add(new Stock("Endeavour Silver", "TSX EDR", 2.88, 2.86, "CAD", "CA"));
            preciousMetals.Add(new Stock("Freeport-McMoRan", "NYSE FCX", 25.03, 0, "USD", "US"));
            preciousMetals.Add(new Stock("Petaquilla Minerals", "TSX PTQ", 0.04, 33.33, "CAD", "CA"));

            return preciousMetals;

        }

        public static List<Stock> GetStocks()
        {
            System.Threading.Thread.Sleep(2000);

            List<Stock> stocks = new List<Stock>();

            stocks.Add(new Stock("Bank Audi GDR", "BAG Bank", 15.12, 11, "LBP", "LB"));
            stocks.Add(new Stock("National American Bank", "NAM Bank", 22.1, 3.21, "USD", "US"));
            stocks.Add(new Stock("Mono Software", "MS", 2.3, 7.1, "EURO", "GE"));
            stocks.Add(new Stock("Funds Trusts", "FT", 14.04, 2.9, "USD", "US"));
            stocks.Add(new Stock("Food and Beverages CO", "FB CO", 22.17, 22.12, "CAD", "CA"));

            return stocks;

        }

        public static List<Stock> GetMoneyStocks()
        {
            System.Threading.Thread.Sleep(2000);

            List<Stock> moneyStocks = new List<Stock>();

            moneyStocks.Add(new Stock("European Euro", "EURO", 1.2395, 0.1, "USD", "EU"));
            moneyStocks.Add(new Stock("United Kingdom Pound", "Pound", 1.5709, 3.21, "USD", "GB"));
            moneyStocks.Add(new Stock("Japanese Yen", "Yen", 0.0084, 1.2, "USD", "JA"));
            moneyStocks.Add(new Stock("Canadian Dollar", "CAD", 0.87, 1.2, "USD", "CA"));

            return moneyStocks;

        }
    }
}

Here we just created a simple stock class with some properties and data retrieval methods. Notice also the Thread.Sleep(2000) statement that will force the method to wait for 2 seconds before returning data.

Now download and extract this zip file: flags.zip. It contains images of some countries flags, copy these images into the Images folder of you web application. Now right click you Web application and select Add-> Skin File. Accept the default name of Skin1 suggested by Visual Studio and click ok. Click Yes when prompted to place this file inside the App-Themes folder. Copy and paste the below inside the skin file:

ASP.NET
<asp:GridView  runat="server" AutoGenerateColumns="false" HeaderStyle-HorizontalAlign="Center" AlternatingRowStyle-BackColor="LightSteelBlue"  > </asp:GridView>

Replace the code inside your Default.aspx page with the below:

ASP.NET
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" Theme="Skin1" CodeBehind="Default.aspx.cs" Inherits="WebParallelProgramming._Default" %>

<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
</asp:Content>
<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
    <asp:Label ID="lblServerResponseTime" runat="server" />
    <h2>Market
    </h2>
    <asp:GridView ID="gvStocks" runat="server">
        <Columns>
            <asp:ImageField DataImageUrlField="ImageName"
                DataImageUrlFormatString="~\Images\{0}.gif"
                AlternateText="Country Photo"
                NullDisplayText="No image on file."
                HeaderText=""
                ReadOnly="true" />
            <asp:BoundField DataField="Name" HeaderText="Company" />
            <asp:BoundField DataField="Symbol" HeaderText="Exchange Symbol" />
            <asp:BoundField DataField="LastValue" HeaderText="Price" />
            <asp:BoundField DataField="Change" HeaderText="Change" />
            <asp:BoundField DataField="Currency" HeaderText="Currency" />

        </Columns>
    </asp:GridView>

    <br />
    <h2>Precious Metals</h2>
    <asp:GridView ID="gvMetals" runat="server">
        <Columns>
            <asp:ImageField DataImageUrlField="ImageName"
                DataImageUrlFormatString="~\Images\{0}.gif"
                AlternateText="Country Photo"
                NullDisplayText="No image on file."
                HeaderText=""
                ReadOnly="true" />
            <asp:BoundField DataField="Name" HeaderText="Company" />
            <asp:BoundField DataField="Symbol" HeaderText="Exchange Symbol" />
            <asp:BoundField DataField="LastValue" HeaderText="Price" />
            <asp:BoundField DataField="Change" HeaderText="Change" />
            <asp:BoundField DataField="Currency" HeaderText="Currency" />

        </Columns>
    </asp:GridView>

    <br />
    <h2>Currenct Stock Exchange</h2>
    <asp:GridView ID="gvMoney" runat="server">
        <Columns>
            <asp:ImageField DataImageUrlField="ImageName"
                DataImageUrlFormatString="~\Images\{0}.gif"
                AlternateText="Country Photo"
                NullDisplayText="No image on file."
                HeaderText=""
                ReadOnly="true" />
            <asp:BoundField DataField="Name" HeaderText="Company" />
            <asp:BoundField DataField="Symbol" HeaderText="Exchange Symbol" />
            <asp:BoundField DataField="LastValue" HeaderText="Price" />
            <asp:BoundField DataField="Change" HeaderText="Change" />
            <asp:BoundField DataField="Currency" HeaderText="Currency" />

        </Columns>
    </asp:GridView>

</asp:Content>

Add a reference to the DataLayer library from your project by right clicking your webapp and selecting Add Reference:

Image 3

replace the code inside your Default.aspx.cs file with the following:

C#
using DataLayer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebParallelProgramming
{
    public partial class _Default : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            DateTime startDate = DateTime.Now;

            gvStocks.DataSource = Stock.GetStocks();
            gvStocks.DataBind();

            gvMetals.DataSource = Stock.GetPreciousMetals();
            gvMetals.DataBind();

            gvMoney.DataSource = Stock.GetMoneyStocks();
            gvMoney.DataBind();

            TimeSpan span = (DateTime.Now - startDate);
            lblServerResponseTime.Text = string.Format("Server reponser time was: {0} seconds", span.Seconds);

        }
    }
}

Here we are just binding the grids to their data sources, we are also populating a label with the time required by the server for the response. Of course this is not the correct way to calculate the server response time, but only for simplicity we are writing it like this. Run your application, you should see something like the below, notice that the server response time is 6 seconds.

Image 4

Now we will use the Task parallel programming to split the data retrieval for these three grids among three different threads. Replace the code inside your Load method with the below:

C#
  DateTime startDate = DateTime.Now;

            List<Stock> stocks = new List<Stock>();
            List<Stock> metals = new List<Stock>();
            List<Stock> money = new List<Stock>();

            Task t1 = new Task
(

    () =>
    {
        stocks = Stock.GetStocks();
    }
);

            Task t2 = new Task
(

    () =>
    {
        metals = Stock.GetPreciousMetals();
    }
);

            Task t3 = new Task
(

    () =>
    {
        money = Stock.GetMoneyStocks();
    }
);

            t1.Start();
            t2.Start();
            t3.Start();
            Task.WaitAll(t1, t2, t3);

            gvStocks.DataSource = stocks;
            gvStocks.DataBind();

            gvMetals.DataSource = metals;
            gvMetals.DataBind();

            gvMoney.DataSource = money;
            gvMoney.DataBind();

            TimeSpan span = (DateTime.Now - startDate);
            lblServerResponseTime.Text = string.Format("Server reponser time was: {0} seconds", span.Seconds);

Here we declared three new tasks, assigned each task to go and fetch the data for a specific grid. Then we waited for the three tasks to finish before binding the returned data to the grids. Run your application and notice the response time, it is now down to two seconds.

Image 5

 

Part 2:

In this part, we will use the SignalR to broadcast stock updates from the server to all the clients. To learn more about SignalR, please check this link:  Introduction to SignalR. SignalR will use websockets whenever possible, and will use the old methods if the conditions for Websocket are not available. 

Now in our application, instead of clients polling the server every specified time for updates, the server will broadcast the updates to the clients. This will significantly reduce the load on the server, and also on the client side. It will also reduce the network traffic between the server side and client side. 

Just to understand a little bit the power of websockets, wbesocket.org did an experiment, and for one use case, there were 100, 000 clients polling through http requests every one second, the total network throughput was 665 Mb per second. When the same number of clients was receiving one message from the server every second through websockets, the total network throughput was 1.526 Mb per second! Pretty amazing, you can find the link here:  http://www.websocket.org/quantum.html. Following is a figure from the same link representing a "comparison of the unnecessary network throughput overhead between the polling and the WebSocket applications":

Image 6

where 

  • Use case A: 1,000 clients receive 1 message per second.
  • Use case B: 10,000 clients receive 1 message per second.
  • Use case C: 100,000 clients receive 1 message per second.

In the sample built by Microsoft, the server will broadcast the updates of each stock one at a time, we will do it in a list, that is the server will broadcast the whole list of stocks updated, and the clients will digest these updates. We will also change the color of the stocks updated according to their change.

Begin by adding SignalR to your project by opening the Tools->Library Package Manager->Package Manager Console and running the command: install-package Microsoft.AspNet.SignalR.

Now as we will be using the SignalR Hub API to handle client-server communications, we need to create a class that will derive from the SignalR Hub class and will be responsible for receiving connections and method calls from clients. Right click your project and add a class named "StockBroadcasterHub.cs". Replace the code inside the class with the following:

C#
using DataLayer;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WebParallelProgramming
{
    //The Hub class provides methods that allow clients to communicate with the server using SignalR connections connected to a Hub.
    [HubName("stockBroadcaster")]
    public class StockBroadcasterHub : Hub//Don't forget to inherit from the Hub class
    {
        private readonly StockBroadcaster _stockBroadcaster;

        public StockBroadcasterHub() : this(StockBroadcaster.Instance) { }

        public StockBroadcasterHub(StockBroadcaster stockBroadcaster)
        {
            _stockBroadcaster = stockBroadcaster;
        }

        public IEnumerable<Stock> GetAllStocks()
        {
            return _stockBroadcaster.GetAllStocks();
        }
    }
}

Note: you can also right click the project and  select Add->SignalR Hub Class (v2), this will add the necessary attributes for you. 

This class is used to define the methods that the clients can call on the server, like the GetAllStocks() method. In our case we will not be implementing calls from the clients, only server broadcasts, however it is good to know. Also this hub is used by the clients to open connections to the server.

The HubName attribute is used to specify how the Hub will be referenced on the client side. We will see how to use it in the JavaScript code later on. If you don't use this attribute then the default name on the client will be a camel-cased version of the class name, which in this case would be stockBroadcaster.

Now since a new Hub class instance will be created on each connection or call from the client, we will need to add a class that will store stock data, perform random additions or subtractions to the stock prices and broadcast the updates to the clients.  Right click your project and add a class named: "StockBroadcaster.cs". Replace the class template code with the following:

C#
using DataLayer;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;

namespace WebParallelProgramming
{
    public class StockBroadcaster
    {
        //Declare and initialze a TimeSpan that will be used by the timer which will be scheduled to send updates to clients
        private readonly TimeSpan _refreshRate = TimeSpan.FromMilliseconds(2000);

        //Declare a random that will help us in deciding which stocks to update, so that we don't have to update all the stocks at the same time.
        private readonly Random _willUpdate = new Random();

        //declare the timer
        private readonly Timer _timer;

        //Used to check if other threads are also updating the same instance, the volatile keyword is used to ensure thread safety.
        private volatile bool _updatingStockPrices = false;

        //This is the lock that will be used to ensure thread safety when updating the stocks.
        private readonly object _updateStockPricesLock = new object();

        //Encapsulates all information about a SignalR connection for a Hub
        private IHubConnectionContext<dynamic> Clients
        {
            get;
            set;
        }

        //Initialize the static Singleton instance. Lazy initialization is used to ensure that the instance creation is threadsafe.
        private readonly static Lazy<StockBroadcaster> _instance = new Lazy<StockBroadcaster>(() => new StockBroadcaster(GlobalHost.ConnectionManager.GetHubContext<StockBroadcasterHub>().Clients));

        //Initialize the dictionary that will be be used to hold the stocks that will be returned to the client. We used
        //the ConcurrentDictionary for thread safety.If you used a Dictionary object make sure to explicitly lock the dictionary before making changes to it.
        private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();

        //Since the constructer is marked as private, then the static _instance is the only instance that can be created from this class
        private StockBroadcaster(IHubConnectionContext<dynamic> clients)
        {
            Clients = clients;

            //Here Fill the Stocks Dictionary that will be broadcasted 
            _stocks.Clear();//Clear the dictionary
            var stocks = new List<Stock>();
            stocks = Stock.GetStocks();
            stocks.AddRange(Stock.GetMoneyStocks());
            stocks.AddRange(Stock.GetPreciousMetals());
            stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));

            _timer = new Timer(UpdateStocks, null, _refreshRate, _refreshRate); //initialize the timer

        }

        /// <summary>
        /// The singelton instance exposed as a public porperty
        /// </summary>
        public static StockBroadcaster Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        /// <summary>
        /// Will return all the stocks
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Stock> GetAllStocks()
        {
            return _stocks.Values; // here we return the dictionary.
        }

        /// <summary>
        /// Will update all the stocks in the dictionary
        /// </summary>
        /// <param name="state"></param>
        private void UpdateStocks(object state)
        {
            lock (_updateStockPricesLock)
            {
                if (!_updatingStockPrices)
                {
                    List<Stock> stocks = new List<Stock>();

                    _updatingStockPrices = true;

                    foreach (var stock in _stocks.Values)
                    {
                        if (TryUpdateStockPrice(stock))
                        {
                            stocks.Add(stock);

                        }
                    }

                    BroadcastAllStocksPrices(stocks);//Broadcast the updated stocks to the clients

                    _updatingStockPrices = false;
                }
            }
        }

        /// <summary>
        /// Will update an individual stock
        /// </summary>
        /// <param name="stock">The stock to be updated</param>
        /// <returns></returns>
        private bool TryUpdateStockPrice(Stock stock)
        {
            // Randomly choose whether to update this stock or not
            var randomUpdate = _willUpdate.NextDouble();
            if (randomUpdate > 0.3)//To increase the possibility of updating this stock, replace 0.3 with a bigger number, but less than 1
            {
                return false;
            }

            // Update the stock price
            var random = new Random((int)Math.Floor(stock.LastValue));
            double percentChange = random.NextDouble() * 0.1;
            bool isChangePostivie = random.NextDouble() > .51;//To check if we will subtract or add the change value, the random.NextDouble will return a value between 0.0 and 1.0. Thus it is almost a fifty/fifty chance of adding or subtracting
            double changeValue = Math.Round(stock.LastValue * percentChange, 2);
            changeValue = isChangePostivie ? changeValue : -changeValue;

            double newValue = stock.LastValue + changeValue;
            stock.Change = newValue - stock.LastValue;
            stock.LastValue = newValue;
            return true;
        }

        /// <summary>
        /// Will broadcast a single stock to the clients
        /// </summary>
        /// <param name="stock">The stock to broadcast</param>
        private void BroadcastStockPrice(Stock stock)
        {
            Clients.All.updateStockPrice(stock);//This updateStockPrice method will be called at the client side, in the clients' Browsers, with the stock parameter inside it.
        }

        /// <summary>
        /// Will broadcast all updated stocks to the clients
        /// </summary>
        /// <param name="stocks">The stocks to broadcast</param>
        private void BroadcastAllStocksPrices(List<Stock> stocks)
        {
            Clients.All.updateAllStocksPrices(stocks);//This updateStockPrice method will be called at the client side, in the clients' Browsers, with the stock parameter inside it.
        }

    }
}

First we created a TimeSpan that will be the refresh rate for our server broadcasts, initialize this to 2000 MS.  Then we will create a Random variable and name it _willUpdate. This will decide whether to update a given stock or not.

Next we will declare a timer that will be used to update and broadcast the stock data every 2 seconds. Now since multiple threads will be running on the same instance of the class, we will be using locks to ensure thread safety. Why use only one instance? Because this is a Web application, for all the users to be able to see the same data, a single instance must exist, or we will have to read data from a database or other datasources (Which is the real life scenario). The _updateStockPricesLock will be the object used for the locking mechanism. 

Now we will create an IHubConnectionContext<dynamic> object and name it Clients. This will be used to encapsulate all information about a SignalR connection for a Hub.

Below that we will initialize the static Singleton instance that will be providing and updating our data. Since this is the only instance that will be allowed to be created, we will declare the Constructer as private. Lazy initialization is used to ensure that the instance creation is thread safe. Also declare a dictionary to store our stocks. We also used a special type of Dictionary called ConcurrentDictionary, this is used to create a "thread-safe collection of key/value pairs that can be accessed by multiple threads concurrently."

C#
//Initialize the static Singleton instance. Lazy initialization is used to ensure that the instance creation is threadsafe.
private readonly static Lazy<StockBroadcaster> _instance = new Lazy<StockBroadcaster>(() => new StockBroadcaster(GlobalHost.ConnectionManager.GetHubContext<StockBroadcasterHub>().Clients));

//Initialize the dictionary that will be be used to hold the stocks that will be returned to the client. We used
        //the ConcurrentDictionary for thread safety.If you used a Dictionary object make sure to explicitly lock the dictionary before making changes to it.
        private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();

        //Since the constructer is marked as private, then the static _instance is the only instance that can be created from this class
        private StockBroadcaster(IHubConnectionContext<dynamic> clients)
        {
            Clients = clients;

            //Here Fill the Stocks Dictionary that will be broadcasted 
            _stocks.Clear();//Clear the dictionary
            var stocks = new List<Stock>();
            stocks = Stock.GetStocks();
            stocks.AddRange(Stock.GetMoneyStocks());
            stocks.AddRange(Stock.GetPreciousMetals());
            stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));

            _timer = new Timer(UpdateStocks, null, _refreshRate, _refreshRate); //initialize the timer

        }

 

The method UpdateStocks will update all the stocks in the dictionary, put the updated stocks in a generic list, and then broadcast this list to the clients. 

C#
/// <summary>
      /// Will update all the stocks in the dictionary
      /// </summary>
      /// <param name="state"></param>
      private void UpdateStocks(object state)
      {
          lock (_updateStockPricesLock)
          {
              if (!_updatingStockPrices)
              {
                  List<Stock> stocks = new List<Stock>();

                  _updatingStockPrices = true;

                  foreach (var stock in _stocks.Values)
                  {
                      if (TryUpdateStockPrice(stock))
                      {
                          stocks.Add(stock);

                      }
                  }

                  BroadcastAllStocksPrices(stocks);//Broadcast the updated stocks to the clients

                  _updatingStockPrices = false;
              }
          }
      }

The next method is the TryUpdateStockPrice. Based on the boolean called randomUpdate, it will decide whether to update the stock or not. If the stock is to be updated, its last price will be modified based on the variable called changeValue. We will also update the Stock.Change property.

C#
/// <summary>
        /// Will update an individual stock
        /// </summary>
        /// <param name="stock">The stock to be updated</param>
        /// <returns></returns>
        private bool TryUpdateStockPrice(Stock stock)
        {
            // Randomly choose whether to update this stock or not
            var randomUpdate = _willUpdate.NextDouble();
            if (randomUpdate > 0.3)//To increase the possibility of updating this stock, replace 0.3 with a bigger number, but less than 1
            {
                return false;
            }

            // Update the stock price
            var random = new Random((int)Math.Floor(stock.LastValue));
            double percentChange = random.NextDouble() * 0.1;
            bool isChangePostivie = random.NextDouble() > .51;//To check if we will subtract or add the change value, the random.NextDouble will return a value between 0.0 and 1.0. Thus it is almost a fifty/fifty chance of adding or subtracting
            double changeValue = Math.Round(stock.LastValue * percentChange, 2);
            changeValue = isChangePostivie ? changeValue : -changeValue;

            double newValue = stock.LastValue + changeValue;
            stock.Change = newValue - stock.LastValue;
            stock.LastValue = newValue;
            return true;
        }

Finally the broadcast method, this method will call a client side function, that is a JavaScript method residing on the client's machine. It will also pass the stocks parameter to this method, these stocks will then be parsed at the client side to update the necessary cells. Note that I also kept a method to update only one stock if you wish to use it, it is called BroadcastStockPrice.

C#
/// <summary>
  /// Will broadcast all updated stocks to the clients
  /// </summary>
  /// <param name="stocks">The stocks to broadcast</param>
  private void BroadcastAllStocksPrices(List<Stock> stocks)
  {
      Clients.All.updateAllStocksPrices(stocks);//This updateStockPrice method will be called at the client side, in the clients' Browsers, with the stocks parameter inside it.
  }

Before we begin writing our client code, we need to register the SignalR route in our web application, add a new class and call it "Startup.cs", or you can right click your project and select Add->OWIN Startup Class. Replace the class code with the below:

C#
using Microsoft.Owin;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

[assembly: OwinStartup(typeof(WebParallelProgramming.Startup))]
namespace WebParallelProgramming
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }

    }
}

Open your default.aspx page and add the following JavaScript links, be sure that they match your jQuery version and paths. The SignalR proxies script file, which is specified in the "/signalr/hubs" URL, is dynamically generated and defines proxy methods for the methods on our Hub class, which in our case is a single method called StockBroadcasterHub.GetAllStocks().

HTML
<!--Script references. -->
  <!--Reference the jQuery library. -->
  <script src="/Scripts/jquery-1.7.1.min.js"></script>
  <!--Reference the SignalR library. -->
  <script src="/Scripts/jquery.signalR-2.1.2.js"></script>
  <!--Reference the autogenerated SignalR hub script. -->
  <script src="/signalr/hubs"></script>
  <!--Reference the StockTicker script. -->
  <script src="StockManager.js"></script>

Now we will create the client side engine that will be receiving the broadcasts from the server and updating our grid views with the new data. Right click your project and add a new JavaScript file, name it "StockManager.js". Inside it, copy and paste the below code:

JavaScript
$(function () {

    var stockBroadcaster = $.connection.stockBroadcaster; // the generated client-side hub proxy, use the same name as that used in the StockBroadCasterHub HubName attribute

    function init() {
       
    }

    // Add a client-side hub method that the server will call to deliver the client updates on a given stock
    stockBroadcaster.client.updateStockPrice = function (stock) {
        //If you want to update a single stock, write your code here
    }

    // Add a client-side hub method that the server will call to deliver the client all the updated stocks
    stockBroadcaster.client.updateAllStocksPrices = function (stocks) {
        //clear all the tds with updated background
        $("td.updatedStockP").removeClass("updatedStockP");
        $("td.updatedStockN").removeClass("updatedStockN");

        $.each(stocks, function (index, stock) {//Loop through the stocks recieved from the server
            var stockSymbol = stock.Symbol; //Get the stock Symbol, we will assume it is unique, you can use other properties to get the uninque value, like an auto generated ID
            $('td').filter(function () {
                return $(this).text() === stockSymbol;//Use this to get only the td with a text equal to the stock symbol, then update its value and background color accordingly.
            }).next().html(stock.LastValue.toFixed(2)).addClass(stock.Change > 0 ? "updatedStockP" : "updatedStockN").next().html(stock.Change.toFixed(2)).addClass(stock.Change > 0 ? "updatedStockP" : "updatedStockN");

        });

    }

    // Start the connection
    $.connection.hub.start().done(init);

});

Notice the stockBroadcaster hub name, which should match the name that is used in the StockBroadCasterHub HubName attribute. First we will use the connection object to get the hub proxy, then we will open the connection using the command:

JavaScript
$.connection.hub.start().done(init);

 After that we will define our updateAllStockPrices method, remember this method? The one that we called in our StockBroadcaster.BroadcastAllStocksPrices() server method.

Inside this JavaScript function, we will first clear all the table cells that are marked as updated stocks cells (we will create the CSS classes shortly). Then we will loop through the stocks array that we received from the server, this is the stock parameter that we passed in our StockBroadcaster.BroadcastAllStocksPrices() method:

C#
Clients.All.updateAllStocksPrices(stocks);//This updateStockPrice method will be called at the client side, in the clients' Browsers, with the stocks parameter inside it.

This is an array of JSON objects, this means we can call the properties of the Stock object on the items of this array. The first challenge is to find for each stock its corresponding row in the three grid views, for this we will assume that the stock symbol is unique, and we will use it to capture the row specific for that stock. Then we will use the Jquery .next() method to get the next sibling of this cell, which is the LastValue column. After that we will use the .html() method to write the new value. The .toFixed(2) is used to round the number to the nearest 2 decimals. After this we use the .addClass() method to style the cell, either with green if the change is positive, or salmon red if the change is negative. Then we will do the same thing for the change column.

Finally we will add the CSS classes to our .css file, copy and paste the below to the file Site.css found inside the Contents folder

CSS
.updatedStockP {
background-color:lightgreen;
}

.updatedStockN {
background-color:lightsalmon;
}

Also add the following to the Site.Master page to link it to the CSS file:

HTML
<link href="Content/Site.css" rel="stylesheet" type="text/css" />

Run your project and check the data updating, if the colors were not changing, press ctrl + F5 to refresh the page and load up the new CSS file. The stocks with positive change will appear in green, those with negative change will appear in red. It will take some time for the data to begin updating because of the Thread.Sleep() operations we are doing in the DataLayer class. You can download the source code of the application from the link at the top of the article.

Thanks for following up :).

Image 7

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionLove this write-up! Pin
andegre17-Dec-14 10:27
andegre17-Dec-14 10:27 
AnswerRe: Love this write-up! Pin
Hassan Mokdad17-Dec-14 22:26
Hassan Mokdad17-Dec-14 22:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.