使用ClosedXML将Gridview导出到Excel时避免警告:您尝试打开的文件格式不同。

5
我正在开发一个ASP.NET 4.5 Webform,并且有一个GridView(其中包含自定义的TemplateField,并从sqlDataSource获取数据)。
我有一个事件来将gridview内容导出到Excel表格中,它完成了它的工作,除了创建的文件在用户打开时会给出警告(我理解这是因为创建的文件不是实际的Excel文件):
“您尝试打开的文件与文件扩展名指定的格式不同。”
protected void btnExport_Excel_Click(object sender, EventArgs e)
        {
            try
            {
                Response.Clear();
                Response.Buffer = true;
                Response.AddHeader("content-disposition", "attachment;filename=GV.xls");
                Response.Charset = "";
                Response.ContentType = "application/ms-excel";
                //Response.ContentType = "application/text";
                Response.ContentEncoding = System.Text.Encoding.Unicode;
                Response.BinaryWrite(System.Text.Encoding.Unicode.GetPreamble());

                using (StringWriter sw = new StringWriter())
                {
                    HtmlTextWriter hw = new HtmlTextWriter(sw);

                    //To Export all pages
                    GridView4.AllowPaging = false;
                    GridView4.AllowSorting = false;
                    GridView4.ShowFooter = false;
                    GridView4.DataBind();
                    //this.BindGrid();

                    GridView4.HeaderRow.BackColor = Color.White;
                    foreach (TableCell cell in GridView4.HeaderRow.Cells)
                    {
                        cell.BackColor = GridView4.HeaderStyle.BackColor;
                    }
                    foreach (GridViewRow row in GridView4.Rows)
                    {
                        row.BackColor = Color.White;
                        foreach (TableCell cell in row.Cells)
                        {
                            if (row.RowIndex % 2 == 0)
                            {
                                cell.BackColor = GridView4.AlternatingRowStyle.BackColor;
                            }
                            else
                            {
                                cell.BackColor = GridView4.RowStyle.BackColor;
                            }
                            cell.CssClass = "textmode";
                        }
                    }

                    GridView4.RenderControl(hw);

                    //style to format numbers to string
                    string style = @"<style> .textmode { } </style>";
                    Response.Write(style);
                    Response.Output.Write(sw.ToString());
                    Response.Flush();
                    Response.End();
                }

                //Display message
                InfoPanel.Visible = true;
                InfoPanel.CssClass = "panel panel-success";
                lblMessage.CssClass = "text text-sucess bold";
                lblMessage.Text = "File has been exported!";

            }
            catch (Exception ex)
            {
                //Display message
                InfoPanel.Visible = true;
                lblMessage.Text = "<b>An error has occurred. Please try again later!</b></br>" + ex.Message;
                lblMessage.CssClass = "text text-danger bold";
                InfoPanel.CssClass = "panel panel-danger";
                panelResult.Visible = false;
            }
        }

在Excel .xls文件中的结果很好(除了标题列没有样式,没有页脚,与Gridview上显示的完全一致):

enter image description here


我正在寻找另一种避免此警告的方法,我发现有人喜欢使用ClosedXML,因此我用此事件替换了上面的事件:

protected void ExportExcel(object sender, EventArgs e)
{
    DataTable dt = new DataTable("GridView_Data");
    foreach(TableCell cell in GridView4.HeaderRow.Cells)
    {
        dt.Columns.Add(cell.Text);
    }
    foreach (GridViewRow row in GridView4.Rows)
    {
        dt.Rows.Add();
        for (int i=0; i<row.Cells.Count; i++)
        {
            dt.Rows[dt.Rows.Count - 1][i] = row.Cells[i].Text;
        }
   }
    using (XLWorkbook wb = new XLWorkbook())
    {
        wb.Worksheets.Add(dt);

        Response.Clear();
        Response.Buffer = true;
        Response.Charset = "";
        Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        Response.AddHeader("content-disposition", "attachment;filename=GV.xlsx");

        using (MemoryStream MyMemoryStream = new MemoryStream())
        {
            wb.SaveAs(MyMemoryStream);
            MyMemoryStream.WriteTo(Response.OutputStream);
            Response.Flush();
            Response.End();
        }
    }
}

结果很糟糕(唯一的好消息是导出的文件是一个真正的2007+ Excel表格,所以没有警告): 输入图像描述

如何使用closedXML获得上述“好”结果?


