24 min read

In this article, by Einar Ingebrigtsen, author of the book, SignalR Blueprints, we will focus on a different programming model for client development: Model-View-ViewModel (MVVM). It will reiterate what you have already learned about SignalR, but you will also start to see a recurring theme in how you should architect decoupled software that adheres to the SOLID principles. It will also show the benefit of thinking in single page application terms (often referred to as Single Page Application (SPA)), and how SignalR really fits well with this idea.

(For more resources related to this topic, see here.)

The goal – an imagined dashboard

A counterpart to any application is often a part of monitoring its health. Is it running? and are there any failures?. Getting this information in real time when the failure occurs is important and also getting some statistics from it is interesting. From a SignalR perspective, we will still use the hub abstraction to do pretty much what we have been doing, but the goal is to give ideas of how and what we can use SignalR for. Another goal is to dive into the architectural patterns, making it ready for larger applications. MVVM allows better separation and is very applicable for client development in general.

A question that you might ask yourself is why KnockoutJS instead of something like AngularJS? It boils down to the personal preference to a certain degree. AngularJS is described as a MVW where W stands for Whatever. I find AngularJS less focused on the same things I focus on and I also find it very verbose to get it up and running. I’m not in any way an expert in AngularJS, but I have used it on a project and I found myself writing a lot to make it work the way I wanted it to in terms of MVVM. However, I don’t think it’s fair to compare the two. KnockoutJS is very focused in what it’s trying to solve, which is just a little piece of the puzzle, while AngularJS is a full client end-to-end framework.

On this note, let’s just jump straight to it.

Decoupling it all

MVVM is a pattern for client development that became very popular in the XAML stack, enabled by Microsoft based on Martin Fowlers presentation model. Its principle is that you have a ViewModel that holds the state and exposes behavior that can be utilized from a view. The view observes any changes of the state the ViewModel exposes, making the ViewModel totally unaware that there is a view. The ViewModel is decoupled and can be put in isolation and is perfect for automated testing. As part of the state that the ViewModel typically holds is the model part, which is something it usually gets from the server, and a SignalR hub is the perfect transport to get this. It boils down to recognizing the different concerns that make up the frontend and separating it all. This gives us the following diagram:
SignalR Blueprints

Back to basics

This time we will go back in time, going down what might be considered a more purist path; use the browser elements (HTML, JavaScript, and CSS) and don’t rely on any server-side rendering.

Clients today are powerful and very capable and offloading the composition of what the user sees onto the client frees up server resources. You can also rely on the infrastructure of the Web for caching with static HTML files not rendered by the server. In fact, you could actually put these resources on a content delivery network, making the files available as close as possible to the end user. This would result in better load times for the user.

You might have other reasons to perform server-side rendering and not just plain HTML. Leveraging existing infrastructure or third-party party tools could be those reasons. It boils down to what’s right for you. But this particular sample will focus on things that the client can do. Anyways, let’s get started.

  1. Open Visual Studio and create a new project by navigating to FILE | New | Project. The following dialog box will show up:
    SignalR Blueprints
  2. From the left-hand side menu, select Web and then ASP.NET Web Application.
  3. Enter Chapter4 in the Name textbox and select your location.
  4. Select the Empty template from the template selector and make sure you deselect the Host in the cloud option. Then, click on OK, as shown in the following screenshot:
    SignalR Blueprints

Setting up the packages

First, we want Twitter bootstrap. To get this, follow these steps:

  1. Add a NuGet package reference.
  2. Right-click on References in Solution Explorer and select Manage NuGet Packages and type Bootstrap in the search dialog box.
  3. Select it and then click on Install.

We want a slightly different look, so we’ll download one of the many bootstrap themes out here. Add a NuGet package reference called metro-bootstrap.

As jQuery is still a part of this, let’s add a NuGet package reference to it as well.

For the MVVM part, we will use something called KnockoutJS; add it through NuGet as well. Add a NuGet package reference, as in the previous steps, but this time, type SignalR in the search dialog box. Find the package called Microsoft ASP.NET SignalR.

Making any SignalR hubs available for the client

Add a file called Startup.cs file to the root of the project. Add a Configuration method that will expose any SignalR hubs, as follows:

