DevExpress Reporting是.NET Framework下功能完善的報表平臺,它附帶了易於使用的Visual Studio報表設計器和豐富的報表控件集,包括數據透視表、圖表,因此您可以構建無與倫比、信息清晰的報表。
爲什麼選擇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。結果將顯示如下: