在ASP.NET中生成Excel文件

时间:2020-03-06 14:53:34  来源:igfitidea点击:

我将在ASP.NET应用程序(VB.NET代码后面)中添加一个节,该节将允许用户获取作为Excel文件返回给他们的数据,该文件将基于数据库数据生成。尽管有多种方法可以实现,但每种方法都有其自身的缺点。我们将如何返回数据?我正在寻找一种尽可能简洁明了的东西。

解决方案

我只是根据数据创建一个CSV文件,因为我认为这是最干净的,而Excel对此有很好的支持。但是,如果我们需要更灵活的格式,我敢肯定有一些第三方工具可以生成真正的Excel文件。

CSV是最简单的方法。大多数情况下,它链接到Excel。否则,我们必须使用自动化API或者XML格式。 API和XML并不难使用。

有关为Excel生成XML的信息

我已经做过几次,每次最简单的方法就是简单地返回CSV(逗号分隔值)文件。 Excel可以完美地导入它,并且执行起来相对较快。

如果来自数据表。

public static void DataTabletoXLS(DataTable DT, string fileName)
    {
        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.Charset = "utf-16";
        HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("windows-1250");
        HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}.xls", fileName));
        HttpContext.Current.Response.ContentType = "application/ms-excel";

        string tab = "";
        foreach (DataColumn dc in DT.Columns)
        {
            HttpContext.Current.Response.Write(tab + dc.ColumnName.Replace("\n", "").Replace("\t", ""));
            tab = "\t";
        }
        HttpContext.Current.Response.Write("\n");

        int i;
        foreach (DataRow dr in DT.Rows)
        {
            tab = "";
            for (i = 0; i < DT.Columns.Count; i++)
            {
                HttpContext.Current.Response.Write(tab + dr[i].ToString().Replace("\n", "").Replace("\t", ""));
                tab = "\t";
            }
            HttpContext.Current.Response.Write("\n");
        }
        HttpContext.Current.Response.End();
                }

从Gridview

public static void GridviewtoXLS(GridView gv, string fileName)
    {
        int DirtyBit = 0;
        int PageSize = 0;
        if (gv.AllowPaging == true)
        {
            DirtyBit = 1;
            PageSize = gv.PageSize;
            gv.AllowPaging = false;
            gv.DataBind();
        }
        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.Charset = "utf-8";
        HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("windows-1250");
        HttpContext.Current.Response.AddHeader(
            "content-disposition", string.Format("attachment; filename={0}.xls", fileName));
        HttpContext.Current.Response.ContentType = "application/ms-excel";

        using (StringWriter sw = new StringWriter())
        {
            using (HtmlTextWriter htw = new HtmlTextWriter(sw))
            {
                //  Create a table to contain the grid
                Table table = new Table();

                //  include the gridline settings
                table.GridLines = gv.GridLines;

                //  add the header row to the table
                if (gv.HeaderRow != null)
                {
                    Utilities.Export.PrepareControlForExport(gv.HeaderRow);
                    table.Rows.Add(gv.HeaderRow);
                }

                //  add each of the data rows to the table
                foreach (GridViewRow row in gv.Rows)
                {
                    Utilities.Export.PrepareControlForExport(row);
                    table.Rows.Add(row);
                }

                //  add the footer row to the table
                if (gv.FooterRow != null)
                {
                    Utilities.Export.PrepareControlForExport(gv.FooterRow);
                    table.Rows.Add(gv.FooterRow);
                }

                //  render the table into the htmlwriter
                table.RenderControl(htw);

                //  render the htmlwriter into the response
                HttpContext.Current.Response.Write(sw.ToString().Replace("£", ""));
                HttpContext.Current.Response.End();
            }
        }
        if (DirtyBit == 1)
        {
            gv.PageSize = PageSize;
            gv.AllowPaging = true;
            gv.DataBind();
        }
               }

 private static void PrepareControlForExport(Control control)
    {
        for (int i = 0; i < control.Controls.Count; i++)
        {
            Control current = control.Controls[i];
            if (current is LinkButton)
            {
                control.Controls.Remove(current);
                control.Controls.AddAt(i, new LiteralControl((current as LinkButton).Text));
            }
            else if (current is ImageButton)
            {
                control.Controls.Remove(current);
                control.Controls.AddAt(i, new LiteralControl((current as ImageButton).AlternateText));
            }
            else if (current is HyperLink)
            {
                control.Controls.Remove(current);
                control.Controls.AddAt(i, new LiteralControl((current as HyperLink).Text));
            }
            else if (current is DropDownList)
            {
                control.Controls.Remove(current);
                control.Controls.AddAt(i, new LiteralControl((current as DropDownList).SelectedItem.Text));
            }
            else if (current is CheckBox)
            {
                control.Controls.Remove(current);
                control.Controls.AddAt(i, new LiteralControl((current as CheckBox).Checked ? "True" : "False"));
            }

            if (current.HasControls())
            {
                Utilities.Export.PrepareControlForExport(current);
            }
        }
    }