public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}

At the top of the Startup.cs file, above the namespace declaration, but right below the using statements, add the following code:

 [assembly: OwinStartupAttribute(typeof(Chapter4.Startup))]

Knocking it out of the park

KnockoutJS is a framework that implements a lot of the principles found in MVVM and makes it easier to apply. We’re going to use the following two features of KnockoutJS, and it’s therefore important to understand what they are and what significance they have:

  • Observables: In order for a view to be able to know when state change in a ViewModel occurs, KnockoutJS has something called an observable for single objects or values and observable array for arrays.
  • BindingHandlers: In the view, the counterparts that are able to recognize the observables and know how to deal with its content are known as BindingHandlers. We create binding expression in the view that instructs the view to get its content from the properties found in the binding context. The default binding context will be the ViewModel, but there are more advanced scenarios where this changes. In fact, there is a BindingHandler that enables you to specify the context at any given time called with.

Our single page

Whether one should strive towards having an SPA is widely discussed on the Web these days. My opinion on the subject, in the interest of the user, is that we should really try to push things in this direction. Having not to post back and cause a full reload of the page and all its resources and getting into the correct state gives the user a better experience. Some of the arguments to perform post-backs every now and then go in the direction of fixing potential memory leaks happening in the browser. Although, the technique is sound and the result is right, it really just camouflages a problem one has in the system. However, as with everything, it really depends on the situation.

At the core of an SPA is a single page (pun intended), which is usually the index.html file sitting at the root of the project. Add the new index.html file and edit it as follows:

  1. Add a new HTML file (index.html) at the root of the project by right- clicking on the Chapter4 project in Solution Explorer. Navigate to Add | New Item | Web from the left-hand side menu, and then select HTML Page and name it index.html. Finally, click on Add.
  2. Let’s put in the things we’ve added dependencies to, starting with the style sheets. In the index.html file, you’ll find the <head> tag; add the following code snippet under the <title></title> tag:
    <link href="Content/bootstrap.min.css" rel="stylesheet" />
    <link href="Content/metro-bootstrap.min.css" 
    rel="stylesheet" />
  3. Next, add the following code snippet right beneath the preceding code:
    <script type="text/javascript" src="Scripts/jquery-
    1.9.0.min.js"></script> <script type="text/javascript" src="Scripts/jquery.signalR-
    2.1.1.js"></script> <script type="text/javascript" src="signalr/hubs"></script> <script type="text/javascript" src="Scripts/knockout-
    3.2.0.js"></script>
  4. Another thing we will need in this is something that helps us visualize things; Google has a free, open source charting library that we will use. We will take a dependency to the JavaScript APIs from Google. To do this, add the following script tag after the others:
    <script type="text/javascript" 
    src="https://www.google.com/jsapi"></script>
  5. Now, we can start filling in the view part. Inside the <body> tag, we start by putting in a header, as shown here:
    <div class="navbar navbar-default navbar-static-top 
    bsnavbar">     <div class="container">         <div class="navbar-header">             <h1>My Dashboard</h1>         </div>     </div> </div>

The server side of things

In this little dashboard thing, we will look at web requests, both successful and failed. We will perform some minor things for us to be able to do this in a very naive way, without having to flesh out a full mechanism to deal with error situations. Let’s start by enabling all requests even static resources, such as HTML files, to run through all HTTP modules. A word of warning: there are performance implications of putting all requests through the managed pipeline, so normally, you wouldn’t necessarily want to do this on a production system, but for this sample, it will be fine to show the concepts. Open Web.config in the project and add the following code snippet within the <configuration> tag:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true" />
</system.webServer>

The hub

In this sample, we will only have one hub, the one that will be responsible for dealing with reporting requests and failed requests. Let’s add a new class called RequestStatisticsHub. Right-click on the project in Solution Explorer, select Class from Add, name it RequestStatisticsHub.cs, and then click on Add. The new class should inherit from the hub. Add the following using statement at the top:

using Microsoft.AspNet.SignalR;

We’re going to keep a track of the count of requests and failed requests per time with a resolution of not more than every 30 seconds in the memory on the server. Obviously, if one wants to scale across multiple servers, this is way too naive and one should choose an out-of-process shared key-value store that goes across servers. However, for our purpose, this will be fine.

