界面组件DevExpress中文教程 - 如何在Node.js应用中创建报表? DevExpress Reporting是.NET Framework下功能完善的报表平台它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集包括数据透视表、图表因此您可以构建无与伦比、信息清晰的报表。DevExpress新旧版本帮助文档下载欢迎进QQ qun获取169725316为什么选择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-exporterProgram.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函数导入WebAssemblyimport { 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 buildnode main.mjsHello, World! Greetings from node version: v18.12.1Hello, Console!通过指定配置设置创建DevExpress报表对于未安装DevExpress的macOS/Linux或Windows操作系统请执行以下操作创建一个nuget.config文件dotnet new nugetconfig删除nuget.config文件中的“clear /”更新add keynuget valuehttps://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和SimplifiedUriJsonSourceusing 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 buildcd resultnode main.mjs应用程序在结果目录中创建一个新的result.pdf文件。使用DevExpress Web文档查看器和报表设计器按照readme文件中列出的步骤运行后端和客户端应用程序并将浏览器指向客户端应用程序中指定的URL。结果将显示如下