Real-Time Web App Made Simple with MVVM over SignalR - Part 2

Introduction

InPart 1, I introduced dotNetify , a C#/JavaScript library for real-time, interactive web application development that provides MVVM-styled data binding over SignalR with the following major benefits:

allows you to architect your web application with a higher degree of separation of concerns: client-side only holds UI component-specific logic, while the UI-agnostic presentation and business logic remains server-side.

eliminates the need to implement RESTful service layer, making your code leaner and more maintainable.

the bulk of your logic will be on the server-side, where you can take full advantage of the better language/tooling support in C#/.NET, which would make it much easier to apply SOLID.

The dotNetify library is not limited to ASP.NET, however. In this Part 2, I'm picking up the real-time web chart example in Part 1 and further develop it into a simple Process Monitor that provides an interactive UI on the browser, and can be hosted either on a Windows service, or self-hosted on a Linux box with the help of Mono .

In place of ASP.NET, I will use Nancy , an open-source, lightweight web framework that's perfect for these kinds of applications. Nancy will be hosted on top of OWIN.

Architecture

The simple Process Monitor we are going to build will be a list of processes that can be filtered by name. When a process is selected on the list, we will show two charts for CPU and memory usages, and the charts will update every second. We will use the APIs provided by System.Diagnostics to get all the data we need.

The following diagram shows the components. We will divide the solution into 3 projects: the principal class library that references dotNetify , Nancy libraries and their dependencies, the self-host console app shell, and the Windows service shell.

Nancy basically provides the initial browser's HTTP request with the static files (HTMLs, styles, scripts), and once the dotNetify script runs on the client-side, further two-way communication is done through SignalR.

Building the Solution

Here, I will take you through setting up a Visual Studio 2015 solution, with the libraries included using NuGet.

Setting Up Class Library

Let's start by creating a brand new Class Library project and name it DemoLibrary , then use the NuGet Package Manager Console to install the following packages:

Nancy has pre-configured places where it will look for static files, including folders with the names " Content " and " Views ". So that's where we'll put our CSS files (under Content ) and HTML files (under Views ). However, " Scripts " doesn't seem to be part of Nancy's convention, so we will need to tell Nancy to recognize it too by adding a Nancy bootstrapper file:

Every view model derives from BaseVM , which provides the implementation of INotifyPropertyChanged and the associated Get , Set , and Changed methods. There are 3 properties that will be bound to the controls on the View:

SearchString , bound to a text-box for the incremental search. When the user types, this property will receive the characters and raise the changed event on the Processes property.

Processes , bound to a list view that displays the process IDs and names.

SelectedProcessId , bound to the click event on any list item. On change, this will raise the Selected event.

As in the example Part 1, the property Data will be bound to the chart control, but we won't put the timer that updates every second here, since we want to use the same timer on the view model for the memory usage chart. Therefore, this class will just provide a public method to raise the changed event on the property and push the update to the client.

The ProcessId property setter switches the data source to the process of a given ID. This property will be set in response to a process being selected on the process list view.

The MemoryUsageVM class follows similar construct and needs no further narrative. And that brings us to the last view model, which requires a bit of explanation.

Master View Model

So far, we have three view models that present distinct views on the web page. If they don't need to be interacting, then that's all there is to it. But we do want them to be interacting such that user selecting a process in the process view will cause the charts to switch their data source to that process. And so, there needs to be a way for us to establish an observer pattern between the view models.

The default dotNetify 's behavior is to instantiate the view models independent of each other and only on request, but in order to support exactly this requirement, it introduces the concept of a master view model . When the client requests for a view model that's associated with a master view model, the instantiation request will go to the master view model to be fulfilled, which gives it the opportunity to do all sorts of initialization on the instances.

The following master view model implements GetSubVM to provide instances of the above view models that have been initialized to let the ProcessVM instance be observed and the other view models be notified when a process is selected. The timer is placed here that calls the Update method of the chart view models every second so they can push their updates to the client every second.

View

We will now create the HTML view into Dashboard.html and put the file under the Views folder. What's shown here has been trimmed, mostly of its Twitter Bootstrap styling ( .css file placed in Content/css ), to focus just on the things of interest.

The data-vm attribute marks the scope of the named view model as explained inPart 1, but now they are nested under the markup with data-master-vm attribute to put them under the scope of the named master view model.

Throughout this view, standard Knockout data binding notations are used to bind various markups to the view model properties, i.e., the textInput , forEach , css , and text . DotNetify adds its own custom binding:

vmCommand , used for command binding. In the above case, when the table row is clicked, send the value of the Id property of the associated process item to the SelectedProcessId .

Code-Behind

For rendering the charts on the canvas elements, the view uses ChartJS library ( .js file placed in Scripts ), which has to be initialized and updated with JavaScript. In Part 1, this was referred as code-behind , written in the module pattern, and is basically the client-side logic of your application to handle UI-specific components. Here's the same JavaScript converted to TypeScript .

The last thing to do for this class library is to include the transpiled JavaScript files in app.js , which is the dotNetify's RequireJS config file. I placed those TypeScripts under /Scripts/CodeBehind .

After compiling, make sure the JavaScript files from the TypeScripts were created. I'm a bit paranoid on this because Visual Studio only builds them on save, and when you do fresh get from the repository, they won't build. That's why I usually include the transpiled JavaScript files along when I check in.

Setting Up Self-Host Console App

We will now set up the console app to self-host the application. This is good for testing, and we can use this later to deploy to other operating system using Mono. Create a new Console Application project, then:

Build and run this app with Administrator privilege. Go to localhost:8080 on your browser, you should see the screen below:

Setting Up Windows Service

We're now ready to host this in a Windows service. Create a new Windows Service project, then do the same thing as the console app: change the output path to ..\Bin , add a reference to the DemoLibrary , and install the Microsoft.Owin.Host.HttpListener package. Then:

open Service1.cs[Design] , right-click and select Add Installer .

on ServiceInstaller1 properties, set the service name.

on ServiceProcessInstaller1 properties, change the account to LocalSystem .

Build and register the service with InstallUtil.exe . Start the service and test it by going to localhost:8080 on the browser.

Cross-Platform With Mono

It doesn't take much to get this application to run on a Linux box. All you have to do is compile the console app project using Xamarin Studio (the free version will do), copy the entire Bin folder to that Linux box, install Mono, and just run the .exe with mono . Here's what it looks like when I ran it on the Koding's Ubuntu VM:

In this environment, the app was able to get process names and total CPU usage but not the individual process info; perhaps due to Mono implementation of System.Diagnostic .

And so, this concludes the presentation. If you like what you see here, there are many more examples in dotNetify 's website at http://dotnetify.net . It is a free, open-source project hosted on Github where participation is welcomed.