5

I am working on a ASP.NET 4.5 Webform and I have a Gridview (that has custom TemplateField and gets data from a sqlDataSource)

I have this event to export the gridview contents to an excel sheet, and it does its jobs well except the created file is giving out an warning when user open it (which I understand because the file that got created is not an actual excel file):

"the file you are trying to open is in a different format than specified by the file extension"

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;
            }
        }

the result in the Excel .xls file is good (no styles except header columns, no footer, just exact as shown on the Gridview):

enter image description here


I am finding another way to avoid this warning, so I see people like to use
ClosedXML, so I replace that event above with this event:

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();
        }
    }
}

and the result is bad (only good new is that the exported file is a real 2007+ Excel sheet so no warnings): enter image description here

How do I get the "good" result above using closedXML?

Ronaldinho Learn Coding
  • 13,254
  • 24
  • 83
  • 110
  • Have you checked the DataTable before inserting it into the worksheet? You may have to call `dt.AcceptChanges();` before after adding the data to the DataTable. – Raidri Feb 12 '16 at 10:22
  • just put it in but still same result? – Ronaldinho Learn Coding Feb 12 '16 at 15:15
  • If I understand correctly, you are happy with the second excel (closedXML) but the data is not displaying? – Peet vd Westhuizen Feb 16 '16 at 08:14
  • yes, I want to use ClosedXML to export a real excel file but headers and data are not displaying, something wrong with the code. – Ronaldinho Learn Coding Feb 17 '16 at 15:46
  • you are going about this in the wrong way why are you parsing the headers etc.. you can accomplish this with much less code using ClosedXML I will post a working example that you can follow.. I use ClosedXML on a daily basis with zero issues. – MethodMan Feb 22 '16 at 01:28
  • Try to save the file with a `.html` extension, but still specifying content type as Excel. Rationale: you're using the old Office HTML format, and Excel is quite capable of opening HTML files. – code4life Feb 22 '16 at 02:07
  • Is there a reason you are exporting the GridView rather than the data that you bound to the GridView? That would really simplify your code and make more logical sense. – mason Mar 01 '16 at 01:29
  • for example a datetime data looks like `2016-01-02 00:00:00.000` in database but in Gridview I only want to display date only, another example would be a "status" data, raw data in database is an integer (1, 2, 3 etc.) but when displaying to Gridview 1 = "Pending", 2 = "Approved" and so on... so I want to export those "processed" data in Gridview, not the raw data. – Ronaldinho Learn Coding Mar 01 '16 at 07:34
  • @RonaldinhoLearnCoding Then that should be part of your export processing code. It's better to export the underlying data and manipulate it from there rather than taking your GridView. Now you've needlessly coupled your export to what your GridView is showing. Messy. – mason Mar 01 '16 at 14:32
  • @mason I don't quite get what you say, so I have already done the "formatting" job - basically make the Gridview display "good" in the web page, and that is also what I want to show in the excel sheet, there is no reason to repeat the "formatting" job again, instead just export that extract Gridview, am I missing something? – Ronaldinho Learn Coding Mar 09 '16 at 17:18
  • Yes, there is a very good reason for repeating the formatting. They're different. An Excel file's formatting is not defined by HTML. It has its own way of doing things. And HTML has its own way. Trying to get Excel to do HTML is a recipe for inflexible code. – mason Mar 09 '16 at 17:22

4 Answers4

4

The main problem in you second part of code (with ClosedXML) , that you are trying to use Text property of GridViewRow for TemplateField field columns. As you can see here, you can get field value via Text property only for BoundField field columns and automatically generated field columns.

To get value from TemplateField you should navigate to inner control which contains value and get value from it.

If you have the following column template:

   <asp:TemplateField>
       <ItemTemplate>
           <asp:Label ID="labelName" runat="server" Text ='<%# Eval("ABC")%>' ></asp:Label>
       </ItemTemplate>
   </asp:TemplateField>

Your code should be:

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

EDIT

Your code should be as follows:

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;
}

