Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple Dashboard: ASP.NET Web API Services with AngularJS and Angular 2 Clients

0.00/5 (No votes)
25 Apr 2016 1  
ASP.NET Web API, SignalR and AngularJS / Angular 2 work together

Introduction

This article presents an example of collaborating RESTful Web services with AngularJS (below referred to as ng1) and Angular 2 (ng2) clients. A simple Web based dashboard service shows state of various processes controlled with hardware devices. The devices are connected to network and capable to communicate using HTTP with Web services dedicated to specific device type. Each such service (in the code sample this is DeviceXApp) receives data from devices with certain time interval, processes the data in order to determine state of controlled process and forwards information about this state to dashboard Web application (DashboardApp). Communication between DeviceXApp and DashboardApp is performed with SignalR. Both Web applications are developed with ASP.NET Web API. Client side of DashboardApp is presented in two flavors, ng1 and ng2. Both clients have the same functionality and user interface, work with the same server software using SignalR for full duplex communication.

Flow

Overall structure of the system is depicted below in Fig. 1.

Fig. 1. Overall structure

DevicesSimulator console application simulates communication activity of hardware devices sending periodic HTTP requests to DeviceXApp. Each device with its first message registers itself with DeviceXApp (it is assumed that DeviceXApp starts prior to device registration). DeviceXApp passes device registation data to DashboardApp which puts the device to appropriate category based on its ID and preconfigured ranges of device IDs for each category. After successful registration device keeps sending its measurement data to DeviceXApp. The latter determines state of the device's process (Ok, Warning or Error) and forwards it to DashboardApp for presentation to user. If device fails to send its data within certain preconfigured time interval then DeviceXApp reports NoData state of the device. ng1 and ng2 applications present client user interface of DashboardApp showing device information and state as per device categories. User interface is presented below in Fig. 2 (please do not be too demanding to it since this is just small and non-realistic sample).

Fig. 2. User interface

Each category table may either expand or collapse. In information flow of the sample we would like to avoid as much as possible moving extra data around in order to reduce network load and improve performance. So each device on its start once registers itself with DeviceXApp service and then sends to it only measured data.

Working scenario

Devices (in our case simulated with DevicesSimulator console application) register themselves with DeviceXApp service with HTTP GET request invoking remotely method DeviceController.RegisterDevice() of DeviceXApp. By this registration device provides the service with its ID, type, name, description and some additional data (e.g. calibration table, etc.). To enable secure connection HTTPS protocol is used. RequireHttpsAttribute : AuthorizationFilterAttribute attribute is placed to a separate assembly RequiresHttps.dll since the attribute is used in all Web services. RegisterDevice() method in its turn registers the device with DashboardApp Web service by remotely invoking method RegisterDeviceWithDashboard() of SignalR ConnectivityHub. After its registration each device keeps sending to DeviceXApp service its updated data remotely invoking method DeviceData() of DeviceController type. The method obtains device status based on data received and remotely asynchronously invokes DashboardApp service method SetDeviceStatus() of SignalR ConnectivityHub. In service DashboardApp, methods of CommunicationHub put devices data and status into appropriate collections of singleton DeviceDataProvider class. DashboardApp delivers device information and status to end user with ng1 and ng2 applications communicating to them with full duplex SignalR via DevicesHub. On their initialization ng1 and ng2 applications invoke method Init() of DevicesHub in order to obtain information about all currently active devices.

HubClientHandler.dll assembly contains HubClient class which provides proxy for SignalR hub clients. Some environments automatically stop services inactive during certain time. To avoid this, keep alive mechanism is provided. KeepAlive.dll assembly contains KeepAliveController which makes priodic HTTP GET requests to itself. Keep alive mechanism is activated from Application_BeginRequest() method. Being disabled by default, it is enabled by setting of KeepAliveTimeoutInSec configuration parameter to a positive value.

Angular

