Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

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

5.00/5 (21 votes)
15 Jul 2017Apache6 min read 45.7K  
SignalR is great for building real-time web functionality. MVVM is great for developing your front-end. What if they can be used together? This tip will show how to do just that, using as example a simple project to do live chart on a web browser.

Introduction

Much has been written on the MVVM pattern and rightly so, due to the significant advantages it brings that makes it so much easier to develop, test and maintain your applications. Then there is SignalR, a .NET library that abstracts out the complex mechanics of push technology over the web into simple function calls.

I wrote a lightweight C#/.NET/JavaScript library that I published as a free, open-source project called dotNetify that marries these two. The premise is simple - I would like to architect the front-end of my web application to be composed of views and view models and that they communicate through automated, declarative binding that is the hallmark of MVVM. Sure, there are already JavaScript-based web frameworks that do just that, but here's the kicker: I want the view model on the server, written in .NET. Why? Well,

  • I don't want to write the service layer with its AJAX calls and RESTful Web APIs just to shuttle data back and forth - it's tedious work, and if automated binding can do this for me with just as much bandwidth-efficiency, wouldn't that just be great?
  • I'm already very productive with C#/.NET and there are presentation logic problems I can solve easily and elegantly with .NET, but that I know will give me much grief if I were to do it using those JavaScript frameworks.

The challenge of course was to develop this automated, two-way binding that can work across the web and only dispatches updates to make it bandwidth-efficient, and that's where SignalR came in. It's already capable of facilitating two-way communication across the web, so all I needed to do was to build an MVVM-styled abstraction over it.

Another great open-source library Knockout (update: also supports React!) provided me with the syntax and mechanics to do declarative binding on the HTML views. Knockout has this concept of view models that sit on the browser, so I did the most logical thing to me:

  • auto-generate the client-side view model from the .NET view model I've written
  • let Knockout facilitate the binding between the HTML view and that auto-generated client-side view model
  • and let SignalR facilitate the binding between that client-side view model with the server-side .NET view model

Now, I got end-to-end automated binding between my HTML view and my .NET view model; awesome!

Another piece I put in was to provide the capability to inject JavaScript code on the client-side to allow me total control of this communication process. I called this the "code-behind". There are many great JavaScript UI components out there, but of course they don't provide bindable properties - so I would need the code-behind to work behind the scenes to facilitate the binding I've declared on the HTML view.

So there, I think I've assembled all pieces of the puzzle. To demonstrate how well this works out, I wrote a simple app that displays a chart on the browser that updates its data every second.

Live Chart Web Application

The source code in that Github repository is just a basic ASP.NET MVC solution that was created from Visual Studio 2015 template (compatible with VS 2013 too). I packaged the dotNetify library and published it to Nuget.org so it will first pull the library files and all its dependencies when you compile it.

Now let's talk about the View Model. This is a UI-agnostic abstraction of the view where I will write my presentation logic. For the live chart app, the view model is extremely simple:

View Model

C#
public class LiveChartVM : BaseVM
{
   private Timer _timer = new Timer(1000);
   private Random _random = new Random();

   public double[] Data
   {
      get { return Get<double[]>(); }
      set { Set(value); }
   }

   public LiveChartVM()
   {
      // Create initial data for the chart.
      Data = new double[20];
      for (int i = 0; i < 20; i++)
         Data[i] = _random.Next(1, 100);

      _timer.Elapsed += Timer_Elapsed;
      _timer.Start();
   }

   public override void Dispose()
   {
      _timer.Stop();
      _timer.Elapsed -= Timer_Elapsed;
      base.Dispose();
   }

   private void Timer_Elapsed(object sender, ElapsedEventArgs e)
   {
      Data = new double[] { _random.Next(1, 100) };
      PushUpdates();
   }
}

There's only one property called Data to provide data to the chart - just an array of doubles that will be bound to the view, and there is your basic Windows timer to simulate update every 1 second. Notice the view model inherits from BaseVM. This is the base class from dotNetify library that hides the nitty-gritty of what we've talked about earlier.

