© Vincent Maverick S. Durano 2019
Vincent Maverick S. DuranoUnderstanding Game Application Development https://doi.org/10.1007/978-1-4842-4264-3_5

5. Building a Simple Real-Time Leaderboard Web App with ASP.NET SignalR and MVC

Vincent Maverick S. Durano1 
(1)
Minnetonka, MN, USA
 

Before we start implementing real-time functionality, let’s get to know what ASP.NET SignalR and MVC are all about. Although we are not going to fully utilize the features that the MVC framework offers, it is still nice to have a basic understanding of how the MVC framework works.

What ASP.NET MVC Is

ASP.NET MVC is part of the ASP.NET framework. The following figure will give you a high-level look at where ASP.NET MVC resides within the ASP.NET framework.
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig1_HTML.jpg
Figure 5-1

The ASP.NET technologies

In the preceding figure, you see that ASP.NET MVC sits on top of ASP.NET. ASP.NET MVC is a UI framework that enables a clean separation of concerns and gives you full control over your markup.

To make it clearer, here’s how I view the high-level process of MVC:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig2_HTML.jpg
Figure 5-2

Request and response flow

Unlike in ASP.NET WebForms, in which requests go directly to a page file (.ASPX), in MVC, when a user requests a page, it will first talk to the Controller, process data when necessary, and return a Model to the View for the user to see.

The Model

Model is just a class that implements the logic for the application domain data. Often, model objects retrieve and store model states in the database.

The Controller

Just like models, Controller is also a class that handles the user interaction. It will work with the model and ultimately select a view to render in the browser.

The View

As the name suggests, a View is the component that displays the application’s UI; typically, this UI is created from the model data.

To put them up together, the M is for Model, which is typically where the business objects, business layer, and data access layer will live. Note that in typical layered architecture, your business layer and data access layer should be in separate projects. The V is for View, which is what the user sees. This could simply mean that any UI- and client-side-related developments will live in the View, including HTML, CSS, and JavaScript. The C is for the Controller, which orchestrates the flow of logic. For example, if a user clicks a button that points to a specific URL, that request is mapped to the controller action method that is responsible for handling any logic required to service the request and return a response. This will typically be a new view, or an update to the existing view.

To get started with ASP.NET MVC 5, I’d recommend you read my series of article here: http://vmsdurano.com/building-web-application-using-entity-framework-and-mvc-5-part-1/

What ASP.NET SignalR Is

ASP.NET SignalR is a new library for ASP.NET developers that makes developing real-time web functionality easy. SignalR allows bidirectional communication between server and client. Servers can now push content to connected clients instantly as it becomes available. SignalR supports WebSockets and falls back to other compatible techniques for older browsers.

SignalR can be used wherever a user is required to refresh a page in order to see up-to-date data. It allows the server to logically “push” data to the client. This is typically required for web-based dashboards and monitoring tools, where information needs to be kept up to date at all times without the user having to refresh the page. SignalR is a powerful, high-level library that abstracts a lot of the complicated underlying technologies in order to provide an easy way to transmit data between the client and the server. SignalR manages the connections automatically and allows data to be sent using either broadcasts or unicasts.

In SignalR, there are two distinct models for implementing client-server communications:
  • Persistent Connections are the base class with an API for exposing a SignalR service over HTTP. They are useful for when developers need direct access to the low-level communication technology. Persistent connections use a model similar to that of WCF.

  • Hubs are built on top of persistent connections and abstract most of the underlying complexity in order to allow developers to call methods on both the client and the server without worrying about the implementation details. One great benefit of using Hubs is that you get model binding and serialization straight out of the box.

Transport Protocols Selection

One of the great features about SignalR is that when a client doesn’t support WebSockets, it automatically falls back to using older methods of communication, as shown in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig3_HTML.jpg
Figure 5-3

SignalR communication flow

SignalR is quite flexible in terms of supporting a variety of transport protocols. It uses the WebSocket transport when available, but falls back to older transports when necessary. WebSocket requires at least Windows Server 2012 or Windows 8, and .NET Framework 4.5 for server and at least IE 10 for the client. If these requirements are not met, SignalR will attempt to use other transports to make its connections.

The following are the available transport protocols:
  • WebSockets

  • Long Polling

  • Server Sent Events

  • Forever Frame

The default transport selection process goes like this:
  1. 1.

    If the client/server doesn’t support WebSockets, then it falls back to use Server Sent Events.

     
  2. 2.

    If Server Sent Events isn’t available, then it falls back to Forever Frame; if Forever Frame if isn’t available, it falls back to Long Polling.

     

