轉載自:玄魂的思想的部落格 一次艱難的ASP.NET產生Excel檔案的曆程總結
統計系統的最後一項任務是匯出資料到Excel,由於是第一次做,邊學邊搞,真費了一番周折,完成之後趕緊做了個總結。
開發環境如下:
作業系統:Windows7
開發工具:vs2010
.NET版本:4.0
伺服器系統:Windows2003
office版本:2003
本文主要涉及的問題:
1) 產生Excel檔案的方法
2)合併儲存格與數字列標識轉字母列標識
3)"異常來自 HRESULT:0x800A03EC "的原因
4)"對象為空白"異常和伺服器端部署
5)"80080005異常"與許可權配置
一.產生Exel的方法
產生Excel的方法為調用本地Office COM組件,操作Excel。建立項目後,添加對應Office版本的Microsoft.Office.Interop.Excel的引用,1-1所示。
圖1-1 添加Microsoft.Office.Interop.Excel引用
為方便起見,這裡以一個樣本程式說明我的過程。樣本程式整體結構如代碼清單1-1所示。
代碼清單1-1 樣本程式結構
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Excel=Microsoft.Office.Interop.Excel;
namespace excel
{
class Program
{
private Excel.Application app = null;
private Excel.Workbook workbook = null;
private Excel.Worksheet worksheet = null;
private Excel.Range workSheet_range = null;
static void Main(string[] args)
{
}
public void createExcel()
{
}
public void addData(int row, int col, string data, string format,string cell1,string cell2)
{
}
public void export()
{
}
}
}
首先我先聲明了四個欄位,分別解釋如下:
1)Excel.Application app。Application對象表示Excel應用程式本身。Application對象公開了大量有關正在啟動並執行應用程式、應用於該執行個體的選項以及在該執行個體中開啟的目前使用者的對象的資訊。
2)Excel.Workbook workbook。Excel.Workbook類表示Excel應用程式中的單個活頁簿。
3)Excel.Worksheet worksheet。Excel提供Sheets集合作為 Microsoft.Office.Interop.Excel.Workbook 對象的屬性,但是Excel中沒有Sheet類。相反,Sheets 集合的每個成員都是一個Microsoft.Office.Interop.Excel.Worksheet對象,或者是一個 Microsoft.Office.Interop.Excel.Chart對象。
4)Excel.Range workSheet_range。Excel.Range對象是Excel應用程式中最常用的對象。在能夠處理Excel內的任何範圍之前,必須將它表示為Range對象,並處理該對象的方法和屬性。Range對象表示一個儲存格、一行、一列、包含一個或多個儲存格塊(可以連續,也可以不連續)的儲存格選定範圍,甚至多個工作表中的一組儲存格。
createExcel()方法用來建立Excel執行個體,也就是初始化聲明的四個變數,實現代碼如代碼清單1-2所示。
代碼清單1-2 初始設定變數
public void createExcel()
{
app = new Excel.Application();
app.Visible = true;
workbook = app.Workbooks.Add(1);//建立workbook
worksheet = (Excel.Worksheet)workbook.Sheets[1];//建立worksheet
}
如代碼清單1-2,Workbook對象由Application對象建立,Worksheet對象由Workbook對象建立。
addData()方法用來向表格中添加資料,四個參數分別為行、列、資料、資料格式,範圍起始格、範圍結束格。實現代碼如代碼清單1-3所示。
代碼清單1-3 添加資料
worksheet.Cells[row, col] = data;
workSheet_range = worksheet.get_Range(cell1, cell2);
workSheet_range.Borders.Color = System.Drawing.Color.Black.ToArgb();
workSheet_range.NumberFormat = format;
Excel中的每一個格以行和列組成的座標來唯一標識,這裡行標識是數字,從1到n;列標識是字母,從A到Z,從AA到AZ……。
workSheet_range對象由worksheet.get_Range(cell1, cell2)方法來初始化,從cell1到cell2會被作為一個整體處理。workSheet_range.NumberFormat設定該地區內的字元格式設定化方式。
export()方法用來匯出產生的Excel,實現如代碼清單1-4所示。
代碼清單1-4 匯出Excel
public void export()
{
workbook.SaveCopyAs(@"D:/aa.xls");
app.Quit();
}
Workbook對象由三個方法用來儲存Excel檔案,分別為Save方法、SaveCopyAs方法和SaveAs方法,這裡使用SaveCopyAs方法。儲存之好調用Application對象的Quit()方法釋放資源。
在本地測試一切正常,測試結果1-2所示。
圖1-2 測試結果
二.合併儲存格與數字列標識轉字母列標識
合併儲存格的時候,必須要傳遞以字母表示的列標識給Excel.Range對象的.Merge(int)方法,但是我們取資料和填充資料使用的都是數位識別碼,這時候必須轉成1-3的形式。
圖1-3 Excel的字母列標識
代碼清單1-5提供的演算法可解決這個問題。
代碼清單1-5 數字列標識轉字母列標識
public string ToName(int index)
{
if (index <= 0)
throw new Exception("invaild parameter");
index--;
List<string> chars = new List<string>();
do
{
if (chars.Count > 0)
index--;
chars.Insert(0, ((char)(index % 26 + (int)'A' )).ToString());
index = (int)((index - index % 26) / 26);
} while (index > 0);
return String.Join(string.Empty, chars.ToArray());
}
三. "異常來自 HRESULT:0x800A03EC "的原因
一不小心就會出"異常來自 HRESULT:0x800A03EC "的異常,原因很簡單,Excel的行列都是從1開始的,如果我們習慣性的給worksheet.Cells[0, 0]或者worksheet.Cells[1, 0]或者worksheet.Cells[0, 1]賦值,就會引發該異常。
四. "未將對象引用設定到對象執行個體"和伺服器端部署
用戶端一切運行正常,接下來就是要把DLL部署到伺服器上。可是伺服器根本沒用安裝Office,在網站找了很久,也沒找到此種方法的解決方案。無奈就安裝了Office,本來以為萬事大吉,但是每次都提示"未將對象引用設定到對象執行個體"。採用拋異常的方法才知道樣本程式中的幾個主要欄位都為空白,那就是本地的COM根本沒調用成功。找到http://www.cnblogs.com/Mainz/archive/2009/11/11/microsoft_office_interop_excel.html文章,於是乎又進行了如下操作。
從伺服器上把EXCEL.EXE拷到了本機上,然後從VS2010的命令列啟動TlbImp,執行命令:TlbImp /out:Interop.Excel.dll Excel.exe。產生Interop.Excel.dll。
在項目中去除了Microsoft.Office.Interop.Excel.dll的引用,添加Interop.Excel.dll,將頂部的using Excel=Microsoft.Office.Interop.Excel,該為using Excel= Interop.Excel,本地運行程式沒有問題。再次補救伺服器端程式,結果又出了新的異常:檢索 COM 類別工廠中 CLSID 為 {00024500-0000-0000-C000-000000000046} 的組件時失敗,原因是出現以下錯誤: 80080005。
五. "80080005異常"與許可權配置
"80080005異常"是應用程式對COM組件操作許可權不足引起的。於是有了下面的操作:
1)控制台->管理工具->元件服務->電腦->我的電腦->DCOM-> Microsoft Excel 應用程式
2)單擊屬性開啟此應用程式的屬性對話方塊。
3) 單擊標識選項卡,然後選擇互動式使用者。
4) 單擊預設安全性選項卡。設定當前伺服器與ASP.NET相關的使用者的存取權限。
5)單擊啟動許可權的編輯預設值。設定ASP.NET相關的使用者的存取權限。
至此,程式才運行成功,可謂一波三折。
六.關閉Excel進程
如果使用這種方式使用者每匯出一個Excel檔案,伺服器端就會啟動一個Excel進程。這回引起兩個問題,一是耗費伺服器資源,二是當進程數達到上限時,會引發異常,調用COM失敗。這個時候要想辦法結束Excel進程。我採用了殺進程的方式,類似下面的做法:
private void DoExcel()
{
Microsoft.Office.Interop.Excel.Application application = new Microsoft.Office.Interop.Excel.Application();
//這裡釋放所有引用的資源
application.Quit();
KillExcel(application);
}
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hwnd,out int ID);
public static void KillExcel(Microsoft.Office.Interop.Excel.Application excel)
{
IntPtr t = new IntPtr(excel.Hwnd); //得到這個控制代碼,具體作用是得到這塊記憶體入口
int k = 0;
GetWindowThreadProcessId(t, out k); //得到本進程唯一標誌k
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(k); //得到對進程k的引用
p.Kill(); //關閉進程k
}