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

A Simple Dashboard with ASP.NET Core 2.0, SignalR, Angular 5 and Chart.js

0.00/5 (No votes)
10 Apr 2018 1  
The sample provides Web access to devices status and displays dynamic metrics charts with ASP.NET Core 2.0, SignalR, Angular 5 and Chart.js.

Fig. 1. Dashboard appearance in browser

Introduction

The article presents a simple dashboard that dynamically displays the status of your information sources and their recent metrics. Information sources (devices) periodically send their measurements using HTTP protocol to appropriate Web services dedicated to specific device's type. Then the data is propagated to a dashboard Web service. A Web application obtains data from the dashboard service and presents them in browser.

I introduced the first version of this system two years ago in this article at the CodeProject. The overall structure remains unchanged, but the implementation is remarkably different. The main differences are:

  • ASP.NET Core for Web API services
  • Angular 5 for frontend (support for AngularJS a.k.a. Angular 1 was dropped)
  • Dynamic presentation of recent measurements history using Chart.js
  • Generally enhanced UI

Previous Windows .NET Framework implementation used SignalR library for communication between services and with Web client. Current version also employs SignalR for .NET Core, which, at the moment, is available in pre-release. SignalR allows developers to relatively easily provide duplex communication with remote method calls.

System Structure

Let's briefly describe the system components and data flow. The overall system structure is depicted in Fig. 2.

Fig. 2. The system structure

Backend

Simulator

In the real world, information sources (devices) generate metrics for the system. In our sample, the role of such devices plays console application DevicesSimulator. It has remained almost unchanged since the previous version. Simulator is written using Windows .NET Framework (not .NET Core) DLLs and, therefore, may not run in non-Windows OS. The simulator sends generated data to DeviceXApp Web API service. On its start, simulator first creates and sends out descriptors of all available devices, and then starts to generate their measurements. Device descriptor contains integer deviceId, typeId and string deviceName, description, deviceSpecificData.

Web API Services

Unlike simulator, both backend services DeviceXApp and DashboardApp2 are developed with .NET Core compatible libraries. The data generated by simulator are transferred to DeviceXApp with HTTP GET and its method RegisterDevice() of DeviceController type is called to receive the data. Type DeviceController does not have much code and relies on its base class BaseDeviceController from the BaseDeviceControllerLib library. Similarly, the method BaseDeviceController.SendMeasurement() is called to periodically receive metrics sent by simulator to DeviceXApp.

DeviceXApp service processes raw data received from devices (simulator in our case) of appropriate type. In this design, each device type has a dedicated service for data processing similar to DeviceXApp. Class BaseDeviceController of BaseDeviceControllerLib is base class for controllers of all such services that may be developed in future. In the real world, it may use device and/or device's type specific data (e.g. calibration table), but in our simple example, the data is forwarded to DashboardApp2 Web service as they are. In addition, DeviceXApp determines device status based on device's type information available to DeviceXApp from its configuration. So the above methods for device registration and data receiving transfer this data to DashboardApp2 using SignalR client DLL HubClientHandlerLib. Its type HubClientHandlerLib.HubClient is used for that purpose by the BaseDeviceController class.

Web service DashboardApp2 acts as a server for both DeviceXApp data provider and frontend Web application written in Angular 5. As such, the dashboard service provides two different SignalR hubs: DevicesHub for .NET Core data provider DeviceXApp, and WebHub for UI Web application. The dashboard also has its own controller, but it is not used by the system and is employed just to indicate in browser that the service is up and running.

SignalR provides different instances of hub class for client calls. Therefore, hub class supposes to be stateless. But DashboardApp2 has to keep collections of device descriptors, their types and measurements history. It also needs to provide data exchange between DevicesHub and WebHub. Singleton class InterHubHelper does this work.

For dynamic Web UI updates, we have to provide continuous data feed from DashboardApp2 to Web application. This may be achieved either by polling the Web application DashboardApp2 WebHub or by pushing data by WebHub to the Web application. The latter scenario is implemented with SignalR streaming mechanism. Method StartStreaming() of WebHub type is called by Web client after establishing connection with the DashboardApp2 server:

public IObservable<MeasurementsResult> StartStreaming()
{
    return Observable.Create<MeasurementsResult>(async observer =>
        {
            while (!cts.Token.IsCancellationRequested)
            {
                await Task.Delay(InterHubHelper.Instance.StreamingIntervalInMs, cts.Token);
                observer.OnNext(new MeasurementsResult 
                            { Measurements = InterHubHelper.Instance.MeasurementsObj });
            }
        });
}

As you can see, SignalR streaming employs observable pattern for continuous data pushing to Web client. Current InterHubHelper.Instance.MeasurementsObj object contains measurements received since the last push and is updated by the singleton's InterHubHelper's timer handler:

