Asp.net生成Excel报表

    技术2025-01-16  16

      测试环境:windows2003,MSSQL2000,IIS6.0,Asp.net2.0

     需求:根据用户查询条件将满足要求的信息查询出来生成xls文件供用户下载.       1.使用Excel组件生成xls文件,不采用CSV文件(逗号分割文件).       2.下载操作时采用ajax方式进行,需要提示界面.       3.需要将下载后的文件删除.

      设计的过程:       1.使用Excel软件设计一个空的模版文件(temp.xls)被用.       2.每次生成时,将temp.xls拷贝一份到临时目录,文件名称随机生成(ran.xls).       3.根据用户提交的条件将数据填充到ran.xls文件中.       4.下载ran.xls文件.       5.将临时目录中的ran.xls文件删除.

    分析:A.过期文件删除处理:     针对第5点,一般可以使用定时清理程序根据文件生成时间进行清理(比方一天前的文件则删除掉),不过考虑到生成的xls文件一般在100KB以内,而且这个导出操作不很频繁(只对VIP用户提供),那么这里采用下载后马上删除的方式来实现,开始尝试了如下下载代码://=======注意***以下代码不能得到预期效果==============        Response.TransmitFile(filename);        //重载WriteFile(filename,true)是把整个文件读入内存再输出,跟下面的BineryWrite(byte[])表现是一样的        //Response.WriteFile(filename);//部分缓存文件        //即使加了下面两句,程序的表现也是不稳定的        //Response.Flush();        //Response.Close();        System.IO.File.Delete(filename);

    //=========End==========

    参考(http://forums.asp.net/t/1031479.aspx,)提到的在每次使用TransmitFile发送文件时,调用一次过期文件删除进程(按文件生成时间).考虑到TransmitFile能稳定的发送大文件,实际应用时建议按这种方式实施.

    针对当前项目,这里采用一次性读入内存再逐步输出的方式进行//===============目前采用的方式============

    Response.ContentType = "application/ms-excel";Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(savefilename, Encoding.GetEncoding("utf-8")));Response.BinaryWrite(File.ReadAllBytes(filename));//直接将输出写入Response.Flush();System.IO.File.Delete(filename);

     B.使用ajax方式下载问题:     用户点下载按钮后将下载按钮设置为不可用(防止多次点击),并且显示一个处理中的gif动画.首先碰到的问题是使用ajax方式无法弹出文件下载对话框,后来找到使用iframe的形式来实现,相关的jquery代码大概是这样的,$("#frmDownload").attr("src","download.ashx?条件");.    起初是将生成Excel表格,发送下载文件做在一个过程里面的,使用iframe方式时,从点了下载按钮后到弹出文件下载对话框为止,对应服务器的处理时间,这段时间内客户端需要将下载按钮butOK禁用,然后显示一个gif,当弹出下载对话框时,再将按钮恢复并且隐藏gif动画,尝试了在ifrmae.onload里捕获下载框弹出事件,结果发现iframe里如果加载的是html页面时会触发,但是当是文件下载时就没有触发,估计是文件下载时IE解释器不生成DOM模型,当然也就没有JS事件了.

      目前解决方法:   把Excel生成跟服务器发出下信号(指上面下载代码那一部分)分开,即首先客户端发出请求,服务器收到后生成excel文件然后返回一段json格式的信息(成功与否,以及excel文件地址),客户端收到成功信息后隐藏gif动画,恢复按钮可用,然后设置iframe.src="下载地址",这样一来就解决了上面的问题.  --另外可以考虑,照样使用iframe.onload事件来处理excel生成结束事件(隐藏动画,恢复按钮),不过服务器应该返回是一个包含 src="下载地址" >的页面,即iframe里再套个iframe, 这个没测试过想来应该可性,这样就不需要将excel处理跟下载分成两步了.

     参考代码如下:///****************ajax下载过程*************************

     

           $("#butOK").click(function(){         $("#butOK").attr("disabled",true);         $("#tip").html("正在处理请求...");         $.getJSON("download.ashx?m=1",function(data){           //这里是演示,实际应用应该根据data里的信息来决定下一步操作           $("#frmDownload").attr("src","download.ashx?m=2");//附加到iframe下载           $("#butOK").attr("disabled","");           $("#tip").html("");         });

                     });//********************代码结束******************

     C.Excel报表生成问题:

       首先这个报表包括表头(第一行是神鹰医药招商网--代理信息目录,第二行是http://www.gyzs.net/ 导出时间:******),第三行是字段名称,而且表格使用了样式以便看上去更加美观,这样一来使用OLEDB方式的Insert语句(按数据库方式处理)就会出错,Insert好象将第一行是字段,第二行就是数据行了,也不知道有没有办法解决.

      另外朋友提供了一个服务端导出xml格式的数据,然后在客户端使用VBA(Js操作Excel组件)生成表格的方式,可以避免在服务器上装Excel程序,配置权限,Excel注销(回收内参)等一系列问题,不过需要对Excel进行格式化(能否下在服务器xls模版再进行操作?),用户当前电脑Excel版本是否会对Js编写有要求,在Js中使用Excel组件是否有权限以及安全方面的问题等, 多需要逐一测试.

     

      目前解决方法:

       使用Miscrosoft Excel 10.0 Object Library (Office 2002)组件,(需要在服务器上安装并配置权限)//*****************写excel文件参考代码**********************

                System.Data.DataTable dt = LoadData();//加载符合条件的数据

                Object miss = System.Reflection.Missing.Value;            Excel.Application ExcelApp = new Excel.ApplicationClass();            Excel.Workbooks wbooks = (Excel.Workbooks)ExcelApp.Workbooks;            Excel.Workbook wbook = wbooks.Open(Path, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss);            //获得第一个工作薄            Excel.Worksheet wsheet = (Excel.Worksheet)wbook.Worksheets.get_Item(1);            ExcelApp.Visible = false;            ExcelApp.DisplayAlerts = false;            wsheet.Cells[2, 1] = string.Format("信息来源:神鹰医药网〖http://www.gyzs.net/〗 导出时间:{0}", DateTime.Now.ToString("yyyy-MM-dd"));

                //填充具体的单元格            //逐个填充            //for (int i = 0; i < dt.Rows.Count; i++)            //{            //    for (int j = 1; j <= 6; j++)            //    {            //        wsheet.Cells[i + 4, j] = dt.Rows[i][j - 1];            //    }            //}            //使用Rang填充            object[,] arr = new object[dt.Rows.Count, dt.Columns.Count];            for (int iRow = 0; iRow < dt.Rows.Count; iRow++)            {                for (int iCol = 0; iCol < dt.Columns.Count; iCol++)                {                    arr[iRow, iCol] = dt.Rows[iRow][iCol];                }            }            Excel.Range range = wsheet.get_Range(wsheet.Cells[4, 1], wsheet.Cells[dt.Rows.Count + 4 - 1, dt.Columns.Count]);            range.set_Value(miss, arr);            ReleaseObject(range);            //填充结束            wbook.Close(true, miss, miss);            ExcelApp.Quit();

                ReleaseObject(wsheet);            ReleaseObject(wbook);            ReleaseObject(wbooks);            ReleaseObject(ExcelApp);

                GC.Collect();            GC.WaitForPendingFinalizers();

     

    //*******************结束***********************  说明:Excel 首先是Applicate对象,代表了Excel应用程序,一个Applicate对应多个WorkBook(集合WorkBooks),一个WorkBook包含多张WorkSheet(工作表),每张表包括多个Cell(单元格式), 上面代码注释了逐个单元格填充数据的方式,目前使用的是按Range按区域(如A1-C3有9个单元格A1表示左上角的哪个,上面使用Get_Range(Cell[LRow,LCol],Cell[RRow,RCol])获取需要填充的区域,而Object[,] arr是一个二纬数组,这些数据将被填充到这个区域(Range)里. 使用区域填充比逐个填充速度要快,3-4倍(我这里测试的信息)

    附加部分:在客户端使用ActiveX方式将Table内容导入到Excel表格里

    //*******需要权限,并且用户安装了合适版本的office

    // 指定页面区域“单元格”内容导入Excelfunction CellAreaExcel(objid) { var oXL = null; var oWB = null; var oSheet = null; try {  oXL = new ActiveXObject("Excel.Application"); } catch (e) {  alert("Excel对象无法创建");  return; } try {  oWB = oXL.Workbooks.Add();  oSheet = oWB.ActiveSheet;  oSheet.Cells.NumberFormatLocal = "@";  var Lenr = gridlist.rows.length;  for (var i = 0; i < Lenr; i++) {   var Lenc = $id(objid).rows(i).cells.length;   for (var j = 0; j < Lenc; j++) {    if (i == 0) {     oSheet.Cells(i + 1, j + 1).Font.Bold = true;     oSheet.Cells(i + 1, j + 1).ColumnWidth = $id(objid).rows(i).cells(j).clientWidth / 7;    }    oSheet.Cells(i + 1, j + 1).value = $id(objid).rows(i).cells(j).innerText;   }  }  oXL.Visible = true; } catch (e) {  alert("导出到excel出错"); }

     oXL.Quit(); oXL = null; oWB = null; oSheet = null; idTmr = window.setInterval("Cleanup();", 1);}

     

    最新回复(0)