Skip to main content

Blazor 站点中的表格

Blazor 是一个使用 C#、HTML、JS 和 CSS 构建用户界面的框架。

¥Blazor is a framework for building user interfaces using C#, HTML, JS and CSS.

SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。

¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.

此演示使用 Blazor 和 SheetJS 来处理和生成电子表格。我们将探讨如何在 Razor 组件中加载 SheetJS,并比较常见的状态模型和数据流策略。

¥This demo uses Blazor and SheetJS to process and generate spreadsheets. We'll explore how to load SheetJS in Razor components and compare common state models and data flow strategies.

Blazor 支持被视为实验性的。

优秀的开源软件会随着用户测试和报告而不断成长。任何问题都应报告给 Blazor 项目以进行进一步诊断。

¥Great open source software grows with user tests and reports. Any issues should be reported to the Blazor project for further diagnosis.

遥测

dotnet 命令嵌入遥测技术。

¥The dotnet command embeds telemetry.

DOTNET_CLI_TELEMETRY_OPTOUT 环境变量应设置为 1

¥The DOTNET_CLI_TELEMETRY_OPTOUT environment variable should be set to 1.

"平台配置" 包含有关在支持的平台上设置环境变量的说明。

¥"Platform Configuration" includes instructions for setting the environment variable on supported platforms.

集成详情

¥Integration Details

安装

¥Installation

可以在加载页面时加载 SheetJS 库,也可以在使用库功能时导入 SheetJS 库。

¥The SheetJS library can be loaded when the page is loaded or imported whenever the library functionality is used.

独立脚本

¥Standalone Script

SheetJS 独立脚本 可以在根 HTML 页面(通常为 wwwroot/index.html)中加载:

¥The SheetJS Standalone scripts can be loaded in the root HTML page (typically wwwroot/index.html):

<!-- use version 0.20.3 -->
<script lang="javascript" src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>

ECMAScript 模块

¥ECMAScript Module

SheetJS ECMAScript 模块脚本可以从函数动态导入。这可确保仅在必要时加载库。以下 JS 示例加载库并返回解析为版本字符串的 Promise:

¥The SheetJS ECMAScript module script can be dynamically imported from functions. This ensures the library is only loaded when necessary. The following JS example loads the library and returns a Promise that resolves to the version string:

async function sheetjs_version(id) {
/* dynamically import the script in the event listener */
const XLSX = await import("https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs");

/* use the library */
return XLSX.version;
}

从 C# 调用 JS

¥Calling JS from C#

Razor 元素中事件的回调会调用 C# 方法。C# 方法可以使用 Blazor API 来调用在浏览器全局范围内可见的 JS 方法。

¥Callbacks for events in Razor elements invoke C# methods. The C# methods can use Blazor APIs to invoke JS methods that are visible in the browser global scope.

设置

¥Setup

从 Blazor 调用 JS 函数的主要机制是 IJSRuntime[^1]。它应该注入到相关 Razor 组件脚本的顶部:

¥The primary mechanism for invoking JS functions from Blazor is IJSRuntime[^1]. It should be injected at the top of relevant Razor component scripts:

Injecting IJSRuntime
@inject IJSRuntime JS

触发并忘记

¥Fire and Forget

使用 SheetJS writeFile 方法 [^2] 导出文件时,浏览器 API 不提供成功或错误反馈。因此,此演示使用 InvokeVoidAsync 静态方法 [^3] 调用函数。

¥When exporting a file with the SheetJS writeFile method[^2], browser APIs do not provide success or error feedback. As a result, this demo invokes functions using the InvokeVoidAsync static method[^3].

以下 C# 方法将在浏览器中调用 export_method 方法:

¥The following C# method will invoke the export_method method in the browser:

Invoking JS functions from C#
private async Task ExportDataset() {
await JS.InvokeVoidAsync("export_method", data);
}

JS 方法必须在全局范围内定义!

¥The JS methods must be defined in the global scope!

在此演示中,脚本被添加到根 HTML 文件的 HEAD 块中:

¥In this demo, the script is added to the HEAD block of the root HTML file:

wwwroot/index.html


<head>
<!-- ... meta / title / base / link tags -->
<link href="SheetJSBlazorWasm.styles.css" rel="stylesheet" />

