65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET 和 jQuery 将 HTML 表格数据导出到 CSV 文件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012年1月30日

CPOL

7分钟阅读

viewsIcon

70587

downloadIcon

2128

如何使用内置的浏览器打开/保存功能将数据导出到“逗号分隔值”文件

HtmlToCsv

引言

本文演示了如何将大多数 HTML 表格的内容下载到 **csv** 文件。为实现此目的,我们使用了一个简单的 index.html 页面,该页面利用 jQuery 和 jQuery.post 方法从 Northwind 源表中请求数据。使用 jQuery.append() 将一个 IFrame 添加到 index.html 页面。IFrame 的 src 是 **SaveCSV.aspx** 加上一个包含下载输出文件的 **FileName **的查询字符串。**SaveCSV.aspx.cs** 包含所有服务器端代码,这些代码执行 **Response.Write **以生成浏览器将作为内容类型 "text/csv" 处理的数据。响应数据中还添加了另一个标题标签 **"Content-Disposition"**,以告知浏览器返回的文本是“附件”,应下载到文件中。

背景

本文的主要灵感来源是 Chris Pels 的 asp.net 视频,您可以在此处找到
[我该如何:]导出数据到逗号分隔 (CSV)

与 Pels 的视频不同之处在于,我不喜欢使用 ASP.NET 网格视图控件,而是想探索方法,通过 AJAX 技术启用从常规 html 文件下载数据。我处理基于 GIS 的地图查看器页面,这些页面从许多来源获取数据。由于通常使用 html 表格并通过 AJAX 从各种 Web 服务收集数据来显示,因此添加将数据保存到文件的选项的最简单方法是在已渲染的表格上添加一个锚点标签。我还想避免地图查看器页面的回发,因为它渲染地图图像可能很慢。

本文代码的亮点在于 jQuery map 方法,该方法将表格内容映射到 JSON 对象。我在这里找到了关于如何使用 **map** 方法的良好说明

Dave Ward 的“使用 jQuery 从 HTML 列表和表格中提取数据”

使用代码

该项目设置为“Web 应用程序”项目类型。我也是一名桌面开发者,我讨厌“网站”项目类型。我认为它非常混乱且难以管理。“Web 应用程序”项目是管理项目的正确方式。

Web.config 文件在 **App_Data** 下包含 **NWind1.mdb** 的路径。如果将项目源复制到 "C:\inetpub\wwwroot\TableViewer",则无需更改 **connectionStrings** 设置。否则,请更改您复制项目的位置。

该项目已在 Windows 7 中设置为 .NET 3.5。如果您创建一个新的 Web 应用程序项目并手动添加源文件,您应该能够使其在旧版本上运行。

此项目有两个必需的引用,它们默认未添加:
System.Configuration - 用于 **SelectionHandler.ashx.cs** 中的 **ConfigurationManager.ConnectionStrings **
System.Web.Extensions - 用于 **SaveCSV.aspx.cs** 中的 **System.Web.Script.Serialization **

Web 应用程序以选择要查看的表开始。**SelectionHandler.ashx.cs** 用于从示例数据库获取数据,然后 **Response.Write** 将其写回客户端。然后使用 jQuery 方法使用 AJAX 结果构建 html 表格。大多数人使用“$”代替“jQuery”,但我更喜欢拼写出“jQuery”以提高可读性。对我来说,JavaScript 已经充满了足够的特殊符号。

我在这里不详细介绍第一步,因为本文实际上是关于第二阶段,即在表格标题中添加 IFrame 链接,以便能够将表格内容保存到文件。第一部分实际上只是我用于演示的测试驱动程序的一部分。如果兴趣足够,我可能会写另一篇文章介绍它。

构建结果表格的特殊部分如下所示

var jsonResult = jQuery.parseJSON(result);
var cols = jsonResult.Columns;
var tableName = jQuery("#TableDD option:selected").text();
var queryStr = "FileName=csvFile.csv";

jQuery("#NwindTable thead").append('<tr><th class="header1" colspan="' + 
cols.length + '"><div>' + tableName + 
'<iframe id="PostFrame" src="SaveCSV.aspx?' + queryStr  '" /></div></th></tr>');

具体来说,这个 IFrame 被附加到表格的第一个标题行

'<iframe id="PostFrame" src="SaveCSV.aspx?'+ queryStr + '" />...

**PostFrame** id 使用 **float: right; width: 100px **进行 CSS 样式设置,以便它保留在表格标题行的右侧。

一旦 IFrame src 被添加到 html 页面,服务器端 cs 代码就会执行以处理初始 **Page_Load** 事件。**"onclick"** 属性被添加到 lnkExport **asp:LinkButton** 并设置为运行 index.html 页面中的 Javascript 函数 **DownloadTable()**。因此,因为 **asp:LinkButton** 具有 **runat="server"** 属性,所以当控件被单击时,javascript 函数将在客户端执行,然后整个 **SaveCSV.aspx** 页面将回发以在正常的 **asp.net** 服务器端处理中进行处理。

Javascript 函数 **DownloadTable()** 被设计为一个可重用的函数,应该足够通用,可以用于从几乎任何 html 表格中提取数据。

jQuery 有一个 **map** 方法,它可以很好地将 **thead/tbody** 行的内容映射到 JSON 对象。有关 map 方法工作原理的文章,请参阅上面“背景”部分引用的链接。