The BaseVM class provides auto-property methods Get and Set, which implements INotifyPropertyChanged to support the binding mechanism. It provides PushUpdates method that does what it says: push updates to the client. If you think why this can't just be done automatically as Data property value is updated, my reason is optimization: when there are multiple active view models, this method will pool them all so that updates are batched up and not require multiple expensive calls.

Moving on to the View; which is also extremely simple:

View

HTML
<html>
   <head>
      <script src="/Scripts/require.js" data-main="/Scripts/app"></script>
      <script src="/Scripts/Example/chart.min.js" type="text/javascript"></script>
      <script src="/Scripts/Example/LiveChart.js" type="text/javascript"></script>
   </head>
   <body>
         <h3>Live Chart</h3>
         <div data-vm="LiveChartVM">
            <canvas data-bind="vmOn: { Data: updateChart }" />
         </div>
   </body>
</html>

The first script tag loads dotNetify JavaScript library and all its dependencies using RequireJS module loader library. The second script tag loads the ChartJS library that provides the rendering of the chart on the HTML5 canvas element, and the last one is the "code-behind" that we'll discuss after this.

Notice the "data-vm" attribute. This is dotNetify's way to define the scope: basically the HTML elements within it will fall under the jurisdiction of the named view model; in this case, it's LiveChartVM, the view model we've defined earlier.

The "data-bind" attribute comes from Knockout, this is its way of declaring binding on an HTML element. The great thing about it is that Knockout also supports custom binding, which I fully took advantage of. ChartJS naturally doesn't provide bindable property to populate its data, so I wrote a custom binding I called "vmOn" that accepts 2 parameters in the format of an object literal: the view model property to bind to, and the "code-behind" method to call when that property value is changed.

This brings us to the Code-Behind. In a perfect world, where everything is declaratively bindable, I wouldn't have to write it. But I'm not living in that world, so here it is:

Code-Behind

JavaScript
var LiveChartVM = (function () {
   return {
      // On data update, update the chart.
      updateChart: function (iItem, iElement) {
         var vm = this;
         var data = vm.Data();

         if (vm._chart == null) {
            vm._chart = this.createChart(data, iElement);
            vm._counter = data.length;
         }
         else {
            for (var i = 0; i < data.length; i++) {
               vm._chart.addData([data[i]], vm._counter++);
               // Remove the oldest data.
               vm._chart.removeData();
            }
         }
         // Reset the data.
         vm.Data(null); 
      },

      // Create the chart with ChartJS.
      createChart: function (iData, iElement) {
         var labels = [];
         for (var i = 0 ; i < iData.length; i++)
            labels.push(i);

         var chartData = {
            labels: labels,
            datasets: [{
               label: "My live dataset",
               data: iData,
               fillColor: "rgba(217,237,245,0.2)", 
               strokeColor: "#9acfea", 
               pointColor: "#9acfea", 
               pointStrokeColor: "#fff" 
            }]
         };

         return new Chart(iElement.getContext('2d')).Line(chartData, { 
            responsive: true, animation: false });
      }
   }
})();

This is written in a module pattern style featuring the executing anonymous function - there's a good blog article that talks about this. But don't think this as your normal, run-of-the-mill JavaScript code; the dotNetify library automatically finds it through naming convention that matches the view model name and pre-processes it so that the "this" reference is scoped to the client-side view model, among other things.

This code provides the "updateChart" method that you remember is bound to the View. On initial page load, the method is called when the Data property is set to its initial set of data, and in turn it calls "createChart" to instantiate and populate a new ChartJS object. Subsequently as Data property is set to a new value every 1 second, the method updates the ChartJS object.

Live Demo

Live demo is available on my website at http://dotnetify.net/index/livechart. Slightly different code since here I sent out the chart X-axis labels as well, but otherwise the same logic.

This website is hosted on a server that meets the requirements for SignalR to use Web Socket, so I was pretty excited to see it in action - very efficient transport with low overhead. By the way, this demo also looks very good on an iPad!

Afterword

So that's it for Part 1. It's been fun for me writing this dotNetify library because I know I could actually take it and use it on a real-world web app and become much more productive - and happier (very important!).

There are quite a few more things that this library can do - the basic CRUD, lazy loading, nested views, communication between views, and so on. Please check out the website at http://dotnetify.net and its GitHub repository.

Related Articles

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0