如果我理解正确,您对第二个Excel(closedXML)感到满意,但数据没有显示? - Peet vd Westhuizen
是的,我想使用ClosedXML来导出真正的Excel文件,但是标题和数据没有显示出来,代码可能有问题。 - Ronaldinho Learn Coding
例如,数据库中的日期时间数据看起来像是“2016-01-02 00:00:00.000”,但在Gridview中,我只想显示日期部分。另一个例子是“状态”数据,在数据库中原始数据是整数(1、2、3等),但在Gridview中显示时,1表示“待处理”,2表示“已批准”等等...所以我想导出Gridview中的这些“处理过”的数据,而不是原始数据。 - Ronaldinho Learn Coding
@mason,我不太明白你的意思,所以我已经完成了“格式化”工作——基本上是使Gridview在网页中显示得很好,这也是我想在Excel表格中展示的内容,没有必要再重复“格式化”工作,而只需导出提取的Gridview,我有什么遗漏吗? - Ronaldinho Learn Coding
是的,重复格式化有非常好的理由。它们是不同的。Excel文件的格式不是由HTML定义的。它有自己的做事方式。而HTML也有自己的方式。试图让Excel执行HTML是一种制造不灵活代码的方法。 - mason
显示剩余6条评论
4个回答

4
你的第二部分代码(使用ClosedXML)的主要问题在于,你试图使用GridViewRowText属性来处理TemplateField字段列。如你所见here,你只能通过Text属性获取BoundField字段列和自动生成的字段列的值。
要获取TemplateField的值,你需要导航到包含该值的内部控件并从中获取值。
如果你有以下列模板:
   <asp:TemplateField>
       <ItemTemplate>
           <asp:Label ID="labelName" runat="server" Text ='<%# Eval("ABC")%>' ></asp:Label>
       </ItemTemplate>
   </asp:TemplateField>

你的代码应该是:

    for (int i=0; i<row.Cells.Count; i++)
    {
        dt.Rows[dt.Rows.Count - 1][i] = (row.Cells[i].FindControl("labelName") as Label).Text;
    }

编辑

你的代码应该如下所示:

protected void ExportExcel(object sender, EventArgs e)
{
    DataTable dt = new DataTable("GridView_Data");
    foreach (DataControlField col in GridView4.Columns)
    {
        dt.Columns.Add(col.HeaderText);
    }
    foreach (GridViewRow row in GridView4.Rows)
    {
        dt.Rows.Add();
        for (int i = 0; i < row.Cells.Count; i++)
        {
            dt.Rows[dt.Rows.Count - 1][i] = (FindControl(row.Cells[i].Controls, "lbl") as Label).Text;
        }
    }
    //your code below is not changed
}

protected Control FindControl(ControlCollection collection, string id)
{
    foreach (Control ctrl in collection)
    {
        if (ctrl.ID == id)
            return ctrl;
    }

    return null;
}

确保在使用TemplateField的所有 Label 控件中,ID"lbl" 相同:

   <asp:TemplateField HeaderText="ID">
       <ItemTemplate>
           <asp:Label ID="lbl" runat="server" Text ='<%# Eval("ID")%>' ></asp:Label>
       </ItemTemplate>
   </asp:TemplateField>
   <asp:TemplateField HeaderText="Name">
       <ItemTemplate>
           <asp:Label ID="lbl" runat="server" Text ='<%# Eval("Name")%>' ></asp:Label>
       </ItemTemplate>
   </asp:TemplateField>
   <asp:TemplateField HeaderText="Amount">
       <ItemTemplate>
           <asp:Label ID="lbl" runat="server" Text ='<%# Eval("Amount")%>' ></asp:Label>
       </ItemTemplate>
   </asp:TemplateField>

我该如何获取所有的表头,在我的代码中,我有类似于<asp:TemplateField HeaderText="Amount" SortExpression="Amount" HeaderStyle-Width="8%">的内容。 - Ronaldinho Learn Coding
你能帮我解决一个问题吗?我有7个ItemTemplate,其中大多数是标签,你能告诉我如何获取数据吗?如果您能提供完整的函数或代码/循环以获取数据,那将非常有帮助。 - Ronaldinho Learn Coding
我仍然无法让它工作,你能给个例子吗?假设有5个TemplateField(带有HeaderText),每个都有1个包含1个labelItemTemplate。你的函数是什么样子的? - Ronaldinho Learn Coding
我已经修改了答案。希望它能帮助你解决问题。 - Pavel Timoshenko
我正在看这些文章,你能看一下它们吗:http://www.codeproject.com/Questions/1027572/Gridview-Export-To-Excel-xlsx-with-TemplateField 和 http://www.aspsnippets.com/Articles/Export-GridView-with-TemplateField-Column-to-Excel-in-ASPNet.aspx ?查找问题所在了吗?你的代码没有起作用? - Ronaldinho Learn Coding
显示剩余2条评论