Let’s add a using statement at the top, as shown here:

using System.Collections.Generic;

At the top of the class, add the two dictionaries that we will use to hold this information:

static Dictionary<string, int> _requestsLog = new 
Dictionary<string, int>(); static Dictionary<string, int> _failedRequestsLog = new
Dictionary<string, int>();

In our client, we want to access these logs at startup. So let’s add two methods to do so:

public Dictionary<string, int> GetRequests()
{
    return _requestsLog;
}
 
public Dictionary<string, int> GetFailedRequests()
{
    return _failedRequestsLog;
}

Remember the resolution of only keeping track of number of requests per 30 seconds at a time. There is no default mechanism in the .NET Framework to do this so we need to add a few helper methods to deal with rounding of time. Let’s add a class called DateTimeRounding at the root of the project. Mark the class as a public static class and put the following extension methods in the class:

public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
    var delta = (d.Ticks - (dt.Ticks % d.Ticks)) % d.Ticks;
    return new DateTime(dt.Ticks + delta);
}
 
public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta);
}
 
public static DateTime RoundToNearest(this DateTime dt, 
TimeSpan d) {     var delta = dt.Ticks % d.Ticks;     bool roundUp = delta > d.Ticks / 2;       return roundUp ? dt.RoundUp(d) : dt.RoundDown(d); }

Let’s go back to the RequestStatisticsHub class and add some more functionality now so that we can deal with rounding of time:

static void Register(Dictionary<string, int> log, Action<dynamic, 
string, int> hubCallback) {     var now =
DateTime.Now.RoundToNearest(TimeSpan.FromSeconds(30));     var key = now.ToString("HH:mm");       if (log.ContainsKey(key))         log[key] = log[key] + 1;     else         log[key] = 1;       var hub =
GlobalHost.ConnectionManager.GetHubContext<RequestStatisticsHub>()
;     hubCallback(hub.Clients.All, key, log[key]); }   public static void Request() {     Register(_requestsLog, (hub, key, value) =>
hub.requestCountChanged(key, value)); }   public static void FailedRequest() {     Register(_requestsLog, (hub, key, value) =>
hub.failedRequestCountChanged(key, value)); }

This enables us to have a place to call in order to report requests and these get published back to any clients connected to this particular hub.

Note the usage of GlobalHost and its ConnectionManager property. When we want to get a hub instance and when we are not in the hub context of a method being called from a client, we use ConnectionManager to get it. It gives is a proxy for the hub and enables us to call methods on any connected client.

Naively dealing with requests

With all this in place, we will be able to easily and naively deal with what we consider correct and failed requests. Let’s add a Global.asax file by right-clicking on the project in Solution Explorer and select the New item from the Add. Navigate to Web and find Global Application Class, then click on Add. In the new file, we want to replace the BindingHandlers method with the following code snippet:

protected void Application_AuthenticateRequest(object sender, 
EventArgs e) {     var path = HttpContext.Current.Request.Path;     if (path == "/") path = "index.html";       if (path.ToLowerInvariant().IndexOf(".html") < 0) return;       var physicalPath = HttpContext.Current.Request.MapPath(path);     if (File.Exists(physicalPath))     {         RequestStatisticsHub.Request();     }     else     {         RequestStatisticsHub.FailedRequest();     } }

Basically, with this, we are only measuring requests with .html in its path, and if it’s only “/”, we assume it’s “index.html”. Any file that does not exist, accordingly, is considered an error; typically a 404 error and we register it as a failed request.

Bringing it all back to the client

With the server taken care of, we can start consuming all this in the client. We will now be heading down the path of creating a ViewModel and hook everything up.

ViewModel

Let’s start by adding a JavaScript file sitting next to our index.html file at the root level of the project, call it index.js. This file will represent our ViewModel. Also, this scenario will be responsible to set up KnockoutJS, so that the ViewModel is in fact activated and applied to the page. As we only have this one page for this sample, this will be fine.

Let’s start by hooking up the jQuery document that is ready:

$(function() {
});

