Paz BiberMoving to single page application with WebAPI and AngularJS

Single Page Application (SPA). Even the acronym sounds so inviting.
If you are developing a web application you’re probably creating a single page application, or at least you have a plan to make your current application a single page app. In CloudShare, we started a few years ago with ASP.NET. It was nice technology, but it lacked separation between view and code.
Then ASP.NET MVC arrived, mitigated this problem and eased handling AJAX calls with MVC controllers. So we moved to ASP.NET MVC. You can create wonderful web pages simply by writing Controllers, Views and defining the correct Model.
You can even give it a “Single Page Flavour” by invoking ajax calls that retrieve data from a server. But there’s still a problem here. Separation between code and HTML is hard (sometimes impossible) on the server side. Did you ever find yourself writing some c# code in the view just because “the product really needs this, so please make a small change to make it work”? Or client side (jQuery… you know what I’m talking about…)

Another challenge also rises up – separation of responsibility. There are times when we want to let one programmer handle the client (Javascript, CSS) and another handle the server. Since the pages and the code are close – it is sometimes hard to separate between them (although MVC does a great job in emphasizing the distinction). We also want to create a Web App, not Web Pages. And last, but not least – testing. In his blog, David described how we test old Javascript pages. Surely there’s a better way, right?

Single Page Application in a nutshell

Moving to SPA means we have one HTML page (we are using one MVC View), which creates an AngularJS application (I’ll elaborate on this in the Client Side section). The page looks like this (default.cshtml):

@using System.Web.Optimization
{
Layout = "~/_SiteLayout.cshtml";
Page.Title = "CloudShare Home";</pre>
}

@section featured

{

@Styles.Render("~/bundle/css")

}

<div ng-app="cloudshareApp" id="ng-app">

<div ui-view></div>

</div>

@section scripts

{

@Scripts.Render("~/bundle/lib")

@Scripts.Render("~/bundle/app")

}

The client side application code is bundled in “~/bundle/app”, and the libraries we use are bundled in “”~/bundle/lib”. The AngularJS application loads the relevant partial HTML (“The View”), the controller, the services and the directives. When data is needed (CRUD) – the client side application calls the server (using standard REST api calls) via Microsoft’s Web API. When a UI fragment is needed, the client side performs a standard GET request (client side is handled by AngularJS).   The first step we took, was to define the software layers. It looks as follows: spa-paz

Server Side

The first 3 layers are standard layers. In a nutshell: We use SQL server as a database, the repository is a set of class that implements a standard IRepo interface and uses NHibernate as ORM, and the business logic layer is a set of classes that uses the database-taken entities (we call them Domain Objects) to perform any business action needed.

REST Api Controllers

We get REST Api support by using Microsoft WebAPI. The code is organized as follows:

  • DTO – where we place all the objects we send / receive.
  • Infra – where we define things like error handling, authentication, mapping and more.
  • Controllers – the core Web Api code. This is where we define the API we expose, either pure REST or custom methods.

 

DTO

DTO (Data Transfer Object) is a POCO (or PONO – Plain Old C# / .NET Object). No logic should be defined in this object, not even the mapping logic (where we map Domain Object(s) to a DTO), although it is very tempting to add a constructor that takes a domain entity. By separating the mapping logic we get better code organization and better testability. Code testing and code injection will be covered on the next blog. Working with REST Api, you usually have 2 GET methods: Get all, and get by id (also generated by Visual Studio, when you create a new Api Controller). Get All is used when we need data for selection, or for grids, while Get by Id is used when we need full entity details. In those cases we use inheritance: Entity Brief (for the basic information – usually name and id) and derived from brief – the entity itself (with all the needed data).

    public class JobBriefApiDto
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }

    public class JobApiDto :JobBriefApiDto
    {
        public int RunOrder { get;set;}
        public string JobTypeId { get;set;}
        public List JobParameters { get; set; }
    }

Job DTO – plain objects with inheritance to support get all and get by id REST api calls.

Infra