4
我尝试了一下,它可行,请找到代码,希望能帮助您:
Index.aspx
    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="ExportExcel.Index" %>

  <!DOCTYPE html>

  <html xmlns="http://www.w3.org/1999/xhtml">
  <head runat="server">
  <title></title>
  </head>
  <body>
<form id="form1" runat="server">
<div>
    <asp:GridView ID="GridView1" HeaderStyle-BackColor="#3AC0F2" HeaderStyle- ForeColor="White"
        runat="server" AutoGenerateColumns="false">
        <Columns>
            <asp:BoundField DataField="Id" HeaderText="Id" ItemStyle-Width="30"  />
            <asp:TemplateField HeaderText="Name">
                <ItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%#Eval("Name") %>'></asp:TextBox>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Country">
                <ItemTemplate>
                    <asp:Label ID="lblCountry" Text='<%# Eval("Country") %>' runat="server" />
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
    <br />
    <asp:Button ID="btnExport" Text="Export" runat="server" OnClick="btnExport_Click" />
</div>
</form>

Index.aspx.cs

        using ClosedXML.Excel;
        using System;
        using System.Collections.Generic;
        using System.Data;
        using System.Drawing;
        using System.IO;
        using System.Linq;
        using System.Web;
        using System.Web.UI;
        using System.Web.UI.WebControls;

   namespace ExportExcel
    {
    public partial class Index : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            GetData();
        }
    }

    private void GetData()
    {
        DataTable dt = new DataTable();
        dt.Columns.AddRange(new DataColumn[3] { new DataColumn("Id", typeof(int)), new DataColumn("Name", typeof(string)), new DataColumn("Country", typeof(string)) });
        dt.Rows.Add(1, "abc", "UK");
        dt.Rows.Add(2, "def", "India");
        dt.Rows.Add(3, "ghi", "France");
        dt.Rows.Add(4, "jkl", "Russia");
        GridView1.DataSource = dt;
        GridView1.DataBind();
    }

    protected void btnExport_Click(object sender, EventArgs e)
    {
        try
        {
            DataTable dt = new DataTable("GridView_Data");
            foreach (TableCell cell in GridView1.HeaderRow.Cells)
            {
                dt.Columns.Add(cell.Text);
            }
            foreach (GridViewRow row in GridView1.Rows)
            {
                TextBox txtNameRow = (TextBox)row.FindControl("txtName");

                Label lblCountryRow = (Label)row.FindControl("lblCountry");

                DataRow drow = dt.NewRow();
                for (int i = 0; i < GridView1.Columns.Count; i++)
                {
                    drow[i] = row.Cells[i].Text;
                }
                drow["Name"] = txtNameRow.Text;
                drow["Country"] = lblCountryRow.Text;
                dt.Rows.Add(drow);
            }
            using (XLWorkbook wb = new XLWorkbook())
            {
                wb.Worksheets.Add(dt);

                Response.Clear();
                Response.Buffer = true;
                Response.Charset = "";
                Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                Response.AddHeader("content-disposition", "attachment;filename=GV.xlsx");

                using (MemoryStream MyMemoryStream = new MemoryStream())
                {
                    wb.SaveAs(MyMemoryStream);
                    MyMemoryStream.WriteTo(Response.OutputStream);
                    Response.Flush();
                    Response.End();
                }
            }

        }
        catch (Exception ex)
        {

            throw;
        }
    }




}
}

3
我在按钮点击事件中调用"导出到Excel"功能,代码如下:
protected void btnPrint_Click(object sender, EventArgs e)
{
    fileName = string.Format(fileName, DateTime.Now.ToString("MMddyyyy_hhmmss"));
    Extensions.ExportToXcel_SomeReport(dt, fileName, this.Page);
}

我有一个叫做Extensions的工具类,其中定义了ExportToExcel_SomeReport方法。