Inside the function created here, we will enter our viewModel definition, which will start off being an empty one:

var viewModel = function() {
};

KnockoutJS has a function to apply a viewModel to the document, meaning that the document or body will be associated with the viewModel instance given. Right under the definition of viewModel, add the following line:

ko.applyBindings(new viewModel());

Compiling this and running it should at the very least not give you any errors but nothing more than a header saying My Dashboard.

So, we need to lighten this up a bit. Inside the viewModel function definition, add the following code snippet:

var self = this;
this.requests = ko.observableArray();
this.failedRequests = ko.observableArray();

We enter a reference to this as a variant called self. This will help us with scoping issues later on. The arrays we added are now KnockoutJS’s observable arrays that allows the view or any BindingHandler to observe the changes that are coming in.

The ko.observableArray() and ko.observable() arrays both return a new function. So, if you want to access any values in it, you must unwrap it by calling it something that might seem counterintuitive at first. You might consider your variable as just another property. However, for the observableArray(), KnockoutJS adds most of the functions found in the array type in JavaScript and they can be used directly on the function without unwrapping. If you look at a variable that is an observableArray in the console of the browser, you’ll see that it looks as if it actually is just any array. This is not really true though; to get to the values, you will have to unwrap it by adding () after accessing the variable. However, all the functions you’re used to having on an array are here.

Let’s add a function that will know how to handle an entry into the viewModel function. An entry coming in is either an existing one or a new one; the key of the entry is the giveaway to decide:

function handleEntry(log, key, value) {
    var result = log().forEach(function (entry) {
        if (entry[0] == key) {
            entry[1](value);
            return true;
        }
    });
 
    if (result !== true) {
        log.push([key, ko.observable(value)]);
    }
};

Let’s set up the hub and add the following code to the viewModel function:

var hub = $.connection.requestStatisticsHub;
var initializedCount = 0;
 
hub.client.requestCountChanged = function (key, value) {
    if (initializedCount < 2) return;
    handleEntry(self.requests, key, value);
}
 
hub.client.failedRequestCountChanged = function (key, value) {
    if (initializedCount < 2) return;
    handleEntry(self.failedRequests, key, value);
}

You might notice the initalizedCount variable. Its purpose is not to deal with requests until completely initialized, which comes next. Add the following code snippet to the viewModel function:

$.connection.hub.start().done(function () {
    hub.server.getRequests().done(function (requests) {
        for (var property in requests) {
            handleEntry(self.requests, property, 
requests[property]);         }           initializedCount++;     });     hub.server.getFailedRequests().done(function (requests) {         for (var property in requests) {             handleEntry(self.failedRequests, property,
requests[property]);         }           initializedCount++;     }); });

We should now have enough logic in our viewModel function to actually be able to get any requests already sitting there and also respond to new ones coming.

BindingHandler

The key element of KnockoutJS is its BindingHandler mechanism. In KnockoutJS, everything starts with a data-bind=”” attribute on an element in the HTML view. Inside the attribute, one puts binding expressions and the BindingHandlers are a key to this. Every expression starts with the name of the handler. For instance, if you have an <input> tag and you want to get the value from the input into a property on the ViewModel, you would use the BindingHandler value. There are a few BindingHandlers out of the box to deal with the common scenarios (text, value for each, and more). All of the BindingHandlers are very well documented on the KnockoutJS site. For this sample, we will actually create our own BindingHandler. KnockoutJS is highly extensible and allows you to do just this amongst other extensibility points.

Let’s add a JavaScript file called googleCharts.js at the root of the project. Inside it, add the following code:

google.load('visualization', '1.0', { 'packages': ['corechart'] 
});

This will tell the Google API to enable the charting package.

The next thing we want to do is to define the BindingHandler. Any handler has the option of setting up an init function and an update function. The init function should only occur once, when it’s first initialized. Actually, it’s when the binding context is set. If the parent binding context of the element changes, it will be called again. The update function will be called whenever there is a change in an observable or more observables that the binding expression is referring to. For our sample, we will use the init function only and actually respond to changes manually because we have a more involved scenario than what the default mechanism would provide us with. The update function that you can add to a BindingHandler has the exact same signature as the init function; hence, it is called an update.