In the infrastructure layer we handle all the common tasks.

  • Error handling is done by catching all the thrown exceptions in one location and transforming them into a standard HttpResponseException. No error handling is done in the business logic. We use a custom ExceptionFilter which inherits from ExceptionFilterAttribute and implements the OnException method.
  • Authentication is done by a WebAPI custom DelegatingHandler (see here for the DelegatingHandler location in the WebAPI processing stack). This will allow us to look for authentication cookie or API key, and map them into internal user domain object before the call is being processed by the controllers.

In future blogs we will dive deeper into Error handling and authentication.

Controllers

Controllers inherits from ApiController, and implements the basic REST calls (GET for select, POST for insert, PUT for update and DELETE for, well, delete). We keep them simple. How simple? the only thing they do is call the Business Logic layer and map the result into DTO. If we find a controller with more code – we refactor.   Standard API GET implementation:


public ClassStudentApiDto Get(string classId)

{

ParameterValidator.CheckValidExternalId(classId, EntityType.CO, "id");

 

var clsId = EntityIdMapper.ExternalToInternal(classId);

 

return ClassStudentMapper.ToDto(

ClassBLService.GetStudent(clsId)

);

}

Client Side

“AngularJS” is a broad area and by no means can I cover it all here. I do hope to explain how we use AngularJS and how we communicate with the Web API. I assume you know AngularJS basic concepts, like scope, promises, dependency injection, and Angular directives. Angular’s main components are:

  • Application
  • Controllers
  • Services (we split them into Network access and library services)
  • Directives

 

Application

AngularJS application is what starts the processing. As seen earlier – on the main (and only) page we instruct AngularJS to use our app (ng-app=”cloudshareApp”). The application is defined in an app.js file:


// Declare app level module which depends on filters, and services

var cloudshareApp = angular.module('cloudshareApp', [

        'ui.router',

        'restangular',

       // More dependencies

])

    .constant("jQuery", window.$)

    .config(['$stateProvider', '$urlRouterProvider', 'RestangularProvider', function ($stateProvider, $urlRouterProvider, RestangularProvider) {

        $stateProvider

            .state('base', {

                url: '',

                abstract: true,

                template: '<div ui-view></div>',
                 
                 resolve: {
                        'urlHelperInitFinished': ['UrlHelper', function(UrlHelper) {
                              return UrlHelper.getInitPromise();
                     }]
                 }
              })
             
             .state('base.home', {
                url: '/home',
                templateUrl: '/ClientApp/app/partials/Home/Home.html',
                controller: 'HomeController'
             })

// More UI-Router states
            
             $urlRouterProvider.otherwise('/home');
    }]);

