65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET Core 2.0、SignalR、Angular 5 和 Chart.js 构建的简单仪表板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (28投票s)

2018 年 4 月 10 日

CPOL

11分钟阅读

viewsIcon

65145

downloadIcon

2849

该示例提供设备状态的 Web 访问,并使用 ASP.NET Core 2.0、SignalR、Angular 5 和 Chart.js 显示动态指标图表。

图 1. 浏览器中的仪表板外观

引言

本文介绍了一个简单的仪表板,可动态显示信息源的状态及其近期指标。信息源(设备)使用 HTTP 协议定期将其测量值发送到专门用于特定设备类型的相应 Web 服务。然后,数据会传播到仪表板 Web 服务。Web 应用程序从仪表板服务获取数据并在浏览器中显示它们。

我早在两年前就在 这篇文章 中向 CodeProject 介绍了该系统的第一个版本。整体结构保持不变,但实现方式却大相径庭。主要区别在于:

  • 用于 Web API 服务的 ASP.NET Core
  • 用于前端的 Angular 5(已弃用对 AngularJS,也称为 Angular 1 的支持)
  • 使用 Chart.js 动态展示近期测量历史
  • 总体 UI 得到增强

之前的 Windows .NET Framework 实现使用 SignalR 库在服务之间以及与 Web 客户端之间进行通信。当前版本也采用 SignalR for .NET Core,目前该版本仍处于预发布阶段。SignalR 允许开发人员相对轻松地提供具有远程方法调用的双向通信。

系统结构

让我们简要描述一下系统组件和数据流。整体系统结构如图 2 所示。

图 2. 系统结构

后端

模拟器

在实际应用中,信息源(设备)会为系统生成指标。在我们的示例中,此类设备的角色由控制台应用程序 DevicesSimulator 扮演。自上一个版本以来,它几乎没有变化。模拟器使用 Windows .NET Framework(而非 .NET Core)DLL 编写,因此可能无法在非 Windows 操作系统上运行。模拟器将生成的数据发送到 DeviceXApp Web API 服务。启动时,模拟器首先创建并发送所有可用设备的描述符,然后开始生成它们的测量值。设备描述符包含整数 deviceIdtypeId 和字符串 deviceNamedescriptiondeviceSpecificData

Web API 服务

与模拟器不同,后端服务 DeviceXAppDashboardApp2 均使用 .NET Core 兼容库开发。模拟器生成的数据通过 HTTP GET 传输到 DeviceXApp,并调用 DeviceController 类型的 RegisterDevice() 方法来接收数据。DeviceController 类型中没有太多代码,它依赖于 BaseDeviceControllerLib 库中的基类 BaseDeviceController。同样,调用 BaseDeviceController.SendMeasurement() 方法来定期接收模拟器发送到 DeviceXApp 的指标。

DeviceXApp 服务处理来自相应类型设备(在本例中为模拟器)的原始数据。在此设计中,每种设备类型都有一个专门的数据处理服务,类似于 DeviceXAppBaseDeviceControllerLibBaseDeviceController 是未来可能开发的所有此类服务的控制器的基类。在实际应用中,它可能使用特定于设备和/或设备类型的(例如校准表)数据,但在我们的简单示例中,数据被转发到 DashboardApp2 Web 服务,保持原样。此外,DeviceXApp 根据 DeviceXApp 从其配置中获取的设备类型信息来确定设备状态。因此,上述用于设备注册和数据接收的方法使用 SignalR 客户端 DLL HubClientHandlerLib 将此数据传输到 DashboardApp2。其类型 HubClientHandlerLib.HubClientBaseDeviceController 类用于此目的。

Web 服务 DashboardApp2 作为 DeviceXApp 数据提供程序和用 Angular 5 编写的前端 Web 应用程序的服务器。因此,仪表板服务提供两个不同的 SignalR 中枢:DevicesHub 用于 .NET Core 数据提供程序 DeviceXAppWebHub 用于 UI Web 应用程序。仪表板也有自己的控制器,但系统未使用它,仅用于在浏览器中指示服务正在运行。

SignalR 为客户端调用提供不同的中枢类实例。因此,中枢类应该是无状态的。但是 DashboardApp2 必须保留设备描述符、它们的类型和测量历史记录的集合。它还需要提供 DevicesHubWebHub 之间的数据交换。单例类 InterHubHelper 完成此工作。

为了动态更新 Web UI,我们必须提供从 DashboardApp2 到 Web 应用程序的连续数据流。这可以通过轮询 Web 应用程序 DashboardApp2WebHub 或由 WebHub 将数据推送到 Web 应用程序来实现。后一种情况是通过 SignalR 流式传输机制实现的。在与 DashboardApp2 服务器建立连接后,Web 客户端会调用 WebHub 类型的 StartStreaming() 方法。

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 });
            }
        });
}