Let’s add the following code underneath the load call:

ko.bindingHandlers.lineChart = {
    init: function (element, valueAccessor, allValueAccessors, 
viewModel, bindingContext) {     } };

This is the core structure of a BindingHandler. As you can see, we’ve named the BindingHandler as lineChart. This is the name we will use in our view later on.

The signature of init and update are the same. The first parameter represents the element that holds the binding expression, whereas the second valueAccessor parameter holds a function that enables us to access the value, which is a result of the expression. KnockoutJS deals with the expression internally and parses any expression and figures out how to expand any values, and so on. Add the following code into the init function:

optionsInput = valueAccessor();
 
var options = {
    title: optionsInput.title,
    width: optionsInput.width || 300,
    height: optionsInput.height || 300,
    backgroundColor: 'transparent',
    animation: {
        duration: 1000,
        easing: 'out'
    }
};
 
var dataHash = {};
 
var chart = new google.visualization.LineChart(element);
var data = new google.visualization.DataTable();
data.addColumn('string', 'x');
data.addColumn('number', 'y');
 
function addRow(row, rowIndex) {
    var value = row[1];
    if (ko.isObservable(value)) {
        value.subscribe(function (newValue) {
            data.setValue(rowIndex, 1, newValue);
            chart.draw(data, options);
        });
    }
 
    var actualValue = ko.unwrap(value);
    data.addRow([row[0], actualValue]);
 
    dataHash[row[0]] = actualValue;
};
 
optionsInput.data().forEach(addRow);
 
optionsInput.data.subscribe(function (newValue) {
    newValue.forEach(function(row, rowIndex) {
        if( !dataHash.hasOwnProperty(row[0])) {
            addRow(row,rowIndex);
        }
    });
 
    chart.draw(data, options);
});
       
chart.draw(data, options);

As you can see, observables has a function called subscribe(), which is the same for both an observable array and a regular observable. The code adds a subscription to the array itself; if there is any change to the array, we will find the change and add any new row to the chart. In addition, when we create a new row, we subscribe to any change in its value so that we can update the chart. In the ViewModel, the values were converted into observable values to accommodate this.

View

Go back to the index.html file; we need the UI for the two charts we’re going to have. Plus, we need to get both the new BindingHandler loaded and also the ViewModel. Add the following script references after the last script reference already present, as shown here:

<script type="text/javascript" src="googleCharts.js"></script>
<script type="text/javascript" src="index.js"></script>

Inside the <body> tag below the header, we want to add a bootstrap container and a row to hold two metro styled tiles and utilize our new BindingHandler. Also, we want a footer sitting at the bottom, as shown in the following code:

<div class="container">
    <div class="row">
        <div class="col-sm-6 col-md-4">
            <div class="thumbnail tile tile-green-sea tile-large">
                <div data-bind="lineChart: { title: 'Web 
Requests', width: 300, height: 300, data: requests }"></div>             </div>         </div>           <div class="col-sm-6 col-md-4">             <div class="thumbnail tile tile-pomegranate tile-
large">                 <div data-bind="lineChart: { title: 'Failed Web
Requests', width: 300, height: 300, data: failedRequests }"></div>             </div>         </div>     </div>       <hr />     <footer class="bs-footer" role="contentinfo">         <div class="container">             The Dashboard         </div>     </footer> </div>

Note the data: requests and data: failedRequests are a part of the binding expressions. These will be handled and resolved by KnockoutJS internally and pointed to the observable arrays on the ViewModel. The other properties are options that go into the BindingHandler and something it forwards to the Google Charting APIs.

Trying it all out

Running the preceding code (Ctrl + F5) should yield the following result:
SignalR Blueprints

If you open a second browser and go to the same URL, you will see the change in the chart in real time. Waiting approximately for 30 seconds and refreshing the browser should add a second point automatically and also animate the chart accordingly. Typing a URL with a file that does exist should have the same effect on the failed requests chart.

Summary

In this article, we had a brief encounter with MVVM as a pattern with the sole purpose of establishing good practices for your client code. We added this to a single page application setting, sprinkling on top the SignalR to communicate from the server to any connected client.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here