public static class Extensions
{
     internal static void ExportToXcel_SomeReport(DataTable dt, string fileName, Page page)
    {
        var recCount = dt.Rows.Count;
        RemoveHtmlSpecialChars(dt);
        fileName = string.Format(fileName, DateTime.Now.ToString("MMddyyyy_hhmmss"));
        var xlsx = new XLWorkbook();
        var ws = xlsx.Worksheets.Add("Some Report Name");
        ws.Style.Font.Bold = true;
        ws.Cell("C5").Value = "YOUR REPORT NAME";
        ws.Cell("C5").Style.Font.FontColor = XLColor.Black;
        ws.Cell("C5").Style.Font.SetFontSize(16.0);
        ws.Cell("E5").Value = DateTime.Now.ToString("MM/dd/yyyy HH:mm");
        ws.Range("C5:E5").Style.Font.SetFontSize(16.0);
        ws.Cell("A7").Value = string.Format("{0} Records", recCount);
        ws.Style.Font.Bold = false;
        ws.Cell(9, 1).InsertTable(dt.AsEnumerable());
        ws.Row(9).InsertRowsBelow(1);
       // ws.Style.Font.FontColor = XLColor.Gray;
        ws.Columns("1-9").AdjustToContents();
        ws.Tables.Table(0).ShowAutoFilter = true;
        ws.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
        DynaGenExcelFile(fileName, page, xlsx);
    }


    /// <summary>
    /// Remove all HTML special characters from datatable field if they are present 
    /// </summary>
    /// <param name="dt"></param>
    private static void RemoveHtmlSpecialChars(DataTable dt)
    {
        for (int rows = 0; rows < dt.Rows.Count; rows++)
        {
            for (int column = 0; column < dt.Columns.Count; column++)
            {
                dt.Rows[rows][column] = dt.Rows[rows][column].ToString().Replace("&nbsp;", string.Empty);
            }
        }
    }

    /// <summary>
    /// Call this Method to Generate the Excel Files from different Lap Reports depending on which one has been selected
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="page"></param>
    /// <param name="xlsx"></param>
    private static void DynaGenExcelFile(string fileName, Page page, XLWorkbook xlsx)
    {
        page.Response.ClearContent();
        page.Response.ClearHeaders();
        page.Response.ContentType = "application/vnd.ms-excel";
        page.Response.AppendHeader("Content-Disposition", string.Format("attachment;filename={0}.xlsx", fileName));

        using (MemoryStream memoryStream = new MemoryStream())
        {
            xlsx.SaveAs(memoryStream);
            memoryStream.WriteTo(page.Response.OutputStream);
        }
        page.Response.Flush();
        page.Response.End();
    }   

}

该方法提供了一个XLS文件,使用.XLS文件扩展名和XLS文件的MIME类型。最好使用正确的扩展名和MIME类型application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - mason
@mason 我正在使用ClosedXML,我很清楚...如果你有一个更好的工作版本,请毫不犹豫地将其发布为答案。 - MethodMan
我不理解你的评论。你正在使用XLS文件使用的MIME类型提供XLSX文件。你需要使用正确的MIME类型。就这么简单。 - mason
@mason 我已经给出了一个例子,使用我正在测试的类型,它也可以使用xlsx类型。我会更新答案,就这么简单! - MethodMan
我看到你进行了一个字符的编辑,但它仍然具有错误的MIME类型。 - mason

1
与普遍的看法不同,您可以将文件扩展名设置为 .html,Excel 可以打开它。

enter image description here

只需将扩展名设置为HTML:
Response.AddHeader("content-disposition", "attachment;filename=GV.html");

保留Excel作为内容类型:

并保留HTML:

Response.ContentType = "application/ms-excel"; 

编辑:哦,对了,忘了提到,这应该使那个烦人的对话框消失。

编辑2:看起来原问题已经改变了...现在它谈论使用ClosedXML...但我会把这个答案留在这里,以防其他人正在使用HTML和Excel。


仅仅因为你能做到并不意味着你应该这么做。MIME类型应准确描述内容,然后由用户设置决定使用何种方式打开文件。 - mason
@mason:我指的是最初的Office HTML规范。它曾经存在于MSDN上,但显然现在已经不在了。然而,这个规范仍然得到支持。因此,如果你决定将一个datatable保存为HTML,然后在Excel中打开它,它会很容易地为你完成这个操作。 - code4life

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接