正如你所见,SignalR 流式传输利用可观察模式将数据连续推送到 Web 客户端。当前的 InterHubHelper.Instance.MeasurementsObj 对象包含自上次推送以来接收到的测量值,并且由单例的 InterHubHelper 的计时器处理程序更新。

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);

其中

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

因此,SignalR 流式传输提供浏览器 UI 的连续异步更新。正如下面将在“前端”章节中讨论的那样,这不仅允许用户看到设备状态的更新,还能在近乎实时的情况下观察相应指标的运行图表。

Web API 服务 DeviceXAppDashboardApp2 都在其 Startup 类型中实现了“跨域资源共享 (CORS)”,允许任何客户端使用其服务。

前端

前端 Web 应用程序使用 Angular 5 开发。它使用外部包来支持通信(SignalR)和图表(Chart.js)。这两个包都应该使用 npm 包管理器进行安装。

由于安装了 angular/cli,对于在本地计算机上运行的开发项目,我们使用 NG Live Development Server 作为 Web 服务器。默认情况下,它监听端口 4200DashboardApp2 的 CORS 支持允许 Web 应用程序与 DashboardApp2 通信。

除了标准的预定义 AppComponent 外,Web 应用程序还包含几个组件,即 HomeComponent(仅作为占位符),用于导航的 DevicesComponentChartComponentNavComponent,以及一个提供程序(服务)CommunicationService。后者放置在共享模块中,以便将来可能在 Web 应用程序的不同模块中使用。该服务是可注入的。但是,我们需要一个地方来存放 SignalR 客户端以及其他状态对象,如组、设备、设备类型和近期测量值的集合。这些数据应该对所有组件可用。这通过引入额外的“单例”类 Communication(在文件 communication.service.ts 中)来实现。由于 Communication 类是不可导出的,因此只能通过其持有者类 CommunicationService 访问它。因此,任何注入了 CommunicationService 的组件都可以使用 CommunicationService.comm“前缀”访问 Communication 的公共变量和方法。

应用程序的主要功能之一是为每个设备提供动态移动图表。Chart.jsChart 类型提供了对图表的支持。图表由 DevicesComponent 一创建设备注册就创建。为确保设备图表的正常运行,我们必须保留它们的实例。但这在传统的 Angular 组件导航中是不可能的。通常,在激活下一个组件时,前一个组件会被销毁。为了保留图表,我们必须保持 DevicesComponent 的实例存活,并仅在组件切换时将其设为不可见。在我们的代码中,实现了一种简单(甚至是天真)的解决方案。引导组件 AppComponent 在其 template 属性中列出了所有其他组件的选择器(在本例中是 NavComponentHomeComponentDevicesComponent),从而强制创建它们。NavComponent 永久可见,而其他组件则在其 HTML 中根据 Boolean 变量 isShown 提供可见性切换机制。在所有组件中可用的单例类 Communication 提供了 selectComponentSubject: Subject<any> 事件。HomeComponentDevicesComponent 订阅它,而 NavComponent 在按下菜单按钮时激活它。选定组件的名称会发送给所有订阅的组件,它们会相应地更新其可见性状态。相同的机制确保在应用程序启动时出现 HomeComponent,而与 URL 中的相对路径无关。这种行为通常通过 Angular 的 Guards 来实现。因此,对于我们的 Web 应用程序,“没有 Guard”并不意味着“不设防”。