我们可以将数据输出为html表单元格,在其上添加.xls或者.xlsx扩展名,然后Excel会将其打开,就好像它是本机文档一样。我们甚至可以通过这种方式进行一些有限的格式和公式计算,因此它比CSV强大得多。另外,从诸如ASP.Net的Web平台输出html表应该非常容易。

如果我们在Excel工作簿中需要多个工作表或者命名工作表,则可以通过名为SpreadSheetML的XML模式执行类似的操作。这不是Office 2007附带的新格式,但是可以完全追溯到Excel 2000的东西完全不同。要说明它的工作方式,最简单的方法是通过一个示例:

<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?> 
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
        xmlns:o="urn:schemas-microsoft-com:office:office"
        xmlns:x="urn:schemas-microsoft-com:office:excel"
        xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
        xmlns:html="http://www.w3.org/TR/REC-html40">
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
      <Author>Your_name_here</Author>
      <LastAuthor>Your_name_here</LastAuthor>
      <Created>20080625</Created>
      <Company>ABC Inc</Company>
      <Version>10.2625</Version>
</DocumentProperties>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
        <WindowHeight>6135</WindowHeight>
        <WindowWidth>8445</WindowWidth>
        <WindowTopX>240</WindowTopX>
        <WindowTopY>120</WindowTopY>
        <ProtectStructure>False</ProtectStructure>
        <ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>

<Styles>
      <Style ss:ID="Default" ss:Name="Normal">
            <Alignment ss:Vertical="Bottom" />
            <Borders />
            <Font />
            <Interior />
            <NumberFormat />
            <Protection />
      </Style>
</Styles>

<Worksheet ss:Name="Sample Sheet 1">
<Table ss:ExpandedColumnCount="2" x:FullColumns="1" x:FullRows="1" ID="Table1">
<Column ss:Width="150" />
<Column ss:Width="200" />
<Row>
      <Cell><Data ss:Type="Number">1</Data></Cell>
      <Cell><Data ss:Type="Number">2</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="Number">3</Data></Cell>
      <Cell><Data ss:Type="Number">4</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="Number">5</Data></Cell>
      <Cell><Data ss:Type="Number">6</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="Number">7</Data></Cell>
      <Cell><Data ss:Type="Number">8</Data></Cell>
</Row>
</Table>
</Worksheet>

<Worksheet ss:Name="Sample Sheet 2">
<Table ss:ExpandedColumnCount="2" x:FullColumns="1" x:FullRows="1" ID="Table2">
<Column ss:Width="150" />
<Column ss:Width="200" />
<Row>
      <Cell><Data ss:Type="String">A</Data></Cell>
      <Cell><Data ss:Type="String">B</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="String">C</Data></Cell>
      <Cell><Data ss:Type="String">D</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="String">E</Data></Cell>
      <Cell><Data ss:Type="String">F</Data></Cell>
