界面組件DevExpress中文教程 - 如何在Node.js應用中創建報表?

DevExpress Reporting是.NET Framework下功能完善的報表平臺,它附帶了易於使用的Visual Studio報表設計器和豐富的報表控件集,包括數據透視表、圖表,因此您可以構建無與倫比、信息清晰的報表。

獲取DevExpress Reporting最新正式版下載

爲什麼選擇Node.js和WebAssembly?

自定義DevExpress報表(在後端應用程序中)可能會給剛接觸 .NET的前端Web開發人員帶來獨特的挑戰,在本文中我們將向您展示.NET和WebAssembly集成是如何幫助解決這些挑戰,並增強整體應用程序的安全性和性能的。

傳統的報表開發/定製方法需要 .NET和相關語言的專業知識,但WebAssembly消除了這個困難。通過在WASM環境中運行.NET應用程序,開發人員無需深入瞭解.NET就可以將交互式報表集成到Node.js應用程序中。

這種集成的第二個好處與安全性有關,WebAssembly在隔離的環境中執行代碼。因此開發人員可以完全控制磁盤、網絡和其他關鍵資源,這種隔離的執行環境可以降低與未授權訪問相關的風險。

Microsoft在最近的.NET更新中一直致力於 .NET 和WebAssembly的集成,在.NET 7中,Micrsooft引入了CLI模板(如wasmconsole和wasmbrowser),並允許開發人員創建在承載.NET運行時的沙盒WebAssembly環境中運行的控制檯和web應用程序。

隨着.NET 8的發佈,應用程序代碼在編譯時直接轉換爲WebAssembly,這一變化帶來了與性能相關的顯著改進,其特點是延遲減少、用戶界面響應更快。

如果您是一個剛接觸.NET的前端Web開發人員,並且對這篇內容感興趣,建議創建一個應用程序,允許創建DevExpress報表並將其導出爲PDF文件。

開始前

  • 安裝 .NET 8 SDK
  • 安裝最新的CLI模板:

>dotnet new install Microsoft.NET.Runtime.WebAssembly.Templates::8.0.3

  • 安裝wasm-tools工作負載:

>dotnet workload install wasm-tools

創建一個簡單的Wasmconsole應用

運行以下命令創建一個示例wasm-export應用:

>dotnet new wasmconsole -o wasm-exporter

當示例項目準備好後,導航到項目文件夾:

>cd wasm-exporter

Program.cs文件包含以下代碼:

using System;
using System.Runtime.InteropServices.JavaScript;

Console.WriteLine("Hello, Console!");

return 0;

public partial class MyClass
{
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from node version: {GetNodeVersion()}";
return text;
}

[JSImport("node.process.version", "main.mjs")]
internal static partial string GetNodeVersion();
}

如您所見,JavaScript導入Greeting .NET函數,而.NET本身導入一個函數,該函數顯示當前安裝的Node.js版本。

反過來,代碼在main.mjs文件加載.NET運行時並將JavaScript函數導入WebAssembly:

import { dotnet } from './_framework/dotnet.js'

const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withDiagnosticTracing(false)
.create();