正如前面在讨论后端时所说,Web 应用程序作为 SignalR 客户端与 DashboardApp2 通信。我们的 Communication 类有一个变量 hubConnection: HubConnection 和主题(事件)streamSubject: Subject<any> 来支持流式传输,以及另一个主题 receiveMeasurementSubject: Subject<any> 来通知组件它们应该从 CommunicationmapMeasurements: Map<number, Array<DeviceMeasurement>> 中获取新测量值。下面给出了激活该类的 Communication 类的 start() 方法。

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}`);
    }
}

Web 客户端 DashboardApp2 服务的 URL 在 Communication 类中被硬编码为 BASE_URL

使用代码示例

本文的代码示例包含两个单独的下载:后端和前端。我仅在 Window 10 操作系统本地计算机上测试了该示例,因此我将仅描述在此环境下的操作步骤。请创建目录 Dashboard,然后将后端文件 Backend_src.zip 解压到该目录中。名为 Backend 的服务器端解决方案将出现在 Dashboard 目录中。现在,在 Backend 目录中,您可以在 VS 2017 中打开 DashboardBackend.sln 并构建它。

NuGet 将安装所需的包,特别是 Microsoft.AspNetCore.SignalR (1.0.0-preview1-final)。然后应启动以下项目:DashboardApp2DeviceXAppDevicesSimulator。请在运行前检查相应的 URL。DevicesSimulator 中硬编码了 DeviceXApp 的 URL,并且 DeviceXApp 在其 appsettings.json 配置文件中包含 DashboardApp2 设备中枢的 URL。如上所述,DevicesSimulator 的给定实现使用的是 .NET Framework DLL 开发的,因此可能无法在非 Windows 环境中运行。

注意 1. 有时,VS 2017 可能无法将后端项目作为“多个启动项目”启动,尤其是在最初几次尝试时,会显示一些奇怪的消息,如“无法启动先前选择的调试器。请选择另一个。”但经过几次初始失败后,VS 就能正常工作。

要运行前端,需要进行一些初步安装。首先,应该安装 Node.jsnpm 包管理器。以下资源解释了如何执行此操作:[1][2]。然后应使用以下命令全局安装 angular/cli

npm install -g @angular/cli

可以使用任何集成开发环境 (IDE)。我的选择是 VS Code。这里 [3][4] 可以找到如何使用 VS Code 进行 Angular 开发的方法。完成这些准备后,请打开控制台,转到您的根 Dashboard 目录,然后从中创建一个新的 Angular 项目:

ng new Frontend

(有关详细信息,请参阅 [5])。

注意 2. 所有外部库的安装命令都应从新创建的 Frontend 目录执行。

请安装项目中使用的 Angular Material UI 库:

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

(有关详细信息,请参阅 [6])。

Angular Font Awesome 包通过以下命令安装:

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

(有关详细信息,请参阅 [7])。

然后必须安装 SignalR,运行以下命令:

npm install @aspnet/signalr

(详细信息可在此处找到:[8])。

最后一个要安装的外部库是 Chart.js

npm install chart.js --save

(另请参阅 [9])。

使用的主要软件包版本如下:

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

在安装完所有必需的库后,您必须将 Frontend_src.zip 文件下载到 Frontend/src 目录,并用 Frontend_src.zip 文件的内容替换其中的 app 目录以及 styles.cssfivicon.ico 文件。现在您可以在 VS Code 中打开 Dashboard/Frontend 目录。

要运行示例,请先启动后端。在 VS 2017 中,运行以下项目:DashboardApp2DeviceXAppDevicesSimulator(请参阅上面的注意 1)。您将看到两个显示消息“This is DeviceXApp”和“This is DashboardApp2”的浏览器窗口。DevicesSimulator 在控制台中运行,显示消息“Press any key to start generate measurements...”。在按下某个键后,它将开始生成测量值。要运行前端,请在菜单中打开 View -> Integrated Terminal,然后在其中运行以下命令:

ng serve -o

此命令构建 Web 应用程序,并在 https://:4200 上启动浏览器,连接到监听此 IP 地址的 NG Live Development Server。Web 应用程序实例显示“This is Home page”消息。通过按下 Devices 菜单按钮,您将导航到(或者更准确地说,使可见)相应的页面,该页面看起来与图 1 类似,具有变化的设备状态和移动的历史指标图表。经过一段时间后,为了测试目的,您可以关闭 DevicesSimulator(仍保持两个服务运行),并观察到所有设备的状态变为“NoData”。然后重新启动 DevicesSimulator,按任意键开始生成测量值,并观察到数据流已恢复。

前端项目的调试可以按以下方式进行。激活 ng serve -o 命令后,任何保存的代码更新都会导致 Web 应用程序重新启动。可以通过在代码中编写 debugger; 语句来设置断点。然后打开浏览器中的 Inspect 窗口(对于 Google Chrome,可以通过 右键菜单 -> Inspect 或快捷键 Ctrl-Shift-I 实现)。一旦达到该行,浏览器将停止并在 Source 选项卡中显示断点。从那里,您可以在浏览器中继续进行调试。如果 Inspect 窗口未打开,debugger; 语句将不起作用。要使用 VS Code 进行调试,应安装附加组件。特别是对于 Google Chrome 浏览器,可以使用 Debugger for Chrome(有关详细信息,请参阅 [10])。

注意 3. 命令 ng serve -o 也可以在不使用 VS Code 或其他 IDE 的情况下,从 Dashboard/Frontend 目录中的命令控制台中激活,效果相同。如果选择此方法,则开发人员可以使用任何可用编辑器和 debugger; 语句技术在浏览器中进行调试,从而完全在 IDE 之外处理 Web 应用程序。

结论

这项工作提供了一种结合 ASP.NET Core、SignalR、Angular 和 Chart.js 的概念,用于实现响应式近乎实时的数据采集和呈现。它绝不打算成为一个功能齐全的产品。因此,Web 应用程序的一些重要部分,如身份验证、登录等,以及部署,都没有在此讨论——有大量的出版物对此进行了全面描述。为了进一步开发,实现使用 TLS/SSL 协议进行通信以确保数据传输安全将是一个不错的选择。应检查是否有能力为大量设备提供图表。在非 Windows 操作系统上测试 DeviceXAppDashboardApp2 项目将很有用。

使用 ASP.NET Core 2.0、SignalR、Angular 5 和 Chart.js 构建的简单仪表板 - CodeProject - 代码之家
© . All rights reserved.