Case Study: Converting a Web Application From ASP.NET MVC to NodeJS

AIS developed a prototype web application for real-time collaboration and visualization of geospatial data. To learn more about this application check out parts one, two, and three of this blog series.

In this post, I’ll briefly discuss the rationale behind our decision to convert the backend of our prototype from ASP.NET MVC to NodeJS and will dive into the details of the conversion process. Our goal was to show that while AIS has a proven track record of leveraging the Microsoft stack to deliver solutions to our customers, we are also able to deliver solutions on other stacks based on our customers’ needs and existing infrastructure.

In this particular instance our customer needed a solution that could run in a Linux environment. While there are options available for running .NET applications in a Linux environment, we felt that in this particular instance it would be better for us to explore a different stack. We chose NodeJS for a few reasons. Due to its lightweight event-based model it lends itself really well to single-page applications and realtime applications using WebSockets. Also, our development team already possessed strong competencies in JavaScript and was intrigued to learn more about this platform. There are plenty of other great reasons for choosing NodeJS, but this post will focus primarily on our conversion process.

Our existing ASP.NET MVC single-page application was based on a service-oriented architecture with the client and server pieces very loosely coupled. We made it our goal from the onset to change as little client-side code as possible and focus on making the new backend plug into the existing client.

The first step in this process was the data access layer. Our .NET application used MongoDB as a data store and the C# driver provided by the Mongo team to access this data. The driver attempts to serialize the Mongo documents to .NET objects. This serialization process caused many headaches and involved a lot of additional code to properly handle serialization/deserialization. We used Mongoose in NodeJS as a replacement for the C# driver. Mongoose provided a really elegant method for retrieving our data from Mongo. Since Mongo documents are stored as JSON, working with the data in NodeJS was much easier and cleaner as no conversion had to take place when reading/writing data. This piece of the conversion process was where we saw the biggest gains in code cleanliness.

The next piece of the conversion process exposed the data to the client. In our .NET application we did this using RESTful web services with the ASP.NET Web API. As you might have guessed, this lent itself very well to being reproduced in NodeJS. We used the Express web application framework which allowed us to duplicate our web services in NodeJS using the same URLs and parameters so that no client code had to be modified. Express was really easy to work with and provided a great framework for exposing our data through RESTful services.

Next up in the conversion process was our WebSockets functionality. Our application leverages WebSockets to provide realtime collaboration features for users (see part two of this blog series for a more in depth look at our collaboration features). .NET provides a very nice library for WebSockets support called SignalR. We leveraged this library heavily so this piece of the conversion proved to be more challenging. We chose Socket.IO as a replacement for SignalR. This was the first time we faced a real struggle when trying to not modify our existing client code.

SignalR uses hubs for handling WebSocket communications. Hubs are .NET classes that provide an RPC framework. SignalR then creates proxy JavaScript objects for these hubs that can be called from the client. This is a very nice model, but very different from the way that Socket.IO does things. Socket.IO uses an event-based model with the client and server emitting and listening for events. Due to the extremely flexible nature of JavaScript, however, we were able to shim the SignalR proxy objects and use these shimmed objects to emit events that Socket.IO listened for on the server side. By shimming the objects we were able to leave out existing core client code intact and just add some additional code to replicate the proxy object model in Socket.IO.

The following is a code sample which shows our original client side code from the .NET application:

joinSharingSession = function (sharingSessionObj) {
    var self = this,
        $pnotify;
    sharingSessionObj.isHost = false;
    self.curSharingSession(sharingSessionObj);
    $pnotify = pnotifyInit('<p>You are currently viewing ' + sharingSessionObj.host + '\'s screen.</p>, 'topleft', false, false);
    self.remoteScreenNotify($pnotify);
    mapdemo.sharingHub.server.joinSharingSession(sharingSessionObj.id);
}

Note how in the last line of that function we are calling the SignalR proxy to join a screen sharing session. Rather than going through and modifying all these snippets of code we simply shimmed the proxy object.

mapdemo.sharingHub.server = {
    // lots of other methods implemented here
    joinSharingSession: function(sharingSessionId) {
        socket.emit('joinSharingSession', sharingSessionId, mapdemo.sharingHub.state.username);
    }
};

The final piece of the conversion process was the client-side Javascript code. Our client side used an MVVM pattern with KnockoutJS for data binding. As I mentioned, we did have to add some client-side code to support WebSockets, however, the entire rest of our client code remained unchanged. Our client code called the same RESTful services at the same URLs to retrieve data and bound this data to the DOM using KnockoutJS.

// I don't care if the endpoint I'm hitting is ASP.NET WebAPI
// or NodeJS Express as long as I get the JSON data I'm looking for
$.ajax('/api/rpc/event/EventByFilter', {
    contentType: 'application/json',
    data: JSON.stringify(filterCriteria),
    type: 'POST'
}).done(function (data) {
    if (data) {
        if (self.curSharingSession() && self.curSharingSession().isHost) {
            mapdemo.sharingHub.server.updateEvents(self.curSharingSession().id, data);
        }
        self.mergeEvents(data);
    }
});

In the end, we’re very pleased with how the conversion process went. Because we implemented strategies such as SOA and MVVM to decouple our client and server, it was fairly easy to convert our server side from ASP.NET MVC to NodeJS with very little effect on our client-side application.

About Kyle Linden

Kyle joined AIS in April of 2013 as a Software Engineer. Kyle specializes in developing web and desktop applications using the Microsoft .NET framework. He also has experience developing native and web-based mobile applications for iOS and Android. In his free time (while not programming) he enjoys watching sports, playing golf, and running.

  • Tony

    I have experiment with these frameworks also. I would try to deploy it to Red Hat OpenShift. It is a cost effective way deploy Node.js in Linux environment with a built in Git Repo.

    • Kyle Linden

      Thanks for the tip, Tony. We are currently using AWS to deploy the application. I believe another member of the team has a blog post in the works discussing some options for deployment with NodeJS.