Transport Protocol Overview

WebSocket is a full duplex protocol that uses http handshaking internally and allows the stream of messages to flow on top of TCP. It supports Google Chrome (> 16), Firefox (> 11), IE (> 10), and Win IIS (>8.0). In other words, if both client and server support WebSockets, then this creates a persistent connection between them, which can be used by either client or server to send the data anytime. As such, this way is the most efficient, takes the least memory, and shows the lowest latency. This is the most preferred protocol for a SignalR application.
  • Simplex Communication: It just spreads in one way when one point just broadcasts while another point just can listen without sending a message, such as television and radio.

  • Half Duplex: One point sends a message and at that moment another point cannot send a message and must wait until the first point finishes its transmission; then it can send its message. It is just one communication at a time, such as old wireless devices like walkie-talkies and HTTP protocol.

  • Full Duplex: Both points can send and receive messages simultaneously; there is no need to wait until the other point finishes its transmission. This is similar to telephones and WebSocket protocol.

Server Sent Events (also known as Event Source): This is another technique introduced with HTML5 that allows the server to push the updates to the client whenever new data is available. This technology is used when WebSocket is not supported. It is supported by most browsers except IE.

Forever Frame : This is part of the Comet model and uses a hidden iframe in the browser to receive the data in an incremental manner from the server. The server starts sending the data in a series of chunks even without even knowing the complete length of the content. It is executed on the client when the data is received.

AJAX Long Polling : This is the least preferred way in SignalR to set up a communication between client and server. Also, it is the most expensive! It is a part of the Comet model and as the name suggests, it keeps polling the server to check for updates. The request that is sent to the server is AJAX based, to minimize the resource usage and provide a better user experience. But it’s still expensive because it keeps polling the server whether there are any updates or not.

For more information, see www.asp.net/signalr

Create a New Web Application

Now that you have an idea of how SignalR transmits and persists data across client and the server, it’s time for us to see that in action.

Let’s add a new ASP.NET web application project. Right-click the Solution and then select AddNew Project. On the left pane under Visual C#Web, select ASP.NET Web Application (.NET Framework) and name it “MemoryGame.Web” just like in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig4_HTML.jpg
Figure 5-4

Create a new ASP.NET web application project

Click OK and then select Empty. Tick the MVC option under the “Add folders and core references for:” just like in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig5_HTML.jpg
Figure 5-5

Create an empty ASP.NET MVC project

Click OK to let Visual Studio generate the project for you.

Integrating ASP.NET SignalR

Install Microsoft.Asp.Net.SignalR in your project via NuGet as shown the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig6_HTML.jpg
Figure 5-6

Install Microsoft.AspNet.SignalR NuGet package

The latest stable version as of the time of writing is v2.3.0. Once installed, you should be able to see them added under the references folder:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig7_HTML.jpg
Figure 5-7

ASP.NET SignalR references

The Microsoft.AspNet.SignalR.Core is responsible for pulling in the server components and JavaScript client required to use SignalR in our application. Microsoft.AspNet.SignalR.SystemWeb contains components for using SignalR in applications hosted on System.Web.

Install Microsoft.AspNet.Web.Optimization and then add the following code under Viewweb.config:
<addnamespace="System.Web.Optimization"/>

Adding a Middleware for SignalR

We need to create a middleware for SignalR so we can configure it for use by creating an IApplicationBuilder extension method. Create a new class at the root of the MemoryGame.Web project, name it “Startup.cs”, and then replace the generated code with the following:
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MemoryGame.Web.Startup))]
namespace MemoryGame.Web
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

The preceding configuration will add the SignalR services to the pipeline and enable us to use ASP.NET SignalR real-time capabilities in our application.

Adding a Hub

Next is to add an ASP.NET SignalR Hub. Add a new class at the root of the project and name it “LeaderboardHub.cs”. Replace the default generated code with the following code:
using Microsoft.AspNet.SignalR;
namespace MemoryGame.Web
{
    public class LeaderboardHub : Hub
    {
        public static void Broadcast()
        {
            IHubContext context = GlobalHost
                                 .ConnectionManager
                                 .GetHubContext<LeaderboardHub>();
            context.Clients.All.displayLeaderBoard();
        }
    }
}

The LeaderboardHub inherits the Hub class and contains a static class called Broadcast.

The Hub is the centerpiece of the SignalR. Similar to the concept of Controller in ASP.NET MVC, a Hub is responsible for receiving input and generating the output to the client.

To make it clearer, the following class:
public class LeaderboardHub : Hub
will generate the following JavaScript client proxy:
var hubProxy = $.connection.leaderboardHub;