<!-- script with `export_method` is in the HEAD block -->
<script>
/* In a normal script tag, Blazor JS can call this method */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}
</script>
</head>


使用 <script type="module"> 时,默认情况下 Blazor 看不到顶层函数定义。它们必须附加到 globalThis

¥When using <script type="module">, top-level function definitions are not visible to Blazor by default. They must be attached to globalThis:

Attaching methods to globalThis
<script type="module">
/* Using `type="module"`, Blazor JS cannot see this function definition */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}

/* Once attached to `globalThis`, Blazor JS can call this method */
globalThis.export_method = export_method;
</script>

Blazor 回调

¥Blazor Callbacks

方法通常使用 @onclick 绑定到 Razor 模板中的按钮。单击以下按钮时,Blazor 将调用 ExportDataset

¥Methods are commonly bound to buttons in the Razor template using @onclick. When the following button is clicked, Blazor will invoke ExportDataset:

Binding callback to a HTML button
<button @onclick="ExportDataset">Export Dataset</button>

Blazor 中的状态

¥State in Blazor

示例 总统表 有一个标题行,其中包含 "名称" 和 "索引" 列。

¥The example presidents sheet has one header row with "Name" and "Index" columns.

pres.xlsx data

C# 表示

¥C# Representation

单行的自然 C# 表示是类对象:

¥The natural C# representation of a single row is a class object:

President class
public class President {
public string Name { get; set; }
public int Index { get; set; }
}

var PrezClinton = new President() { Name = "Bill Clinton", Index = 42 };

整个数据集通常存储在类对象数组中:

¥The entire dataset is typically stored in an array of class objects:

President dataset
private President[] data;

数据交换

¥Data Interchange

InvokeVoidAsync 可以将数据从 C# 状态传递到 JS 函数:

¥InvokeVoidAsync can pass data from the C# state to a JS function:

    await JS.InvokeVoidAsync("export_method", data);

数据集中的每一行都将作为单独的参数传递给 JavaScript 方法,因此 JavaScript 代码应该收集参数:

¥Each row in the dataset will be passed as a separate argument to the JavaScript method, so the JavaScript code should collect the arguments:

Collecting rows in a JS callback
/* NOTE: blazor spreads the C# array, so the ... spread syntax is required */
async function export_method(...rows) {
/* display the array of objects */
console.log(rows);
}

每一行都是一个简单的 JavaScript 对象。

¥Each row is a simple JavaScript object.

Blazor 自动展开数组。每一行都作为单独的参数传递给 JavaScript 方法。

¥Blazor automatically spreads arrays. Each row is passed as a separate argument to the JavaScript method.

示例方法使用 JavaScript 扩展语法来收集参数。

¥The example method uses the JavaScript spread syntax to collect the arguments.

导出数据

¥Exporting Data

使用收集的对象数组,SheetJS json_to_sheet 方法 [^4] 将从数据集生成 SheetJS 工作表 [^5]。使用 book_new 方法 [^7] 创建工作簿 [^6] 对象后,使用 writeFile [^2] 写入文件:

¥With the collected array of objects, the SheetJS json_to_sheet method[^4] will generate a SheetJS worksheet[^5] from the dataset. After creating a workbook[^6] object with the book_new method[^7], the file is written with writeFile[^2]:

JS Callback for exporting datasets
/* NOTE: blazor spreads the C# array, so the spread is required */
async function export_method(...rows) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs");
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new(ws, "Data");
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
}

HTML 表格

¥HTML Tables

显示数据集时,Razor 组件通常会生成 HTML 表:

¥When displaying datasets, Razor components typically generate HTML tables:

Razor template from official starter


<table class="table" id="weather-table">
<thead>
<tr><th>日期</th><th>临时。(C)</th> <th>临时。(F)</th><th>摘要</th></tr>
</thead>
<tbody>
@foreach (var forecast in forecasts) {
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr> }
</tbody>
</table>


如果它有 id,前端的 JS 代码可以使用 document.getElementById DOM 方法找到表元素。可以使用 table_to_book 方法 [^8] 生成 SheetJS 工作簿对象并使用 writeFile[^2] 导出:

¥If it has an id, JS code on the frontend can find the table element using the document.getElementById DOM method. A SheetJS workbook object can be generated using the table_to_book method[^8] and exported with writeFile[^2]:

JS Callback for exporting HTML TABLE elements
async function export_method() {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs");
const wb = XLSX.utils.table_to_book(document.getElementById("weather-table"));
XLSX.writeFile(wb, "SheetJSBlazor.xlsx");
}

此方法使用文档中已存在的数据,因此不会从 C# 向 JavaScript 传递任何额外数据。

¥This approach uses data that already exists in the document, so no additional data is passed from C# to JavaScript.

完整演示

¥Complete Demo

Blazor + WASM 入门应用包含一个 "天气" 组件,该组件显示来自 C# 管理的数据集的数据。此演示使用 SheetJS 以两种方式导出数据:

¥The Blazor + WASM starter app includes a "Weather" component that displays data from a C#-managed dataset. This demo uses SheetJS to export data in two ways:

  • "导出数据集" 会将行对象从底层 C# 数据存储发送到前端。SheetJS json_to_sheet 方法 [^4] 构建工作表。

    ¥"Export Dataset" will send row objects from the underlying C# data store to the frontend. The SheetJS json_to_sheet method[^4] builds the worksheet.

  • "导出 HTML 表" 将使用 SheetJS table_to_book 方法 [^8] 抓取表格。不会将任何额外数据发送到前端。

    ¥"Export HTML Table" will scrape the table using the SheetJS table_to_book method[^8]. No extra data will be sent to the frontend.

测试部署

该演示在以下部署中进行了测试:

¥This demo was tested in the following deployments:

架构日期
darwin-arm2024-10-15

平台配置

¥Platform Configuration

  1. DOTNET_CLI_TELEMETRY_OPTOUT 环境变量设置为 1

    ¥Set the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1.

How to disable telemetry (click to hide)

将以下行添加到 .profile.bashrc.zshrc

¥Add the following line to .profile, .bashrc and .zshrc:

(add to .profile , .bashrc , and .zshrc)
export DOTNET_CLI_TELEMETRY_OPTOUT=1

关闭并重新启动终端以加载更改。

¥Close and restart the Terminal to load the changes.

  1. 安装.NET

    ¥Install .NET

Installation Notes (click to show)

For macOS x64 and ARM64, install the dotnet-sdk Cask with Homebrew:

brew install --cask dotnet-sdk

For Steam Deck Holo and other Arch Linux x64 distributions, the dotnet-sdk and dotnet-runtime packages should be installed using pacman:

sudo pacman -Syu dotnet-sdk dotnet-runtime

https://dotnet.microsoft.com/en-us/download/dotnet/6.0 is the official source for Windows and ARM64 Linux versions.

  1. 在 macOS 中打开新的终端窗口或在 Windows 中打开 PowerShell 窗口。

    ¥Open a new Terminal window in macOS or PowerShell window in Windows.

应用创建

¥App Creation

  1. 创建一个新的 blazorwasm 应用:

    ¥Create a new blazorwasm app:

dotnet new blazorwasm -o SheetJSBlazorWasm
cd SheetJSBlazorWasm
dotnet run

当 Blazor 服务运行时,终端将显示一个 URL:

¥When the Blazor service runs, the terminal will display a URL:

info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:6969
  1. 在新的浏览器窗口中,打开步骤 3 中显示的 URL。

    ¥In a new browser window, open the displayed URL from Step 3.

  2. 单击 "天气" 链接并确认页面包含数据表。

    ¥Click the "Weather" link and confirm the page includes a data table.

  3. 停止服务器(在终端窗口中按 CTRL+C)。

    ¥Stop the server (press CTRL+C in the terminal window).

SheetJS 集成

¥SheetJS Integration

  1. HEAD 块中向 wwwroot/index.html 添加以下脚本标记:

    ¥Add the following script tag to wwwroot/index.html in the HEAD block:

wwwroot/index.html (add within the HEAD block)
<script>
/* NOTE: blazor spreads the C# array, so the spread is required */
async function export_dataset(...rows) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs");
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new(ws, "Data");
XLSX.writeFile(wb, "SheetJSBlazorDataset.xlsx");
}