Ensure that all Label controls used in TemplateField have the same ID as "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>
Pavel Timoshenko
  • 721
  • 6
  • 13
  • How do I get all the headers, in my code I have something like `` – Ronaldinho Learn Coding Feb 17 '16 at 15:48
  • Also can you help with the loop, so I have like 7 ItemTemplate, most are labels, how do I get the data? It's would be really helpful if you can show a complete function or the codes/loop that will get the data. – Ronaldinho Learn Coding Feb 17 '16 at 15:57
  • I still can't get this working, can you show an example, assume that there are 5 `TemplateField` (with `HeaderText`), each has 1 `ItemTemplate` which contain 1 `label`. How your functions look like? – Ronaldinho Learn Coding Feb 17 '16 at 16:16
  • I've changed answer. I hope it will help you to solve the problem. – Pavel Timoshenko Feb 17 '16 at 18:14
  • For the header column, got an err: `'object' does not contain a definition for 'HeaderText' and no extension method 'HeaderText' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)` – Ronaldinho Learn Coding Feb 17 '16 at 19:55
  • I was looking at these articles, can you take a look at them: http://www.codeproject.com/Questions/1027572/Gridview-Export-To-Excel-xlsx-with-TemplateField and this: http://www.aspsnippets.com/Articles/Export-GridView-with-TemplateField-Column-to-Excel-in-ASPNet.aspx to find out? Your code was not working? – Ronaldinho Learn Coding Feb 17 '16 at 20:02
  • I did a small fix. Everything works correctly for me. – Pavel Timoshenko Feb 17 '16 at 20:54
4

I tried it's working ,please find the code hope it will help you:

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;
        }
    }




}
}
VCody
  • 506
  • 3
  • 12
3

I call the Export to Excel on a button click event like the following

protected void btnPrint_Click(object sender, EventArgs e)
{
    fileName = string.Format(fileName, DateTime.Now.ToString("MMddyyyy_hhmmss"));
    Extensions.ExportToXcel_SomeReport(dt, fileName, this.Page);
}

from there I have a utils class called Extensions where I have the ExportToExcel_SomeReport method defined

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();
    }   

}
MethodMan
  • 18,625
  • 6
  • 34
  • 52
  • This method serves an XLSX file up with the MIME type of an XLS file and using the .XLS file extension. It would be better to use the proper extension and MIME type `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`. – mason Feb 27 '16 at 15:48
  • @mason I am using ClosedXML and I am well aware.. if you have a better working version by all means please post an it as an answer – MethodMan Feb 29 '16 at 00:06
  • I do not understand your comment. You're serving an XLSX file with the MIME type used by an XLS file. You need to use the correct MIME type. It's as simple as that. – mason Feb 29 '16 at 00:16
  • @mason I have given the example using the type that I was testing with as it also works using the xlsx type I will update the answer it's as simple as that..! – MethodMan Feb 29 '16 at 14:33
  • I see you made a 1 character edit, but it still has the wrong MIME type. – mason Feb 29 '16 at 14:42
1

Contrary to popular belief, you can set the extension of your file to be .html, and Excel can open it up.

enter image description here

Just set the extension to HTML:

Response.AddHeader("content-disposition", "attachment;filename=GV.html");

And retain Excel as the content type:

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

EDIT: Oh, right, forgot to mention, this should make that annoying dialog go away.

EDIT 2: Looks like the original question has changed... now it's talking about using ClosedXML... but I'll leave this answer here just in case someone else is using HTML and Excel.

code4life
  • 15,655
  • 7
  • 50
  • 82
  • Just because you can does not mean you should. The MIME type should accurately describe the content. It's then up to the user's settings to determine what to open the file with. – mason Mar 01 '16 at 01:26
  • @mason: I'm referring to the original office HTML specs. Which used to exist on MSDN, but apparently not there anymore. Which, however, is *still* supported. Hence if you decide to save a datatable to HTML and then open it in Excel, it will quite easily do that for you. – code4life Mar 01 '16 at 18:14