</Row>
<Row>
      <Cell><Data ss:Type="String">G</Data></Cell>
      <Cell><Data ss:Type="String">H</Data></Cell>
</Row>
</Table>
</Worksheet>
</Workbook>

由于Excel可以理解HTML,因此我们只需将数据作为HTML表写到扩展名为.xls的临时文件中,获取文件的FileInfo,然后使用

Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + fi.Name);
Response.AddHeader("Content-Length", fi.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(fi.FullName);
Response.End();

如果要避免使用临时文件,则可以写入内存流并将字节写回,而不是使用WriteFile

如果省略了content-length标头,则可以直接将html写回,但这可能无法在所有浏览器中始终正常运行

如果用数据填充GridView,则可以使用此功能获取HTML格式的数据,但表示浏览器是excel文件。

Public Sub ExportToExcel(ByVal fileName As String, ByVal gv As GridView)

        HttpContext.Current.Response.Clear()
        HttpContext.Current.Response.AddHeader("content-disposition", String.Format("attachment; filename={0}", fileName))
        HttpContext.Current.Response.ContentType = "application/ms-excel"

        Dim sw As StringWriter = New StringWriter
        Dim htw As HtmlTextWriter = New HtmlTextWriter(sw)
        Dim table As Table = New Table

        table.GridLines = gv.GridLines

        If (Not (gv.HeaderRow) Is Nothing) Then
            PrepareControlForExport(gv.HeaderRow)
            table.Rows.Add(gv.HeaderRow)
        End If

        For Each row As GridViewRow In gv.Rows
            PrepareControlForExport(row)
            table.Rows.Add(row)
        Next

        If (Not (gv.FooterRow) Is Nothing) Then
            PrepareControlForExport(gv.FooterRow)
            table.Rows.Add(gv.FooterRow)
        End If

        table.RenderControl(htw)

        HttpContext.Current.Response.Write(sw.ToString)
        HttpContext.Current.Response.End()

    End Sub

    Private Sub PrepareControlForExport(ByVal control As Control)

        Dim i As Integer = 0

        Do While (i < control.Controls.Count)

            Dim current As Control = control.Controls(i)

            If (TypeOf current Is LinkButton) Then
                control.Controls.Remove(current)
                control.Controls.AddAt(i, New LiteralControl(CType(current, LinkButton).Text))

            ElseIf (TypeOf current Is ImageButton) Then
                control.Controls.Remove(current)
                control.Controls.AddAt(i, New LiteralControl(CType(current, ImageButton).AlternateText))

            ElseIf (TypeOf current Is HyperLink) Then
                control.Controls.Remove(current)
                control.Controls.AddAt(i, New LiteralControl(CType(current, HyperLink).Text))

            ElseIf (TypeOf current Is DropDownList) Then
                control.Controls.Remove(current)
                control.Controls.AddAt(i, New LiteralControl(CType(current, DropDownList).SelectedItem.Text))

            ElseIf (TypeOf current Is CheckBox) Then
                control.Controls.Remove(current)
                control.Controls.AddAt(i, New LiteralControl(CType(current, CheckBox).Checked))

            End If

            If current.HasControls Then
                PrepareControlForExport(current)
            End If

            i = i + 1

        Loop

    End Sub

如果必须使用Excel而不是CSV文件,则需要在服务器上的一个Excel实例上使用OLE自动化。最简单的方法是拥有一个模板文件,并以编程方式将其填充数据。我们将其保存到另一个文件。

尖端:

  • 不要以交互方式进行。让用户启动该过程,然后发布包含该文件链接的页面。这样可以减轻在生成电子表格时潜在的性能问题。
  • 使用模板,如我之前所述。它使修改它变得更加容易。
  • 确保将Excel设置为不弹出对话框。在Web服务器上,这将挂起整个excel实例。
  • 将Excel实例放在单独的服务器上,最好放在防火墙后面,这样它就不会暴露为潜在的安全漏洞。
  • 密切注意资源使用情况。在OLE自动化接口上生成扩展名(PIA只是对此进行填充)是一个相当繁重的过程。如果我们需要将其扩展到高数据量,则可能需要对体系结构有所了解。

如果我们不介意文件的格式有点基本,则某些"使用mime类型诱骗excel来打开HTML表"方法会起作用。这些方法还会使CPU繁重的工作拖到客户端上。如果要对电子表格的格式进行细粒度的控制,则可能必须如上所述使用Excel本身来生成文件。

我或者走CSV路由(如上所述),或者最近更频繁地使用Infragistics NetAdvantage生成文件。 (在绝大多数情况下,Infragistics都在发挥作用,我们只是导出现有的UltraWebGrid,这实际上是一个LOC解决方案,除非需要进行额外的格式调整。我们也可以手动生成Excel / BIFF文件,但几乎没有必要。)

伙计,在.net中,我想我们可能有一个可以做到这一点的组件,但是在经典的ASP中,我已经完成了它的工作,它创建了一个html表并将页面的mime tipe更改为vnd / msexcel。我猜想,如果我们使用gridview并更改mime类型,也许应该可以使用,因为gridview是html表。

避免出现"看起来这些数字已存储为文本"绿色三角形的唯一防弹方法是使用Open XML格式。值得使用它,只是为了避免不可避免的绿色三角形。

我见过的excel报表最好的方法是用XML扩展名写出XML数据,并将其流传输到具有正确内容类型的客户端。 (应用程序/ xls)

这适用于需要基本格式的任何报告,并允许我们使用文本比较工具与现有电子表格进行比较。

我个人更喜欢XML方法。我将从数据库中的数据中返回数据,将其保存到XMl,然后创建一个xslt文件,其中包含一个转换规则,该转换规则将格式化适当的文档,并且简单的XML转换将完成工作。最好的部分是,我们可以设置单元格的格式,进行条件格式设置,设置页眉和页脚,甚至设置打印范围。

CSV

优点:

  • 简单的

缺点:

  • 它可能无法在其他语言环境或者不同的Excel配置中使用(即列表分隔符)
  • 无法应用格式,公式等

的HTML

优点:

  • 还是很简单
  • 支持简单的格式和公式

缺点:

  • 我们必须将文件命名为xls,Excel可能会警告我们有关打开非本机Excel文件的信息
  • 每个工作簿一个工作表

OpenXML(Office 2007 .XLSX)

优点:

  • 本机Excel格式
  • 支持所有Excel功能
  • 不需要Excel的安装副本
  • 可以生成数据透视表
  • 可以使用开源项目EPPlus生成

缺点:

  • Excel 2007外部的兼容性有限(如今应该不再是问题)
  • 除非我们使用第三方组件,否则会很复杂

SpreadSheetML(开放格式XML)

优点:

  • 与本机Excel格式相比简单
  • 支持大多数Excel功能:格式,样式,公式,每个工作簿多个工作表
  • 不需要安装Excel即可使用它
  • 无需第三方库-只需写出xml
  • 可以通过Excel XP / 2003/2007打开文档

缺点:

  • 缺乏好的文件
  • 在较早版本的Excel(2000之前的版本)中不支持
  • 只写,即在我们打开它并从Excel中进行更改后,它将转换为本机Excel。

XLS(由第三方组件生成)

优点:

  • 生成具有所有格式,公式等的本机Excel文件。

缺点:

  • 花费金钱
  • 添加依赖项

COM互操作

优点:

  • 使用本机Microsoft库
  • 阅读对本机文档的支持

缺点:

  • 非常慢
  • 依赖/版本匹配问题
  • 阅读时网络使用的并发/数据完整性问题
  • 非常慢
  • Web使用的扩展问题(与并发性不同):需要在服务器上创建许多繁重的Excel应用程序实例
  • 需要Windows
  • 我是否提到过它很慢?

只需通过Microsoft.Office.Interop命名空间避免COM Interop。它是如此的缓慢,不可靠和不可扩展。不适用于受虐狂。

根据给出的答案并与同事协商,看来最好的解决方案是生成XML文件或者HTML表并将其作为附件下推。我的同事建议的一项更改是,可以将数据(即HTML表)直接写到Response对象,从而消除了写出文件的需要,这可能由于权限问题,I / O而很麻烦。争用,并确保按计划进行清除。

这是一段代码...我还没有检查过,也没有提供所有被调用的代码,但是我认为它很好地表达了这个想法。

Dim uiTable As HtmlTable = GetUiTable(groupedSumData)

    Response.Clear()

    Response.ContentType = "application/vnd.ms-excel"
    Response.AddHeader("Content-Disposition", String.Format("inline; filename=OSSummery{0:ddmmssf}.xls", DateTime.Now))

    Dim writer As New System.IO.StringWriter()
    Dim htmlWriter As New HtmlTextWriter(writer)
    uiTable.RenderControl(htmlWriter)
    Response.Write(writer.ToString)

    Response.End()

假设这是用于Intranet的,我们可以在其中设置权限和授权IE,则可以使用驱动Excel的JScript / VBScript生成工作簿客户端。这为我们提供了本机Excel格式,而无需尝试在服务器上自动执行Excel的麻烦。

除了在相当特殊的情况下,我不确定是否会真的推荐这种方法,但是在经典的ASP鼎盛时期它是相当普遍的。

我们当然可以总是选择第三方组件。就个人而言,我在Spire.XLS上有很好的经验http://www.e-iceblue.com/xls/xlsintro.htm

该组件在应用程序中非常易于使用:

Workbook workbook = new Workbook();

        //Load workbook from disk.
        workbook.LoadFromFile(@"Data\EditSheetSample.xls");
        //Initailize worksheet
        Worksheet sheet = workbook.Worksheets[0];

        //Writes string
        sheet.Range["B1"].Text = "Hello,World!";
        //Writes number
        sheet.Range["B2"].NumberValue = 1234.5678;
        //Writes date
        sheet.Range["B3"].DateTimeValue = System.DateTime.Now;
        //Writes formula
        sheet.Range["B4"].Formula = "=1111*11111";

        workbook.SaveToFile("Sample.xls");

我们一直从datagrid导出数据以保持卓越性能。将其转换为HTML,然后写入Excel文件

Response.ContentType = "application/vnd.ms-excel"
    Response.Charset = ""
    Response.AddHeader("content-disposition", "fileattachment;filename=YOURFILENAME.xls")
    Me.EnableViewState = False
    Dim sw As System.IO.StringWriter = New System.IO.StringWriter
    Dim hw As HtmlTextWriter = New HtmlTextWriter(sw)
    ClearControls(grid)
    grid.RenderControl(hw)
    Response.Write(sw.ToString())
    Response.End()

使用此方法的唯一陷阱是,我们的许多网格中都包含按钮或者链接,因此我们也需要这样做:

'needed to export grid to excel to remove link button control and represent as text
Private Sub ClearControls(ByVal control As Control)
    Dim i As Integer
    For i = control.Controls.Count - 1 To 0 Step -1
        ClearControls(control.Controls(i))
    Next i

    If TypeOf control Is System.Web.UI.WebControls.Image Then
        control.Parent.Controls.Remove(control)
    End If

    If (Not TypeOf control Is TableCell) Then
        If Not (control.GetType().GetProperty("SelectedItem") Is Nothing) Then
            Dim literal As New LiteralControl
            control.Parent.Controls.Add(literal)
            Try
                literal.Text = CStr(control.GetType().GetProperty("SelectedItem").GetValue(control, Nothing))
            Catch
            End Try
            control.Parent.Controls.Remove(control)
        Else
            If Not (control.GetType().GetProperty("Text") Is Nothing) Then
                Dim literal As New LiteralControl
                control.Parent.Controls.Add(literal)
                literal.Text = CStr(control.GetType().GetProperty("Text").GetValue(control, Nothing))
                control.Parent.Controls.Remove(control)
            End If
        End If
    End If
    Return
End Sub

我发现在某个地方,它运作良好。

这是SpreadML的免费包装程序,效果很好。

http://www.carlosag.net/Tools/ExcelXmlWriter/

我使用上述建议的解决方案之一遇到的问题之一与该答案类似,就是如果我们将内容作为附件推出(对于非ms浏览器,我发现这是最干净的解决方案) ,然后在Excel 2000-2003中打开它,它的类型是" Excel Web Page"而不是本机Excel文档。

然后,我们必须向用户解释如何在Excel中使用"另存为类型"将其转换为Excel文档。如果用户需要编辑此文档,然后将其重新上传到站点,这会很麻烦。

我的建议是使用CSV。这很简单,如果用户确实从Excel中打开它,则Excel至少会提示他们以本机格式保存它。

这是一个从存储过程中提取的报告。结果将导出到Excel。
它使用ADO而不是ADO.NET,这是此行的原因

oSheet.Cells(2, 1).copyfromrecordset(rst1)

它完成了大部分工作,但ado.net中不提供该功能。

‘Calls stored proc in SQL Server 2000 and puts data in Excel and ‘formats it

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim cnn As ADODB.Connection
        cnn = New ADODB.Connection
        cnn.Open("Provider=SQLOLEDB;data source=xxxxxxx;" & _
          "database=xxxxxxxx;Trusted_Connection=yes;")

        Dim cmd As New ADODB.Command

        cmd.ActiveConnection = cnn

        cmd.CommandText = "[sp_TomTepley]"
        cmd.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc
        cmd.CommandTimeout = 0
        cmd.Parameters.Refresh()

        Dim rst1 As ADODB.Recordset
        rst1 = New ADODB.Recordset
        rst1.Open(cmd)

        Dim oXL As New Excel.Application
        Dim oWB As Excel.Workbook
        Dim oSheet As Excel.Worksheet

        'oXL = CreateObject("excel.application")
        oXL.Visible = True
        oWB = oXL.Workbooks.Add
        oSheet = oWB.ActiveSheet

        Dim Column As Integer
        Column = 1

        Dim fld As ADODB.Field
        For Each fld In rst1.Fields

            oXL.Workbooks(1).Worksheets(1).Cells(1, Column).Value = fld.Name
            oXL.Workbooks(1).Worksheets(1).cells(1, Column).Interior.ColorIndex = 15
            Column = Column + 1

        Next fld

        oXL.Workbooks(1).Worksheets(1).name = "Tom Tepley Report"
        oSheet.Cells(2, 1).copyfromrecordset(rst1)
        oXL.Workbooks(1).Worksheets(1).Cells.EntireColumn.AutoFit()

        oXL.Visible = True
        oXL.UserControl = True

        rst1 = Nothing

        cnn.Close()
        Beep()

    End Sub

这是一个解决方案,可将数据表以CSV格式进行流式传输。快速,干净和容易,并且可以处理输入中的逗号。

public static void ExportToExcel(DataTable data, HttpResponse response, string fileName)
{
    response.Charset = "utf-8";
    response.ContentEncoding = System.Text.Encoding.GetEncoding("windows-1250");
    response.Cache.SetCacheability(HttpCacheability.NoCache);
    response.ContentType = "text/csv";
    response.AddHeader("Content-Disposition", "attachment; filename=" + fileName);

    for (int i = 0; i < data.Columns.Count; i++)
    {
       response.Write(data.Columns[i].ColumnName);
       response.Write(i == data.Columns.Count - 1 ? "\n" : ",");
    }        
    foreach (DataRow row in data.Rows)
    {
        for (int i = 0; i < data.Columns.Count; i++)
        {
            response.Write(String.Format("\"{0}\"", row[i].ToString()));
            response.Write(i == data.Columns.Count - 1 ? "\n" : ",");
        }
    }

    response.End();
}