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)
{
measurements = lstMeasurement.Where(m => m.Timestamp > lastTime).ToArray();
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);
this.hubConnection.on('registerDeviceWithWeb', result => this.onRegisterDevice(result.device));
try {
await this.hubConnection.start();
this.isConnected = true;
await this.getInitInfo();
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.