以下是将表格数据行映射到 Rows 对象的代码

//assemble comma separated rows in a JSON map
var Rows = jQuery("#NwindTable tbody tr").map(function() {
var cells = "";
  jQuery(this).find('td').each(function() {
    var celValue = jQuery(this).text();
    
    if (cells.length > 0) {
      cells += ',' + celValue.replace(/,/g, ';');
    } 
    else {
      cells += celValue.replace(/,/g, ';');
    }
  });
  return { row: cells };
}).get();

这里的代码检查每一行 (tr) 的 td 单元格并提取文本。
然后将文本字符串中的逗号替换为 ';',并将该行的所有值追加到 **cells var **,每个值之间用逗号分隔。最后,将 **cells** 作为 **row** 对象添加到 **Rows **JSON 对象中。

Columns 对象以类似的方式填充,并支持多个 thead 行。由于我的列名都不包含逗号,因此我不需要对 **th** 单元格使用 Javascript replace 方法。

最后一段 Javascript 代码将 ColumnsRows 合并到 tableCSV 对象中。然后,IFrame(在 **index.html** 页面中标记为 id="PostFrame")的 src 页面(**SaveCSV.aspx**)中隐藏的 <input> 标签(id="TableData")获取 tableCSV 对象的 JSON.stringify() 版本的 [值]。

以下是完成所有这些工作的两行代码

var tableCSV = { "Columns": Columns, "Rows": Rows };

//Save data to Iframe page SaveCSV.aspx before postback to page is done.
//The runat='server' in asp:LinkButton does the postback needed for download

jQuery("#PostFrame").contents().find("#TableData")[0].value = JSON.stringify(tableCSV);

客户端渲染和 Javascript 已完成,最后在客户端发生的是 SaveSCV.aspx 页面的 asp.net 回发。

TableData 表单对象中提取 sData。然后调用 ExportCSV 方法,将 csv 数据发送回客户端浏览器,并带有一个标头,告诉浏览器保存或在外部应用程序中打开数据,而不是在浏览器中显示数据。

请注意,在初始页面加载时,**"FileName"** 属性已保存在 Page.Session 对象中。请查看代码

protected void Page_Load(object sender, EventArgs e)
{
  Session["_bExportCSVCalled"] = "false";
  if (!IsPostBack)
  {
    lnkExport.Attributes.Add(
    "onclick", "window.top.DownloadTable();");
    Session["_FileName"] = Context.Request.QueryString["FileName"];
  }
  else
  {
    string sData = Context.Request.Form["TableData"];
    if (String.IsNullOrEmpty(sData)) { return; }
    ExportCSV(sData);
  }
}

为了将字符串数据转换回反序列化对象,使用了 JavaScriptSerializer Deserialize 方法。这是 ExportCSV 中的代码片段

JavaScriptSerializer serializer = new JavaScriptSerializer();
TableData data = serializer.Deserialize<TableData>(sData);

这看起来很简单,除了必须先创建这一组类才能使其工作

public class columns
{
  public string ColumnNames;
}

public class rows
{
  public string row;
}

[Serializable]
public class TableData
{
  public columns[] Columns;
  public rows[] Rows;
}

回到 ExportCSV,cs TableData 对象可用于通过两个简单的 for 循环 **Response.Write** 所有行。

最后要做的是重写 Page Render 方法,以防止其他常规表单数据与您的 csv 数据一起回发。我引用的文章通过 **Response.End**(终止所有其余的 Page 处理)来完成此操作,但 Microsoft 不推荐这样做。他们推荐使用 **Context.ApplicationInstance.CompleteRequest()**。

因此,以下代码片段完成了服务器端的“文件”下载

protected override void Render(HtmlTextWriter writer)
{
  bool bExportCSVCalled = Convert.ToBoolean(Session["_bExportCSVCalled"]);
  if (bExportCSVCalled)
  {
    Context.Response.Flush();
    return; //stop the rest of the output from being written
  }
  base.Render(writer);
}

请注意,我在之前设置了 Context.Response.BufferOutput = true 后在此处执行了 Context.Response.Flush()。

这会导致所有 Response.Write 数据都保留在内存中,直到调用 Response.Flush()。如果您不希望这样,可以删除设置 BufferOutput = true 的行。然后您还必须删除 Response.Flush() 调用。

Render 方法中,如果 ExportCSV 没有先调用,我将恢复到通常的 Render 基方法。

现在,如果一切正常,客户端浏览器将弹出一个消息,询问用户是要打开还是保存文件。如果选择了打开选项,则设置为处理扩展名为“.csv”的文件(例如 Excel)的应用程序将打开并包含数据。

关注点

从 IFrame 中的父页面操作 DOM 元素可能非常困难。过去,我花费了大量时间来弄清楚如何在使用标准 Javascript 时找到这些元素的引用。jQuery 使这变得相当容易。

我最初认为可以使用通用的 ashx 处理程序来发送响应以进行文件下载,但浏览器不会以这种方式启动下载对话框。我发现 IFrame 解决方案是解决此问题的一种相当干净的解决方法。IFrame 页面执行回发而不是父页面。

历史

首次发布于 2012 年 1 月 29 日

© . All rights reserved.