These fairly small ng applications were developed to provide samples for communication with SignalR and to feel differences between ng1 and ng2 approaches. As it was said above our ng1 and ng2 applications support duplex SignalR communication with DashboardApp service. Their visual part is rather simple (presented above in Fig. 2 for ng2; in case of ng1 it is the same but (ng1) is written instead of (ng2)). It comprises of a table with expandable / collapsable collections of rows. Each such a collection represents a category of devices, whereas each row represents a device. For each category and device its name, description and the last state are presented. The state of category reflects the worst state of devices belonging to it. Table is periodically updated with data pushed by DashboardApp. HTML <p> tag displaying server time and a button with its explicit update are placed above the table (it does not look too sexy, but the button illustrates direct call from ng applications to DashboardApp).

AngularJS (ng1)

To enable ng1 application, appropriate jQuery and Angular JavaScript files are placed in their correspondent folders under .\DevicesManagement\DashboardApp\Scripts folder. File .\Scripts\_Custom\ng1.js contains ng1 application code. The application is activated from .\DevicesManagement\DashboardApp\ng1.html file and consists of three main methods, namely,

app.run() initialize global variables and start connection to DashboardApp with devicesHub
devicesServer = app.factory('devicesHubProxy', ... provide mechanism for SignalR communication  
devicesServer.controller('DevicesController', ... call getServerTime() method of DashboardApp devicesHub, subscribe for all, allDevices and serverTime push calls from devicesHub with devicesHub.on('pushMethodName' , ..., and call init() method of DashboardApp devicesHub (it should be called after subscription to push calls).

The controller also contains assignment of variables and functions used in ng1.html file.

To ensure communication with SignalR ng1.html file contains references

<!-- Reference the SignalR library. -->
<script src="node_modules/jquery.signalR-2.2.0.min.js"></script>

<!-- Reference the autogenerated SignalR hub script. -->
<script src="/signalr/hubs"></script>

Angular 2 (ng2)

Writing this article I had no intension to provide ng2 tutorial. For that purpose I'd like to recommend an excelent IMHO book [1]. This work is to illustrate SignalR communication aspect of ng2 application. ng2 platform is still to be officially released. In our application version 2.0.0.-beta.6 is used. ng2 was installed with Node.js installation following recommendations from [1]. Particularly Node Package Management (npm) utility was used. Command

"C:\Program Files\nodejs"\npm init -y

being called from working folder of DashboardApp .\Devicesmanagement\DashboardAppcreates, creates initial version of package.json configuration file. Then this file was manually edited - please see its modified version in .\DashboardApp folder.

Command

"C:\Program Files\nodejs"\npm install

creates node_module folder containing dependencies specifies in package.json file. Newly created folder node_module contains subfolders with JavaScript and TypeScript files to be referred by our application. Then file config.js was created in folder .\DashboardApp to configure ng2 application. In our case it states that our application code is in folder app, and the code to launch is located in the file .\app\main.ts. Such configuration of the package allows us to use in index.html file the line

<script>System.import('app')</script>

which loads the content of .\app\main.ts. File index.html serves as main HTML file of ng2 application. Its <head> tag provides references to appropriate JavaScript files and link to stylesheet CSS file. For the sake of simplicity referenced JavaScript files were moved to .\DashboardApp\node_module folder and the rest of this folder content was deleted. File index.html contains the same SignalR related references that ng1.html does for ng1 case.

ng2 application is placed in .\DashboardApp\app folder and its subfolders. The ng2 application file structure is given below:

DeviceManagement
    DashboardApp
        app
            components
                application			
                    application.css
                    application.html
                    application.ts
			
            services
                communication.ts
				
            main.ts

File main.ts is starting point of the application. It launches ApplicationComponent implemented in file application.ts. ApplicationComponent uses CommunicationService (file communication.ts) responsible for SignalR support. Code of ApplicationComponent is given below:

import {bootstrap} from 'angular2/platform/browser';
import {Component} from 'angular2/core';
import {NgFor} from 'angular2/common';
import {CommunicationService} from 'app/services/communication.ts';

@Component({
    selector: 'main1',
    providers: [CommunicationService],
    templateUrl: 'app/components/application/application.html',
    styleUrls: ['app/components/application/application.css', 'Styles/styles.css'],
    directives: [NgFor]
})
export default class ApplicationComponent {
	
    public currentServerTime: string;

    private comm: CommunicationService;

    public imagesUrl: Array<string> = ['expand.png', 'collapse.png'];
    public imageIndex: Array<int> = [];
    public image: Array<string> = [];
    public categoryIndicator: Array<int> = [];
    public categoryIndicatorAsString: Array<string> = [];
    public allCategoriesInfo: Array<any> = [];
    public allDevicesInfo: Array<any> = [];
    public allDevices: Array<any> = [];
	
    constructor(private comm: CommunicationService) {
		
        if (comm) {		
            this.comm = comm;

            comm.getServerTime(data => this.currentServerTime = data);	
	
            comm.subscribeToBrowserFunction('devicesHub', 'all', lst => {
		
                this.allCategoriesInfo = lst[0];

                if (this.allCategoriesInfo != null)
                    for (var i = 0; i < this.allCategoriesInfo.length; i++) {
                        this.imageIndex.push(0);
                        this.image.push(this.imagesUrl[0]);
                        this.categoryIndicator.push(0);
                        this.categoryIndicatorAsString.push('NoData');
                    }
	
                    this.allDevicesInfo = lst[1];	
			
                    this.allDevicesFunc(lst[2]);
                });
	
                comm.subscribeToBrowserFunction('devicesHub', 'allDevices', devices => {
                    this.allDevicesFunc(devices);
                }
            });
	
            comm.subscribeToBrowserFunction('devicesHub', 'serverTime', timeAsString => {
	    	    this.currentServerTime = timeAsString;
            });
	
            comm.init();
        }
    }
	
    allDevicesFunc(devices: Array<any>) {
		
        this.allDevices = devices;
			
        if (this.allDevicesInfo != null && this.allDevices != null && this.allCategoriesInfo != null && 
            this.allDevicesInfo.length == this.allDevices.length && this.allDevices.length > 0) {
			
            // Overall category indicator
            for (var i = 0; i < this.allCategoriesInfo.length; i++) {
                this.categoryIndicator[i] = 0;

            for (var j = 0; j < this.allDevices.length; j++) {

                if (this.allCategoriesInfo[i].Id == this.allDevicesInfo[j].CategoryId &&
                    this.allDevices[j].Status >= this.categoryIndicator[i]) {
						
                        this.categoryIndicator[i] = this.allDevices[j].Status;
                        this.categoryIndicatorAsString[i] = this.allDevices[j].StatusAsString;
                    }
                }
            }
        }
    }
			
    toggleImage(categoryIndex: int) {
        this.imageIndex[categoryIndex] = (this.imageIndex[categoryIndex] + 1) % 2;
        this.image[categoryIndex] = this.imagesUrl[this.imageIndex[categoryIndex]];
    }
	
    getServerTime() {
        this.comm.getServerTime(data => this.currentServerTime = data);
    }
}

File application.ts

 

Files application.html and application.css provide HTML and CSS markup for the application. Metadata of ApplicationComponent refers to these files and contains selector main1 defined as a tag in <body> of index.html. Constructor of ApplicationComponent invokes getServerTime() and init() methods of devicesHub and subscribes for push calls all, allDevices and serverTime thus acting similarly to DevicesController in ng1 application (as you probably know, ng1 controller is not supported in ng2).

Class CommunicationService (file communication.ts) is rersponsible for duplex SignalR communication with DashboardApp:

export class CommunicationService {

    getServerTime(callback: Function) {
        // Start hub, call .NET method of this hub and call callback on its result.
        $.connection.hub.start().then(() => 
            $.connection.devicesHub.server.getServerTime().then(t => callback(t)));		
    }
	
    init() {
        $.connection.hub.start().then(() => $.connection.devicesHub.server.init());			
    }
	
    subscribeToBrowserFunction(hubName: string, funcName: string, callback: Function) {
        var connection = $.hubConnection('');
        connection.createHubProxy(hubName).on(funcName, data => callback(data));
        connection.start();
    }
}

File communication.ts

 

In the above code snippet method subscribeToBrowserFunction() provides infrastructure to subscibe for server's push call connecting it to its handler callback. Methods getServerTime() and init() perform remote invokation of appropriate methods of DashboardApp's DevicesHub type.

 

Currently ng2 application and its referenced files are not integrated into DashboardApp Visual Studio project, and they are linked only via index.html file. The ng2 application is not transpiled (i.e. compiled from TypeScript to JavaScript) before running. Although it is clear that pre-transpiling allows ng2 application run in any browser including those that do not support TypeScript yet, the decision was to avoid tedious transcompiling manipulations in hope that full ng2 support in the browsers is just matter of ng2 final release and some short time. So, transpiling in the browser is assumed.

Note: At the moment not all even modern browsers support transpiling. In Windows this simple ng2 application is running fine in Google Chrome and Mozilla Firefox. Microsoft Edge cannot expand categories, and Internet Explorer cannot run ng2 TypeScript based application at all. All the above browsers run ng1 application with no problem.

Run Sample

The sample should be built with Visual Studio (VS 2013 or VS 2015). Then multiple startup projects DeviceXApp, DashboardApp and DevicesSimulator should be started. IIS Express may be used for simplicity. Wait until the services fully started and then press any key in DevicesSimulator to start devices simulation. Alternatively, after build the services may be started from command line. For that purpose for DashboardApp and DeviceXApp start command console cmd.exe as Administrator, set IIS Express folder (most probably this is C:\Program Files (x86)\IIS Express) as current and run iisexpress for required site (those sites should be described in IIS Express configuration file ..\Documents\IISExpress\config\applicationhost.config):

C:\...>iisexpress /site:DashboardApp
C:\...>iisexpress /site:DeviceXApp

Then start DevicesSimulator and press any key when the services are ready.

To invoke client application you should browse

  • for ng1:   https://localhost:44300/ng1.html , and

  • for ng2:   https://localhost:44300 .

What Next?

Many features may be added converting this sample into more realistic solution, like e.g. logon, persisting state of devices in DeviceXApp using database, publishing in the cloud (I have taken a brief attempt to publish DeviceXApp and DashboardApp from Visual Studio as Microsoft Azure Web Apps - only ng1 version worked. Apparently browser transpiling is not yet supported). It would be also nice to fully integrate ng2 application into Visual Studio solution and provide proper transpiling procedure.

In DeviceXApp handling requests from devices is very basic (since it is not a focal point of this article): each request is processed as it is received and DashboardApp methods invokation is performed. For the real world applications more sophisticated mechanism should be implemented to serve multiple parallel devices requests.

Conclusions

The article presents simple ASP.NET Web API applications with AngularJS and Angular 2 clients. The applications communicate with the clients and each other using SignalR and received information from outside world (some external devices or their simulator) with plain HTTP requests. The fact that AngularJS and Angular 2 cliens share the same service allows us to compare both versions of Angular.

Thanks

My deep gratitude is to Kosta Trosman for his suggestion on dashboard layout, Meir Bechor for a small but given just in time hint, and of course Michael Molotsky for a very useful discussion in the scope of the article.

References

[1] Angular 2 Development with TypeScript by Yakov Fain and Anton Moiseev, 2016

Note

Code of the article was placed at CodePlex which is not available any more. Now it is possible to download code directly. Some references are available with NuGet.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here