辛辛苦苦幫User寫了GridView或DataGrid網頁,User常會神來一筆: 我想把這個清單轉成Excel耶~~~

簡單! Mouse拖曳選取、Ctrl-C複製、開啟Excel、Ctrl-V貼上不就OK啦!

很不幸地,每次提供這種建議都會招來User白眼。User要是這麼愛自己DIY,IT人員一年大概可以多放十天年假吧!? 而且還有一個大問題,Grid常會設自動分頁,要User剪剪貼貼之餘還要順便玩一下"接龍",並不合情理。

過去我搞過一些鋸箭解決方案: 用foreach DataTable中的每一個Row,再Response.Write成CSV;再不然就是寫成Reporting Service報表,借重它輸出成Excel功能。

但這二個方法都有些缺點,前者會因為轉成CSV會喪失格式設計,後者則得在做好DataGrid/GridView後,再另外做一張報表並建立Reporting Service機制,同時,二者都得在GridView/DataGrid外多花功夫。如果GridView可以直接輸出成Excel,該有多好?

再度碰到這個需求,這回我Google了一下,才發現已經有許多人找到直接匯出的解決方案囉!

利用GridView自行Render出與網頁上格式相同的HTML,配合application/vnd.xls MIME Type,GridView就可以直接在Excel中重現,很酷吧?

protected void btnExportExcel_Click(object sender, EventArgs e)
{
    Response.Clear();
    Response.AddHeader("content-disposition", 
        "attachment;filename=PoolExport.xls");
    Response.ContentType = "application/vnd.xls";
    System.IO.StringWriter sw = new System.IO.StringWriter();
    System.Web.UI.HtmlTextWriter htw = new HtmlTextWriter(sw);
 
    //關閉換頁跟排序
    GridView1.AllowSorting = false;
    GridView1.AllowPaging = false;
    
    //移去不要的欄位
    GridView1.Columns.RemoveAt(GridView1.Columns.Count - 1);
    GridView1.DataBind();
 
    //建立假HtmlForm避免以下錯誤
    //Control 'GridView1' of type 'GridView' must be placed inside 
    //a form tag with runat=server. 
    //另一種做法是override VerifyRenderingInServerForm後不做任何事
    //這樣就可以直接GridView1.RenderControl(htw);
 
    HtmlForm hf = new HtmlForm();
    Controls.Add(hf);
    hf.Controls.Add(GridView1);
    hf.RenderControl(htw);
 
    Response.Write(sw.ToString());
    Response.End();
}

補充說明幾個重點:

  1. 由於是透過HTML+MIME宣告的原理,DataGrid、GridView都適用。
  2. 記得移去不要的欄位,取消排序、分頁的設定。
  3. 這個寫法會產生Control 'GridView1' of type 'GridView' must be placed inside a form tag with runat=server的錯誤,可利用override VerifyRenderingInServerForm或另建一個假的HtmlForm來避免
  4. 這個寫法還會導致RegisterForEventValidation can only be called during Render()的錯誤,需在Page宣告中停用EnableEventValidation解決。(可以參考這篇文章中的【技術細節】一欄)


Comments

# by Frank

我也用這方法從網頁DataGrid產生excel,但如果資料筆數太多等待時間會成指數性成長,只有20筆大概等兩秒,500筆要等到7分鐘,想請教Jeffrey大大有遇過類似的問題嗎?

# by evakey

我在 GridView1.RenderControl(htw); 之前使用 GridView1.EnableViewState = false; 就不會有 Control 'GridView1' of type 'GridView' must be placed inside //a form tag with runat=server. 的錯誤,但不是很肯定,請你試試囉

# by Jeffrey

To evakey, 在ASP.NET 2.0測試了一下,沒有成功... 如果還有其他情報,歡迎再提供給我。謝謝!

# by evakey

今早再試,發現使用 EnableViewState = false; 的確有效,但如果在 PostBack 後再關閉,似乎來不及… 有什麼方法可以清除 GridView 的 ViewState 呢? 再來試試~

# by evakey

重新整理兩個方法(註:我使用的是 DataGrid) 1. 因為我的查詢結果含有排序功能所以存成 Excel 時要先關閉 AllowSorting 功能(不然會有連結,醜),然後需要再 DataBind 一次。而在 DataBind 之前使用 EnableViewState = false; 即可避免「Control 'GridView1' of type 'GridView' must be placed inside a form tag with runat=server. 」的錯誤 2. 另一個方法於 http://blog.csdn.net/net_lover/archive/2006/09/25/1282106.aspx 所發現 只要加上 public override void VerifyRenderingInServerForm(Control control) { //避免「型別 xxx 的控制項 xxx 必須置於有 runat=server 的表單標記之中。」的問題 } 即可 不知道這兩個方法有何優缺點…

# by ettore@e-cefala.it

try http://www.gridviewtoexcel.com that's a real XML and xlsx export to Excel

# by devin

Excel可辨認HTML組成的Table 匯出的Excel可用記事本開啟. 不過在存成Excel後,編碼就成了Excel的編碼嚕 :) 在vs2003 匯出後,另存新檔時,會預設格式為HTML Office 2000 下測試

# by joe

請問大大 若要將GridView 產生的EXECL檔 自動轉存在特定的目錄中 要如何實作? 謝謝

# by Jeffrey

to joe, "自動轉存"的理想並不容易實現,企圖用Javascript/Flash/Silverlight等技術在未經使用者同意(也就是使用者不必進行任何操作),就將檔案存入使用者本機,會嚴重違反瀏覽器的安全原則。想突破此一限制大概只能靠客製ActiveX Control或撰寫Plug-In才做得到,不過得說服使用者願意安裝你的元件/程式才行。