async function export_html(id) {
const XLSX = await import("https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs");
const wb = XLSX.utils.table_to_book(document.getElementById(id));
XLSX.writeFile(wb, "SheetJSBlazorHTML.xlsx");
}
</script>
  1. Pages/Weather.razor 顶部附近注入 IJSRuntime 依赖:

    ¥Inject the IJSRuntime dependency near the top of Pages/Weather.razor:

Pages/Weather.razor (add highlighted lines)
@page "/weather"
@inject HttpClient Http
@inject IJSRuntime JS
  1. Pages/Weather.razor 中向 TABLE 元素添加 ID:

    ¥Add an ID to the TABLE element in Pages/Weather.razor:

Pages/Weather.razor (add id to TABLE element)
{
<table class="table" id="weather-table">
<thead>
<tr>
  1. Pages/Weather.razor 中向 @code 部分添加回调:

    ¥Add callbacks to the @code section in Pages/Weather.razor:

Pages/Weather.razor (add within the @code section)
    private async Task ExportDataset()
{
await JS.InvokeVoidAsync("export_dataset", forecasts);
}

private async Task ExportHTML()
{
await JS.InvokeVoidAsync("export_html", "weather-table");
}
  1. Pages/Weather.razor 中向模板添加导出按钮:

    ¥Add Export buttons to the template in Pages/Weather.razor:

Pages/Weather.razor (add highlighted lines)
<p>This component demonstrates fetching data from the server.</p>

<button @onclick="ExportDataset">Export Dataset</button>
<button @onclick="ExportHTML">Export HTML TABLE</button>

测试

¥Testing

  1. 再次启动 dotnet 进程:

    ¥Launch the dotnet process again:

dotnet run

当 Blazor 服务运行时,终端将显示一个 URL:

¥When the Blazor service runs, the terminal will display a URL:

info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:6969
  1. 在新的浏览器窗口中,打开步骤 12 中显示的 URL。

    ¥In a new browser window, open the displayed URL from Step 12.

  2. 单击 "天气" 链接。页面应与以下屏幕截图相匹配:

    ¥Click the "Weather" link. The page should match the following screenshot:

SheetJSBlazorWasm with Exports

  1. 单击 "导出数据集" 按钮并将生成的文件保存到 SheetJSBlazorDataset.xlsx。在电子表格编辑器中打开文件并确认数据与表格匹配。由于底层数据集使用不同的标签,列标签将有所不同。

    ¥Click the "Export Dataset" button and save the generated file to SheetJSBlazorDataset.xlsx. Open the file in a spreadsheet editor and confirm the data matches the table. The column labels will differ since the underlying dataset uses different labels.

SheetJSBlazorDataset.xlsx

  1. 单击 "导出 HTML 表" 按钮并将生成的文件保存到 SheetJSBlazorHTML.xlsx。在电子表格编辑器中打开文件并确认数据与表格匹配。列标签将与 HTML 表匹配。

    ¥Click the "Export HTML TABLE" button and save the generated file to SheetJSBlazorHTML.xlsx. Open the file in a spreadsheet editor and confirm the data matches the table. The column labels will match the HTML table.

SheetJSBlazorHTML.xlsx

有点奇怪的是,官方 dotnet Blazor 示例数据集将 1 C-13 C 标记为 "freezing",但将 -2 C 标记为 "chilly"。按理说,-2 C 也应该冻结。

¥It is somewhat curious that the official dotnet Blazor sample dataset marks 1 C and -13 C as "freezing" but marks -2 C as "chilly". It stands to reason that -2 C should also be freezing.

[^1]: 请参阅 dotnet 文档中的 "Microsoft.JSInterop.IJSRuntime"

¥See "Microsoft.JSInterop.IJSRuntime" in the dotnet documentation.

[^2]: 见 writeFile 于 "写入文件"

¥See writeFile in "Writing Files"

[^3]: 请参阅 dotnet 文档中的 "Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync"

¥See "Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync" in the dotnet documentation.

[^4]: 见 json_to_sheet 于 "实用工具"

¥See json_to_sheet in "Utilities"

[^5]: 见 "Sheet 对象"

¥See "Sheet Objects"

[^6]: 见 "工作簿对象"

¥See "Workbook Object"

[^7]: 见 book_new 于 "实用工具"

¥See book_new in "Utilities"

[^8]: 见 "HTML" 实用程序中的 table_to_book

¥See table_to_book in "HTML" Utilities