timer = new Timer(_ =>
   {
       lock (locker)
       {
           if (lastTime > DateTime.MinValue)
           {
               // Output only measurements received since last update for this connection
               measurements = lstMeasurement.Where(m => m.Timestamp > lastTime).ToArray();

               // Forget old measurements
               lstMeasurement = lstMeasurement.Where(m => 
                       m.Timestamp > DateTime.Now - TimeSpan.FromSeconds(historyDepthInSec)).ToList();
           }
       }

       lastTime = DateTime.Now;
    },
    null, StreamingIntervalInMs, StreamingIntervalInMs);

where:

Measurement[] measurements;
public Measurement[] MeasurementsObj
{
    get
    {
        lock (locker)
            return measurements;
    }
}

Thus, SignalR streaming provides continuous asynchronous update of browser UI. As it will be discussed below in the Frontend chapter, this allows the user not only see update of devices status, but also observe a running chart of appropriate metrics in near real time.

Both Web API services DeviceXApp and DashboardApp2 in their Startup type implement Cross-Origin Resource Sharing (CORS) allowing any client use their services.

Frontend

The frontend Web application is developed with Angular 5. It uses external packages to support communication (SignalR) and charts (Chart.js). Both packages should be installed with npm packager.

Since angular/cli is installed, for our development project running in local machine, NG Live Development Server is used as a Web server. By default, it listens on port 4200. DashboardApp2 CORS support permits Web application communicate with DashboardApp2.

Web application consists apart from standard predefined AppComponent, of several components, namely, HomeComponent (presented as a placeholder only), DevicesComponent, ChartComponent and NavComponent for navigation, and a provider (service) CommunicationService. The latter is placed in a shared module for possible future use in different modules of Web application. The service is injectable. However, we need some place to keep SignalR client as well as other state objects like collections of groups, devices, devices' types and recent measurements. These data should be available to all components. This is achieved with introduction of additional "singleton" Communication class (in file communication.service.ts). Since Communication class is not exportable, it is accessible only via its holder class CommunicationService. So any component with injected CommunicationService gains access to Communication's public variables and methods using CommunicationService.comm "prefix".

One of the main features of the application is a dynamic moving chart for each device. Type Chart of Chart.js provides support for charts. The chart is created by DevicesComponent as soon as device is registered with it. To ensure proper functioning of the device charts, we have to keep their instances. But this is not possible with traditional navigation between Angular components. Normally, the previous component is destroyed upon activation of the next one. To preserve charts, we have to keep alive instance of DevicesComponent and just make it invisible on component switch. In our code, a simple (rather naive) solution is implemented. A bootstrap AppComponent lists in its decoration template property selectors of all other components (in our case, these are NavComponent, HomeComponent and DevicesComponent), thus forcing their creation. NavComponent is permanently visible, while others provide in their HTML mechanism of visibility switch depending on Boolean isShown variable. Singleton class Communication available across all components provides selectComponentSubject: Subject<any> event. HomeComponent and DevicesComponent subscribe for it, and NavComponent activates it when menu button is pressed. Name of selected component is sent to all subscribed components, and they update their visibility state accordingly. The same mechanism ensures appearance of HomeComponent at the start of the application irrespectively on relative path in URL. Such behavior is often achieved with Angular Guards. So, for our Web application "without Guard" does not mean off-guard.

As it was said above discussing backend, Web application communicates with DashboardApp2 acting as SignalR client. Our Communication class has variable hubConnection: HubConnection and subjects (events) streamSubject: Subject<any> to support streaming, and another one receiveMeasurementSubject: Subject<any> to notify components that they should take new measurements from Communication's mapMeasurements: Map<number, Array<DeviceMeasurement>>. Method start() of Communication class activating the class is given below:

async start() {
    this.hubConnection = new HubConnection(this.BASE_URL);

    // Handler on streaming push measurements
    this.hubConnection.on('registerDeviceWithWeb', result => this.onRegisterDevice(result.device));

    try {
        // Start connection
        await this.hubConnection.start();
		
        this.isConnected = true;
		
        await this.getInitInfo();
			
        // Handler to receive measurements
        await this.streamSubject.asObservable().subscribe(result => this.onReceiveMeasurements(result));

        await this.hubConnection.stream("StartStreaming").subscribe(this.streamSubject);
    }
    catch (err) { 
        console.error(err + ': Unable to establish connection with ' + `${this.BASE_URL}`);
    }
}

URL of DashboardApp2 service for Web client is hardcoded as BASE_URL in Communication class.

Using Code Sample

Code sample for this article consists of two separate downloads: for backend and frontend. I have tested the sample only under Window 10 OS in local machine, so I will describe procedure for this environment only. Please create directory Dashboard and then unzip backend file Backend_src.zip inside this directory. Directory Backend with server side solution will appear under Dashboard directory. Now in directory Backend, you can open DashboardBackend.sln in VS 2017 and build it.

NuGet will install required packages, notably Microsoft.AspNetCore.SignalR (1.0.0-preview1-final). Then the following projects should be started: DashboardApp2, DeviceXApp and DevicesSimulator. Please check appropriate URLs before running. DevicesSimulator has hardcoded URL of DeviceXApp, and DeviceXApp has URL of DashboardApp2 device hub in its appsettings.json configuration file. As it was said above, the given implementation of DevicesSimulator is developed using .NET Framework DLLs and therefore may not run in a non-Windows environment.

Note 1. It may happen that VS 2017 fails to launch backend projects as "Multiple startup projects" for a couple of initial starts with some strange messages like "Unable to launch the previously selected dubugger. Please choose another." But after initial several failures, VS works properly.

To run frontend, some preliminary installations have to be performed. To begin with, Node.js and npm packager should be installed. The following resources explain how to do this: [1], [2]. Then angular/cli should be installed globally with:

npm install -g @angular/cli

Some Integrated Development Environment (IDE) may be used. My choice was VS Code. Here [3], [4] you may find how to use VS Code for Angular development. After these preparations please open console, go to your root Dashboard directory and create a new Angular project from there with:

ng new Frontend

(please refer [5] for details).

Note 2. Commands for installation of all external libraries should be carried out from newly created Frontend directory.

Please install Angular Material UI library used in the project:

npm install --save @angular/material @angular/cdk

with details here [6]).

Angular Font Awesome package is installed with:

npm install --save font-awesome angular-font-awesome

command having providing details here [7].

Then SignalR installation has to be performed running the following command:

npm install @aspnet/signalr

(details may be found here [8]).

The last external library to be installed is Chart.js

npm install chart.js --save

(see also [9]).

The following versions of main packages are used:

Node.js 8.10.0
npm 5.6.0
Angular 5.2.9
Angular CLI 1.7.4
Typescript 2.5.3

After all required libraries have been installed, you have to download Frontend_src.zip file to Frontend/src directory and replace there directory app and files styles.css and fivicon.ico with Frontend_src.zip file content. Now you can open Dashboard/Frontend directory in VS Code.

To run the sample, first start backend. From VS 2017, run the following projects: DashboardApp2, DeviceXApp and DevicesSimulator (please see Note 1 above). You will see two browser windows displaying messages "This is DeviceXApp" and "This is DashboardApp2". DevicesSimulator is running in console showing message "Press any key to start generate measurements...". It will start to generate measurements after a key will be pressed. To run frontend, open in menu View -> Integrated Terminal and run there the following command:

ng serve -o

This command builds Web application and starts browser on http://localhost:4200 connecting to NG Live Development Server listening on this IP address. Web application instance displays "This is Home page" message. By pressing Devices menu button, you will navigate to (or rather make visible) appropriate page looking similar to one in Fig. 1 with changing devices status and moving historical metrics charts. After some time for testing purposes, you may close DevicesSimulator (still keeping both services running) and observe that status of all devices becomes "NoData". Then restart DevicesSimulator, press there any button to start measurements generation and see that data flow has resumed.

Debugging frontend project may be carried out as following. After activation of ng serve -o command, any saved code update leads to restart of Web application. Breakpoint may be set by writing operator debugger; in the code. Then open Inspect window in browser (for Google Chrome it can be done with right click menu -> Inspect or shrotcut Ctrl-Shift-I). As soon as this line will be reached, browser will stop and show the breakpoint in Source tab. From there, you can proceed with debugging in browser. debugger; operator has no effect if Inspect window is not open. To debug with VS Code, addition component should be installed. Particularly for Google Chrome browser, Debugger for Chrome may be used (refer [10] for details).

Note 3. Command ng serve -o may be also activated from the Dashboard/Frontend directory in command console without VS Code or other IDE with the same effect. If this approach is chosen, then the developer can deal with the Web application completely without IDE using any available editor and the debugger; operator technique for debugging in browser.

Conclusions

This work provides a concept of combination of ASP.NET Core, SignalR, Angular and Chart.js to implement responsive near real-time data acquisition and presentation. It by no means pretends to be a full blown product. Therefore, such important parts of Web application as authentication, login, etc. as well as deployment are not addressed here - there are plenty of publications describing those topics comprehensively. For further development, it would be nice to implement communication with TLS/SSL protocol to secure transferring data. Possibility to have charts for large number of devices should be checked. Test DeviceXApp and DashboardApp2 projects in non-Windows OS will be useful.

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