# by 戰鬥大貓

感謝,正好有這個需求要處理,馬上來試試看!

# by Frances

請問可以分別存在多個sheet裡嗎??

# by Jeffrey

to Frances, 據我所知,這種產生HTML再標ContentType的方法只能產生單一Sheet,如果有人知道解決方案請再出個聲。

# by Pamela

想要請問一下,照著大大的做法做了,為何我在所匯出的Excel會出現不只GridView,還包含了Web上的其他欄位 找了好久,真的不知道為什麼?!

# by Jeffrey

to Pamela, 這段程式中,以Response.Clear()起始,以Response.Write(); Response.End()結尾,理論上應該只有我們輸出的內容而已,我想不太出哪裡可以被加料... 莫非是HttpModule、HttpHandler或Control Adpater之類的機制在作怪?

# by Chenyy

我的情形跟Pamela兄很類似,在匯出成Excel時最上面會出現空行XD,後來發現New出來的HtmlForm會有一些script跟Viewstate及Hidden欄位等資訊. 改用override VerifyRenderingInServerForm方式的格式才比較正常.

# by Hans

以Response.Clear()起始,Response.Write()和 Response.End()結尾方法导出Excel文件时,如何可以向"btnExportExcel_Click"所在页面输入脚本呢?似乎用这个方法就无法输出JS脚本给页面以提示Excel已生成.

# by Jeffrey

to Hans, 關於"输出JS脚本给页面以提示Excel已生成",我想到的解法是傳回一個Web跑JS,再用Iframe或window.open導到下載。不過,下載時,User就會被提示需要開啟或另存檔案,我覺得似乎不太需要額外的alert提示。

# by karen

請問為什麼匯出成Excel 後,檔案會預設格式為HTML,要如何改成檔案格式為excel(.xls)?

# by Jeffrey

to karen, 是指Browser/IE開啟時當成HTML開啟? 理論上設為Response.ContentType = "application/vnd.xls";後應該就會被視為xls,以Excel開啟才對。要再提供更詳細的訊息給大家嗎?

# by Karen

我匯出的Excel可以開啟 可是會出現檔案格式與附檔名格式不同的訊息 有辦法不要show出這個訊息嗎??(我是用excel2007開啟的)

# by Jeffrey

to Karen, 是的,我也發現Excel 2007開啟時會多了這個警示,到截稿為止,我還沒找到閃掉的方法。

# by 大估

小弟最近的案子,有關匯出excel的部份,開始轉用NPOI的元件來處理,詳細說明:http://msdn.microsoft.com/zh-tw/ee818993.aspx 在此給大家作另一個解法參考~

# by No.18

黑暗大: 不好意思借轉一下本篇url在小鋪上 已有註明是來自黑暗的範例

# by 江小咪

請問一下,我的網頁上半部是datagird資料,下半是用table拉出來的畫面,裡面有個欄位是利率:XXXX %,值是用lable顯示。 整張表單要可以匯出成excel,datagrid的部份用版主的方法是OK的,下半部是用以下寫法 Dim Header3 As String = "" Header3 += "<table><tr>" Header3 += "<td ><span>比率:" & Me.VlblC_Credit_Rate.Text & "</span></td>" Header3 += "</tr>" Header3 += "</table>" Response.Write(Header3) Response.End() ---------------------------- 網頁上的資料是 利率:0.1213% 但轉成excel後,是顯示0.12%,點選excel的欄位會變成0.1213%,我看此欄位的格式是百分比,小數位數2位,有辦法在程式中指定excel的格是允許4位小數嗎?? 謝謝

# by Jeffrey

to 江小咪, 這裡運作的原理有點像提供CSV讓Excel開啟(只是多了顏色字型等格式),欄位的型別及呈現格式還是會由Excel自行決定。我知道Excel裡可以透過手動修改欄位格式將小數放到4位,至於要在程式裡指定看來有些困難。如果對Excel希望能做到更精準的控制,或許可以考慮使用NPOI: http://msdn.microsoft.com/zh-tw/ee818993.aspx

# by Jason

Excel上面會多一行空白的問題,可以試試用下面的方法 把 Response.Write(sw.ToString()); 改成 Response.Write(sw.ToString().Substring(sw.ToString().IndexOf("<table")))

# by 小強

黑暗大您好 我是使用System.IO.File.WriteAllText(targetPath + "/excel.xls", stringWrite.ToString(), Encoding.UTF8); 我確定 stringWrite裡面某個欄位的值字串為"0123456",但是匯出excel時 ,卻省略成123456,可見他把他看成數值了,不知黑暗大有什麼方法呢? 感激不盡

# by Jeffrey

to 小強,我知道在CSV中可以用="0123456"的技巧強制Excel將其視為字串(參考: http://blog.darkthread.net/post-2007-01-31-tips-excel-csv.aspx),應該可以如法泡製。

# by 小賤健

今天在用這招時,異想天開,在一長串數字組成的文字後,多加上 &nbsp;。竟然也可以過關,我在 O2K7 測是正確的,沒再被 Excel 以科學記號輸出。

# by Joseph

我已用了上述的方法, 而我的gridview內容只是文字, 沒有連結或按鈕等控制, 但為何開出excel時只用空白一片??

# by Jeffrey

to Joseph, 建議觀察sw.ToString()所傳回的HTML內容為何,應能找到一些線索。

# by KUN

在GridView1_RowDataBound 加入 for (int i = 0; i < e.Row.Cells.Count; i++) { e.Row.Cells[i].Attributes.Add("style", "vnd.ms-excel.numberformat:@"); } 就可以解決0050變成50的問題了.

Post a comment