setModuleImports('main.mjs', {
node: {
process: {
version: () => globalThis.process.version
}
}
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);

await dotnet.run();

一旦您使用了dotnet build命令構建了這個應用程序,運行它的方式與您通常運行node.js應用程序的方式相同,來查看兩個函數的執行結果:

>dotnet build
>node main.mjs
Hello, World! Greetings from node version: v18.12.1
Hello, Console!

通過指定配置設置創建DevExpress報表

對於未安裝DevExpress的macOS/Linux或Windows操作系統,請執行以下操作:

  • 創建一個nuget.config文件:

dotnet new nugetconfig

  • 刪除nuget.config文件中的“clear /”
  • 更新add key="nuget" value="https://api.nuget.org/v3/index.json" ,並將nuget鍵替換爲自定義提要名稱,並將該值替換爲從DevExpress NuGet Gallery頁面獲得的DevExpress NuGet Feed URL。

完成後,安裝在WebAssembly應用程序中創建文檔所需的NuGet包:

dotnet add package Newtonsoft.Json
dotnet add package DevExpress.Drawing.Skia --version 23.2.*-*
dotnet add package DevExpress.Reporting.Core --version 23.2.*-*
dotnet add package SkiaSharp.HarfBuzz --version 2.88.7
dotnet add package SkiaSharp.NativeAssets.WebAssembly --version 2.88.7
dotnet add package HarfBuzzSharp.NativeAssets.WebAssembly --version 2.8.2.4
dotnet add package SkiaSharp.Views.Blazor --version 2.88.7

在項目配置文件(wasm- exporters .csproj)中添加一個本地SkiaSharp依賴項:

<ItemGroup>
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\*.a" />
</ItemGroup>

指定生成的可執行文件和庫的路徑:

<wasmappdir>./result</wasmappdir>

在這一點上,我們完成了準備工作,並準備開始編碼。

我們的應用程序由兩個部分組成:一個基於.NET服務器的應用程序編譯成程序集,一個JavaScript客戶端應用程序創建並配置.NET運行時環境,以便在WASM中運行該程序集。

.NET解決方案

在您喜歡的代碼編輯器中打開文件夾,在Program.cs代碼單元中實現以下類:ReportExporter、JsonSourceCustomizationService和SimplifiedUriJsonSource:

using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
using DevExpress.Data;
using DevExpress.DataAccess.Json;
using DevExpress.XtraPrinting;
using DevExpress.XtraReports.UI;

return 0;

public partial class ReportExporter {
[JSExport]
internal static async Task ExportToPdfAsync(JSObject exportModel, JSObject result) {
using var report = new XtraReport();
((IServiceContainer)report).AddService(typeof(IJsonSourceCustomizationService), new JsonSourceCustomizationService());

using var reportStream = new MemoryStream(exportModel.GetPropertyAsByteArray("reportXml"));
report.LoadLayoutFromXml(reportStream, true);

PdfExportOptions pdfOptions = report.ExportOptions.Pdf;
if(exportModel.HasProperty("exportOptions")) {
SimplifiedFillExportOptions(pdfOptions, exportModel.GetPropertyAsJSObject("exportOptions"));
}

using var resultStream = new MemoryStream();
await report.ExportToPdfAsync(resultStream, pdfOptions);
result.SetProperty("pdf", resultStream.ToArray());
resultStream.Close();
}

static void SimplifiedFillExportOptions(object exportOptions, JSObject jsExportOptions) {
PropertyInfo[] propInfos = exportOptions.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach(PropertyInfo pi in propInfos) {
if(!jsExportOptions.HasProperty(pi.Name))
continue;

if(pi.PropertyType == typeof(bool)) {
pi.SetValue(exportOptions, jsExportOptions.GetPropertyAsBoolean(pi.Name));

} else if(pi.PropertyType == typeof(string)) {
pi.SetValue(exportOptions, jsExportOptions.GetPropertyAsString(pi.Name));

} else if(pi.PropertyType.IsEnum) {
string val = jsExportOptions.GetPropertyAsString(pi.Name);
if(Enum.IsDefined(pi.PropertyType, val)) {
pi.SetValue(exportOptions, Enum.Parse(pi.PropertyType, val));
}

} else if(pi.PropertyType.IsClass) {
SimplifiedFillExportOptions(pi.GetValue(exportOptions), jsExportOptions.GetPropertyAsJSObject(pi.Name));
}
}
}
}
public class JsonSourceCustomizationService : IJsonSourceCustomizationService {
public JsonSourceBase CustomizeJsonSource(JsonDataSource jsonDataSource) {
return jsonDataSource.JsonSource is UriJsonSource uriJsonSource ? new SimplifiedUriJsonSource(uriJsonSource.Uri) : jsonDataSource.JsonSource;
}
}
public partial class SimplifiedUriJsonSource : UriJsonSource {
public SimplifiedUriJsonSource(Uri uri) : base(uri) { }
public override Task GetJsonStringAsync(IEnumerable sourceParameters, CancellationToken cancellationToken) {
return GetJsonData(Uri.ToString());
}
[JSImport("getJsonData", "main.mjs")]
internal static partial Task GetJsonData(string url);
}

ReportExporter

該類實現ExportToPdfAsync方法並將其導出到JavaScript模塊,該方法創建一個XtraReport實例,將JsonSourceCustomizationService自定義服務添加到報表對象模型,並將可選的導出選項從javascript對象映射到本地.NET對象,使用XtraReport.ExportToPdfAsync方法將報表導出爲PDF。

JsonSourceCustomizationService

這個服務取代了JsonDataSource.JsonSource值,使用滿足Blazor限制的自定義對象。這是因爲WebAssembly不允許HTTP請求,而報表模型可能會引用帶有URI的JSON源。

SimplifiedUriJsonSource

該類是UriJsonSource類的後代,並將HTTP請求重定向到應用程序的javascript段。

JavaScript實現

main.mjs文件是應用程序的核心JS段,將其內容替換爲以下代碼:

// Import necessary modules.
import { dotnet } from '._framework/dotnet.js';
import fs from 'fs';
import { get as httpGet } from 'http';
import { get as httpsGet } from 'https';
import url from 'url'

// Configure .NET runtime for WASM execution.
const { setModuleImports, getAssemblyExports, getConfig } = await dotnet
.withDiagnosticTracing(false)
.create();
setModuleImports('main.mjs', { getJsonData });

// Retrieve the exported methods from the WASM part.
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

// Prepare the report model and related options.
const repx = fs.readFileSync('../reports/report1.repx');
const model = {
reportXml: repx,
exportOptions: {
DocumentOptions: {
Application: "WASM",
Subject: "wasm integration"
},
PdfUACompatibility: "PdfUA1"
}
}

// Export the report to PDF.
const result = {};
await exports.ReportExporter.ExportToPdfAsync(model, result);
const buffer = Buffer.from(result.pdf);
fs.writeFileSync('result.pdf', buffer);

// Define a method to fetch JSON data from a given URL.
function getJsonData(jsonUrl) {
return new Promise((resolve) => {
const fetchMethod = url.parse(jsonUrl).protocol === 'https:' ? httpsGet : httpGet;
fetchMethod(jsonUrl, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', err => resolve(''));
});
}

// Initiate the .NET runtime.
await dotnet.run();

該文件中的代碼:

  • 在WASM中配置 .NET 運行時。
  • 導入getJsonData()函數來從URL檢索JSON文件。
  • 調用並執行ExportToPdfAsync方法來生成PDF文檔。
  • 使用本地Node.js函數保存生成的PDF文件。

查看結果

要運行應用程序,首先構建.NET應用程序,導航到result文件夾,然後運行JavaScript應用程序:

>dotnet build
>cd result
>node main.mjs

應用程序在結果目錄中創建一個新的result.pdf文件。

使用DevExpress Web文檔查看器和報表設計器

按照readme文件中列出的步驟,運行後端和客戶端應用程序,並將瀏覽器指向客戶端應用程序中指定的URL。結果將顯示如下:

DevExpress XAF (WinForms UI)實用案例圖

更多DevExpress線上公開課、中文教程資訊請上中文網獲取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章