界面组件DevExpress中文教程 - 如何在Node.js应用中创建报表?

news2024/11/24 3:12:12

DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。

获取DevExpress Reporting最新正式版下载(Q技术交流:532598169)

为什么选择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线上公开课、中文教程资讯请上中文网获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1635040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电商日志项目(一)

电商日志项目 一、项目体系架构设计1. 项目系统架构2. 项目数据流程二、环境搭建1. NginxLog文件服务1.1. 上传,解压1.2. 编译安装1.3. 启动验证2. Flume-ng2.1. 上传解压2.2. 修改配置文件2.3. 修改环境变量2.4. 验证3. Sqoop3.1. 上传解压3.2. 配置环境变量3.3. 修改配置文件…

「玻尔曾孙」领衔!超辐射原子,重塑全球精准测时——

超辐射原子能够帮助我们以前所未有的精度测量时间。在哥本哈根大学最近的一项研究中&#xff0c;研究人员开发了一种新的测量时间间隔&#xff08;秒&#xff09;的方法&#xff0c;这种方法克服了目前最先进原子钟面临的一些限制。 这一成就有望在多个领域产生深远影响&#x…

el-date-picker 禁用时分秒选择(包括禁用下拉框展示)

2024.04.26今天我学习了对el-date-picker进行禁用时分秒&#xff0c; 在使用el-date-picker组件的时候&#xff0c;我们有可能遇到需要把时分秒的时间固定&#xff0c;然后并且不能让他修改&#xff1a; 1714120999296 比如右上角的这个时间&#xff0c;我们要给它固定是‘08:…

Flask模版详解

Flask模版详解 概述Jinja2模板引擎渲染模版的步骤变量控制结构自定义错误页面链接静态文件 概述 模板是一个包含响应文本的文件&#xff0c;其中包含用占位变量表示的动态部分&#xff0c;其具体值只在请求的上下文中才能知道。使用真实值替换变量&#xff0c;再返回最终得到的…

Android4.4真机移植过程笔记(三)

如果文章字体看得不是很清楚&#xff0c;大家可以下载pdf文档查看&#xff0c;文档已上传&#xff5e;oo&#xff5e; 7、安装加密APK 需要修改文件如下&#xff1a; 相对Android4.2改动还是蛮大的&#xff0c;有些文件连路径都变了: //Android4.2 1、frameworks/native/libs…

如何运用结构化思维来规划个人发展

结构化思维不仅在工作中非常有用&#xff0c;在日常生活中同样可以发挥巨大作用。无论是解决家庭琐事、规划个人发展&#xff0c;还是做出重要决策&#xff0c;结构化思维都能帮助我们更有条理地思考和行动。 一、解决生活中的问题 生活中总会遇到各种各样的问题&#xff0…

Unity+Shader入门精要-1. 入门shader

今天开始正式整合学习的shader内容。 Simple Shader 主要介绍了大概的shader格式。 Shader "Unity Sgaders Book/Chapter 5/Simple Shader" //shader名 {Properties{//声明color类型的属性_Color("Color Tint", Color) (1.0,1.0,1.0,1.0)}SubShader{Pa…

【SpringBoot】数据脱敏

文章目录 什么是数据脱敏JsonSerialize自定义Jackson注解定制脱敏策略定制JSON序列化实现脱敏工具类 定义Person类&#xff0c;对其数据脱敏模拟接口测试总结 什么是数据脱敏 数据脱敏&#xff0c;也称为数据的去隐私化或数据变形&#xff0c;是一种技术手段&#xff0c;用于对…

tcp inflight 守恒算法的自动收敛

inflight 守恒算法看起来只描述理想情况&#xff0c;现实很难满足&#xff0c;是这样吗&#xff1f; 从 reno 到 bbr&#xff0c;无论哪个算法都在描述理想情况&#xff0c;以 reno 和 bbr 两个极端为例&#xff0c;它们分别描述两种理想管道&#xff0c;reno 将 buffer 从恰好…

【C++庖丁解牛】C++11---新的类的功能 | 可变参数模板

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1.新的类功能1.1 默认成员…

浏览器预加载器如何使页面加载速度更快

预加载器&#xff08;也称为推测或前瞻预解析器&#xff09;可能是浏览器性能有史以来最大的改进。 那么什么是预加载器以及它如何提高性能呢&#xff1f; 浏览器如何加载网页 网页充满了依赖关系——在下载相关的CSS之前页面无法开始渲染&#xff0c;然后当遇到脚本时HTML解…

零基础该如何自学linux运维?

零基础该如何自学linux运维&#xff1f;以下是建议帮助你入门Linux运维的一些建议。 一、自学建议&#xff1a; 理解基础概念&#xff1a;首先&#xff0c;你需要对Linux操作系统的基本概念有所了解&#xff0c;包括文件系统、用户权限、进程管理等。安装Linux系统&#xff1…

若依前后端部署系统--详细附图

一、后端部署 1、在ruoyi项目的Maven中的生命周期下双击package.bat打包Web工程&#xff0c;生成jar包文件。 提示打包成功 2、多模块版本会生成在ruoyi/ruoyi-admin模块下target文件夹,我们打开目录ruoyi-admin/taget&#xff0c;打开cmd&#xff0c;运行java -jar jar包名称…

【C语言进阶】程序编译中的预处理操作

&#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL.. &#x1f4da;以后会将数据结构收录为一个系列&#xff0c;敬请期待 ● 本期内容讲解C语言中程序预处理要做的事情 目录 1.1 预处理符号 1.2 #define 1.2.1 #define定义标识…

【Docker学习】docker stats

命令&#xff1a; docker container stats 描述&#xff1a; 显示容器资源使用的状态&#xff08;实时&#xff09; 用法&#xff1a; docker container stats [OPTIONS] [CONTAINER...] 别名&#xff1a; docker stats(docker的一些命令可以简写&#xff0c;docker stats就等同…

Odoo17开发环境搭建

1.先下载godoo17_20240227_02.zip压缩包&#xff0c;里面包含了项目用到的所有的插件了&#xff0c;直接使用这个包即可。 下载地址&#xff1a;https://download.csdn.net/download/java173842219/89242257 2.解压该压缩包&#xff0c;目录如下&#xff1a; 3.下载pycharm并…

如何保证Redis双写一致性?

目录 数据不一致问题 数据库和缓存不一致解决方案 1. 先更新缓存&#xff0c;再更新数据 该方案数据不一致的原因 2. 先更新数据库&#xff0c;再更新缓存 3. 先删除缓存&#xff0c;再更新数据库 延时双删 4. 先更新数据库&#xff0c;再删除缓存 该方案数据不一致的…

声明式事务(@Transactional)使用时需要注意的坑

前言 上两篇文章已经详细分析了申明式事务的实现原理&#xff0c;知道了底层原理之后&#xff0c;现在就可以开始使用申明式事务去简化我们的代码了。但是在使用Transactional注解的时候也会经常遇到一些问题&#xff0c;有些问题不仔细测试观察的话还不容易发现&#xff0c;比…

Windows vbs脚本定时给焦点窗口发送消息

直接上脚本代码,你们可以自己看着改 MsgInputbox("message1") Msg1Inputbox("message2") numInputbox("number")a1 bnumset wshshellCreateObject("wscript.shell") 创建Windows的shell对象打开shell窗口 wscript.sleep 5000for ia t…

vue本地调试devtools

一、谷歌浏览器加载扩展程序 二、把解压的压缩包添加即可&#xff0c;重启浏览器 三、启动前端本地项目&#xff0c;即可看到Vue小图标