By default, JavaScript clients refer to Hubs by using a camel-cased version of the class name. SignalR automatically makes this change so that JavaScript code can conform to JavaScript conventions. The preceding example code would be referred to as leaderBoardHub in JavaScript code. We’ll take a look at how we are going to invoke the Hub from our JavaScript code later in this chapter.

The Broadcast() method creates an instance of the IHubContext interface. IHubContext provides access to information about an IHub and basically exposes two main properties, which are the Clients and Groups. In this example, a connected client can call the Broadcast server method and displayLeaderBoard client proxy method, and when it does, the data received is broadcast to all connected clients, as shown in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig8_HTML.jpg
Figure 5-8

SignalR client-to-server invocation and vice versa

SignalR handles connection management automatically and lets you broadcast messages to all connected clients simultaneously, like a chat room. You can also send messages to specific clients. The connection between the client and server is persistent, unlike a classic HTTP connection, which is re-established for each communication.

SignalR provides a simple API for creating server-to-client remote procedure calls (RPC) that call JavaScript functions in client browsers (and other client platforms) from server-side .NET code. SignalR also includes API for connection management (for instance, connect and disconnect events) and grouping connections.

Adding an API Endpoint

At this point, the MemoryGame.API Web API server doesn’t have access to the Hub. Since the MemoryGame.API application was created separately and will be hosted in a different server with different URL/ports, then we need to create an API for exposing a public endpoint to that server to communicate with SignalR.

Let’s go ahead and add a new Web API controller class. Right-click the Controllers folder and then select AddWeb API Controller class (v2.1) as shown in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig9_HTML.jpg
Figure 5-9

Adding a new Web API controller class

On the next screen, name the class “LeaderBoardAppController”, just like in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig10_HTML.jpg
Figure 5-10

Setting a controller name

Click OK and then replace the default generated code with the following code:
using System.Web.Http;
namespace MemoryGame.Web.Controllers
{
    [RoutePrefix("api/ranking")]
    public class LeaderBoardAppController : ApiController
    {
        [HttpPost,Route("")]
        public void Broadcast()
        {
            LeaderboardHub.Broadcast();
        }
    }
}

The LeaderBoardAppController class derives the ApiController class, which enables it to become a Web API controller rather an MVC controller. This class uses the RoutePrefix attribute to define a common route prefix that is set to “api/ranking”.

The Broadcast() method class calls the static Broadcast method of the LeaderboardHub class that we created earlier. Notice that the method is decorated with the [HttpPost] and [Route] attributes. This signifies that this method can be invoked only on a POST Http request and routes to “api/ranking”. If you remember, setting the Route attribute to empty ( [Route(“”)] ) automatically maps to the base route defined at the class level.

Note

You can also define a client proxy method outside the Hub via IHubContext. For example, in your Web API controller action, you can do something like in the following code:

[HttpPost, Route("")]
public void Broadcast()
{
    IHubContext context = GlobalHost
                          .ConnectionManager
                          .GetHubContext<LeaderboardHub>();
    context.Clients.All.displayLeaderBoard();
}

Note

If you want to use Hubs API for SignalR version 2 in .NET clients, such as Windows Store (WinRT), WPF, Silverlight, and console applications, then see https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-net-client

Configure Web API Routing

The next thing that we are going to do is to configure Web API routing within an ASP.NET MVC application.

Add a new class under the App_Start folder of the MemoryGame.Web project. Name the class “WebApiConfig.cs” and copy the following code:
using System.Web.Http;
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API routes
        config.MapHttpAttributeRoutes();
    }
}

The preceding code enables attribute-based routing for Web API.

The final step is to register the WebApiConfig class in Global.asax. In the Application_Start method of the file Global.asax.cs file, add a call to GlobalConfiguration.Configure() method; be careful to place it before the call to RouteConfig.RegisterRoutes(RouteTable.Routes):
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace MemoryGame.Web
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

Again, take note of the registration sequence in your code or the routing won’t work properly and you will end up getting an unexpected behavior.

Enabling API Endpoint-to-Endpoint Communication

Now that we’re done creating an API endpoint for invoking SignalR communication, we need to modify the UpdateScore() method of the GameController class in the MemoryGame.API application. Head over to MemoryGame.API project and drill down to APIGameController.cs file, as shown in the following figure:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig11_HTML.jpg
Figure 5-11

Navigating to the GameContoller class

Double-click the GameController.cs file to open it and then replace the UpdateScore() method with this code:
[HttpPost, Route("score")]
public void UpdateScore(Rank user)
{
    _gm.UpdateCurrentBest(user);
    HttpClient client = new HttpClient();
    var uri = new Uri($"http://localhost:57865/api/ranking");
    client.PostAsync(uri, null).Wait();
}

What we did there is to add the lines of code for invoking the API endpoint that we’ve created in the previous section using the HttpClient object.

The preceding code is responsible for updating data in the database and automatically broadcasts a trigger to SignalR to display real-time live updates in the page.

Note

You may need to change the value of Uri with the actual URL at which your application is running. For this example, localhost:57865 is the generated port number generated by Visual Studio 2017 when running the application in debug mode.

Adding an MVC Controller

Let’s add a new MVC 5 controller file. To do that, right-click the Controllers folder and then select AddControllers.

../images/475615_1_En_5_Chapter/475615_1_En_5_Figa_HTML.jpg

Select MVC 5 Controller – Empty and then click Add.

On the next screen, set the name as “HomeController”. Click Add and it should generate the following code:
using System.Web.Mvc;
namespace MemoryGame.Web.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

The preceding code is just an action method that throws an Index View. For this particular example, we don’t really need to build the UI in MVC with Razor, as we will be using only JavaScript and plain HTML to generate the UI. The MVC here is used only to launch a View, and that’s it.

Adding a View

Add a new View in the “Views/Home” folder and name it “Index”. Replace the generated code with the following code:
<div id="body">
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                 <h1>Leader Board</h1>
            </hgroup>
        </div>
    </section>
    <section class="content-wrapper main-content clear-fix">
        <h1>
            <span>
                Top Challengers
                <imgsrc="~/Images/goals_256.png"style="width:40px; height:60px;"/>
            </span>
        </h1>
        <table id="tblRank" class="table table-striped table-condensed table-hover"></table>
    </section>
</div>
@section scripts{
    @Scripts.Render("~/Scripts/jquery.signalR-2.3.0.min.js")
    @Scripts.Render("~/signalr/hubs")
    <script type="text/javascript">
        $(function () {
            var hubProxy = $.connection.leaderboardHub;
            hubProxy.client.displayLeaderBoard = function () {
                LoadResult();
            };
            $.connection.hub.start() ;
            LoadResult();
        });
        function LoadResult() {
            var $tbl = $("#tblRank");
            $.ajax({
                url: 'http://192.168.0.14:45455/api/game/players',
                type: 'GET',
                datatype: 'json',
                success: function (data) {
                    if (data.length > 0) {
                        $tbl.empty();
                        $tbl.append('<thead><tr><th>Rank</th>'
                            + '<th></th>'
                            + '<th></th>'
                            + '<th>Best</th>'
                            + '<th>Achieved</th>'
                            + '</tr></thead > ');
                        var rows = [];
                        for (var i = 0; i < data.length; i++) {
                            rows.push('<tbody><tr><td>'
                                + (i + 1).toString() + '</td><td>'
                                + data[i].FirstName + '</td><td>'
                                + data[i].LastName + '</td><td>'
                                + data[i].Best + '</td><td>'
                                + data[i].DateAchieved
                                + '</td></tr></tbody>');
                        }
                        $tbl.append(rows.join(“));
                    }
                }
            });
        }
    </script>
}
Take note of the sequence for adding the client script references:
  • jQuery

  • jQuery.signalR

  • /signalr/hub

jQuery should be added first, then the SignalR Core JavaScript and finally the SignalR Hub script.

The reference to the SignalR-generated proxy is dynamically generated JavaScript code, not a physical file. SignalR creates the JavaScript code for the proxy on the fly and serves it to the client in response to the “/signalr/hubs” URL.

Again, take note of the preceding script’s order sequence reference; otherwise, SignalR client will not work.

For more information, see https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

Let’s take a look at what we did there by breaking the code into sections.

The LoadResult() function uses a jQuery AJAX to invoke a Web API call through AJAX GET request. If there’s any data from the response, it will generate an HTML by looping through the rows. The LoadResult() function will be invoked when the page is loaded or when the displayLeaderboard() client proxy method from the Hub is invoked. By subscribing to the Hub, ASP.NET SignalR will do the entire complex plumbing for us to do real-time updates without any extra work needed in our side. Thanks, SignalR!

Output

Here’s the final output when you deploy and run the project:
../images/475615_1_En_5_Chapter/475615_1_En_5_Fig12_HTML.jpg
Figure 5-12

Real-time leaderboard page

The preceding page uses SignalR Hub client-server communication to automatically update the data without refreshing the page once a user from the mobile app syncs their information and scores.