We use UI Router to map url to state. This can be thought of as: with a given url, (for example: www.cloudshare.com#createEnvironment) URL Router maps the URL (defined in the “url” property) to the partial HTML (defined in the “templateUrl” property) and the handling controller (defined in the “controller” property).

Controller

Declaring a controller is done by providing the controller name, it’s dependencies and a constructor function. We defined a template that we found helpful in keeping everything in order and simplifying testing: We define a constructor function that returns an object with the method “populateScope” (this method does all the initial scope population, and it is easier to test a function than to test the constructor), and when adding the controller, we create a new object and call “populateScope”. Here is a snippet (pipelineInstanceController.js):


(function () {

    'use strict';

    angular.module('cloudshareApp')

    .controller('PipelineInstanceController',

        ['$scope', /* More dependencies */,

            function ($scope, /* More dependencies */) {

                var ctrl = new PipelineInstanceController($scope, /* More dependencies */);

                ctrl.populateScope();

                return ctrl;

            }]);

    var PipelineInstanceController = (function () {

        function PipelineInstanceController($scope, /* More dependencies */) {

            this.$scope = $scope;

            /* Save the rest of the dependencies */

        };

        PipelineInstanceController.prototype.populateScope = function () {

            // Do all the initializing stuff

            /* Bind all the functions to the the scope */

            scope.processJobInstancesData = this.processJobInstancesData.bind(this);

            scope.processJobInstancesData = this.processJobInstancesData.bind(this);

        };

        

        PipelineInstanceController.prototype.processJobInstancesData = function (stageInstance, jobInstances) {

            // Do some logic here

        };

        PipelineInstanceController.prototype.setPipelineInstance = function (pipelineInstance) {

            // Do some more logic here

        };

        return PipelineInstanceController;

    })();

})();

 

Services

As mentioned earlier, services can be used either as libraries or as network access wrappers. Since we are focusing on AngularJS and Web API, I will discuss the network access services. AngularJS offers a nice REST access mechanism ($resource), but we found Restangular more appealing due to it’s Promise based approach, and the way it constructs the REST URLs. We setup Restangular at the application config:


        RestangularProvider.setBaseUrl('/api/');

        RestangularProvider.setDefaultHttpFields({

            "headers": {"common": {"Accept": "application/json"}}

        });

And in our service we define a const “BASE_RESOURCE” that points to the REST base URL. We now define the methods that invokes the API calls – all the functions return a Promise. Here is a snippet:


(function () {

    "use strict";

    angular.module('cloudshareApp')

    .factory('PipelineInstances', ['Restangular', '$q', function (Restangular, $q) {

        return new PipelineInstancesService(Restangular, $q);

    }]);

    var PipelineInstancesService = (function () {

        function PipelineInstancesService(restangular, $q) {

            this._restangular = restangular;

            this.$q = $q;

        }

        PipelineInstancesService.BASE_RESOURCE = "PipelineInstances";

// Get all (usually returns a brief DTO)

        PipelineInstancesService.prototype.<b>getAll</b> = function () {

            return this._restangular.all(PipelineInstancesService.BASE_RESOURCE).getList();

        };

// Get by Id (returns a full DTO object)

        PipelineInstancesService.prototype.getById = function (pipelineInstanceId) {

            return this._restangular.one(PipelineInstancesService.BASE_RESOURCE, pipelineInstanceId).get();

        };

// Calls web API POST (insert a new item)

        PipelineInstancesService.prototype.createPipelineInstance = function (pipelineInstanceName, pipeline) {

            return this._restangular.all(PipelineInstancesService.BASE_RESOURCE).post({ pipeline: pipeline });

        };

        return PipelineInstancesService;

    })();

})();

Directives

From AngularJS site: “At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS’s HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children”. Since directives relate to DOM manipulation, they have little to do with SPA (but a lot to do with using AngularJS), so I will mention them briefly: We think of directives as a way to add functionality to a DOM element, these directives usually implement the link function, and do not have a template (for example: the directive stopEvent which catches the mouse click and prevents it from propagating to the rest of the DOM), and as UI component – these directives have a template (or external templateUrl). For example: the directive job, which handles how we display a job entity.   stopEvent directive:


cloudshareApp.directive('stopEvent', function() {

    return {

        restrict: 'A',

        link: function (scope, element, attr) {

            element.bind('click', function (e) {

                e.stopPropagation();

            });

        }

    };

});

job directive:


angular.module('cloudshareApp')

.directive('job', ['PipelinesUi', function (PipelinesUi) {

    return {

        restrict: 'E',

        replace: true,

        scope: {

            job: '='

        },

        link: function (scope) {

            scope.getJobTypeName = function () {

                return getJobTypeName(scope.job);

            };

        },

        templateUrl: 'ClientApp/app/partials/Pipelines/Job.html'

    };

}]);

Conclusion

Single Page Application is the standard in web applications. AngularJS is a great framework that handles all the “Plumbing” needed for binding, data transfer and client side code / view separation. It also has a tremendous architectural and management impact. It strengthens the client / server partitioning, enables focusing on the data model and maybe the most important feature – it is built for testing. Web API is Microsoft’s standard for REST API, and it integrates seamlessly with AngularJS client. It provides flexibility with ease of development. This post describes only half of what working with AngularJS and Web API is. In my next post I will describe how testing is done (and yes, we too believe TDD is the right way to go).

Paz Biber

Paz Biber

CloudUI team leader.

Leave a Reply