[转] 导出Excel时, 迅雷下载页面儿不下载文件的问题. --- 转自: http://wenku.baidu.com/view/0860012ee2bd960590c6778a?fr=prin
这里采用的是在服务端先生成Excel文件,然后利用文件地址下载的方法。
生成Excel文件的方法,见:【原】.Net创建Excel文件(插入数据、修改格式、生成图表)的方法
先试用Response.WriteFile的方法:
FileInfo fi = new FileInfo(excelFile);//excelFile为文件在服务器上的地址
HttpResponse contextResponse = HttpContext.Current.Response;
contextResponse.Clear();
contextResponse.Buffer = true;
contextResponse.Charset = "GB2312"; //设置了类型为中文防止乱码的出现
contextResponse.AppendHeader("Content-Disposition", String.Format("attachment;filename={0}", excelName)); //定义输出文件和文件名
contextResponse.AppendHeader("Content-Length", fi.Length.ToString());
contextResponse.ContentEncoding = Encoding.Default;
contextResponse.ContentType = "application/ms-excel";//设置输出文件类型为excel文件。
contextResponse.WriteFile(fi.FullName);
contextResponse.Flush();
contextResponse.End();
其中第一行的excelFile为Excel文件在服务器上的地址,比如:“C:\Website\Excel\xx.xlsx”。
这种方法也是网上一般提供的方法,但在实际操作中,却出现了意向不到的问题:
在Chrome下
一切正常,Excel文件直接下载到Chrome的默认下载文件夹中。
在Firefox下
由于安装了FlashGot插件,会先选择应用的下载工具:
在这里显示是正常的,如果选择“保存文件”,Excel文件也会被保存到默认文件夹中,但如果试用第三方下载工具,比如迅雷,会出现如下窗口:

注意到网址一栏,会在页面实际地址后添加ViewState信息,而另存名称也不是Excel文件本身的名称,而是页面的名称。
点击确定后,被下载的文件又变成了实际文件(有时会先变成.zip文件,再变为实际文件)
在IE7下
会先弹出保存对话框,文件正常,同样因为装了迅雷的缘故,点保存时,弹出迅雷的下载对话框,和Firefox下不同,网址后面没有ViewState信息。

点确定,下载的则是页面文件:
如果在迅雷的下载对话框中点取消,则会使用IE的下载,这里的文件又是正确的了:
怀疑迅雷是根据下载对话框中的网址重新请求下载,与发起请求的页面已经无关,而IE又不会把ViewState信息传到迅雷中,导致下载的文件不是想要的Excel页面。
之后又尝试了分段下载的方式,其实也是无效的,因为迅雷根本不理会你提供给它的下载机制,而且这样在Firefox下调用迅雷时,由于分段下载的Viewstate并不包含Excel文件的完整信息,迅雷下载下的也是残缺的文件。
最后只能采用最老土的解决方法:Response.Redirect(),转向实际文件地址。
FileInfo fi = new FileInfo(excelFile);
HttpResponse contextResponse = HttpContext.Current.Response;
contextResponse.Redirect(string.Format("~/Template/{0}", excelName), false);
这样在三个浏览器下测试都正常了,因为请求的是实际文件的地址,在迅雷中显示的也是实际文件的地址。下载就不会出现问题。但这样相当于告知客户端用户文件的实际地址,隐私性不佳。但好在这里并不需要太好的隐私性,而且文件会在一定时间之后删除,所以倒并不是太大的问题了。
上面是第一次考虑的结果,似乎还是有些懒了……
事后考虑,既然每次迅雷实际都是重新请求URL,那么我们就应该给迅雷传入一个能生成Excel文件的URL。
即,在点击“生成Excel”按钮的时候,转向另一个Export页面,在这个页面的Page_Load方法中完成生成Excel文件、下载Excel文件的步骤。
String fileName = Request.QueryString["FileName"];
String exportName = Request.QueryString["Export"];
if(fileName != null)
{
ExportManger.CreateExcel(fileName);//先在服务器端创建Excel文件。
Response.Redirect(String.Format("{0}?Export={1}",Request.Path.ToString(),fileName));//重定向到本页面,但Query参数变为Export。
}
else if(exportName != null)
{
ExportManger.ExportExcel(exportName);//下载Excel文件。
}
这里页面跳转了两次,第一次是生成Excel,第二次是下载Excel。
之所以跳转两次,是因为迅雷会捕获最后的URL,如果生成和下载放在一起进行,那么迅雷下载时会重复再生成一遍Excel文件。下载Excel文件的代码ExportManger.ExportExcel(exportName)就使用了本文开头介绍的Response.Write方法,也可以用分段下载的方法:
if(fi.Length > 0)
{
FileStream sr = new FileStream(fi.FullName,System.IO.FileMode.Open,System.IO.FileAccess.Read, System.IO.FileShare.Read);
int size = 1024;//设置每次读取长度。
for (int i = 0; i < fi.Length / size + 1; i++)
{
byte[] buffer = new byte[size];
int length = sr.Read(buffer, 0, size);
contextResponse.OutputStream.Write(buffer, 0, length);
}
sr.Close();
}
else
{
contextResponse.WriteFile(fi.FullName);
}
这里的结果是只生成了一次Excel并在服务器保留,以后每次下载的时候都使用带"Export"的参数下载相同的文件。那么如果需要文件只是一次性的,每次下载都需要重新生成,则只需要把Export页面的下载和生成放到一起。然后把开头的Response.Write方法最后变成:
contextResponse.Flush();
fi.Delete();
contextResponse.End();
即每次响应清空后把文件先删除,再结束响应。
这样就解决了利用下载工具出现的下载不能的问题,同时保护了服务器文件地址的隐私,并可以采用分段写入的方法写入大文件,而且可以按需要即时删除生成的文件而不占用服务器空间
1.添加Excel引用
可以在.Net选项卡下添加Microsoft.Office.Interop.Excel引用,或在COM下添加Microsoft Excel 12.0 Object Library。它们都会生成Microsoft.Office.Interop.Excel.dll。
2.创建Excel。
有两种方法创建一个Excel Workbook实例。
1.需要一个模板文件,使用Open方法,参数较多:
object miss = Missing.Value;
Application excelApp = new Application();
excelApp.Workbooks.Open(TemplateName, miss, true, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss, miss);
其中Open方法的第一个和第三个参数是模板名称(路径)和是否只读。其余参数一般不需要设置。这里将原文件设为只读,因为不会对模板文件进行修改。
2.不需要模板文件,使用Add方法,只需要一个参数:
object miss = Missing.Value;
Application excelApp = new Application();
excelApp.Workbooks.Add(miss);
3.向Excel中插入数据表
插入数据表的方法比较简单,使用之前生成的WorkBook中的WorkSheet,向里面添加二维数据,需要调用WorkSheet接口的get_Range方法获取插入区域,然后通过Value2(忽略格式)赋值。
Worksheet workSheet= (Worksheet)excelApp.Worksheets[2];
int rowCount = 20;
int colCount = 5;
object[,] dataArray = new object[rowCount, colCount];
Random rand = new Random(DateTime.Now.Millisecond);
for(int i = 0; i < rowCount ;i++)
{
for(int j=0;j<colCount;j++)
{
dataArray[i, j] = i+j;
}
}
workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[rowCount, colCount]).Value2 = dataArray;
workSheet = null;
第一行,直接用excelApp中的Worksheet,是因为默认的Workbook是Workbooks[1],也就是直接取Workbooks[1]中的Worksheets放入Application对象的Worksheets属性中。
倒数第二行,get_Range方法中的两个参数分别是要插入数据区域的起始和中止坐标(左上角坐标为【1,1】)。这里直接将二维数组插入表格比一点一点插入每个格子效率高些。
4.修改Excel表格样式
设置表格样式主要是设置Range类对象的属性。和插入数据类似,通过Worksheet的get_Range方法获得需要设置样式的区域,设置相应的属性改变样式。
Range range = workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, colCount]);
range.Interior.Color = 255 ; //设置区域背景色。
range.Font.Bold = true; //设置字体粗体。
range.BorderAround(XlLineStyle.xlContinuous, Microsoft.Office.Interop.Excel.XlBorderWeight.xlThick, Microsoft.Office.Interop.Excel.XlColorIndex.xlColorIndexAutomatic, 15); //设置区域边框
几乎所有可以通过图形界面对Excel格式的设置,包括公式、排序等都可以在这里设置。不过由于属性和方法没有详细的说明,所以需要一点点慢慢摸索……
5.向Excel中插入图表
插入图表则主要是操作ChartObject对象和Chart对象。
//设置图表大小。
ChartObject chartObj = charts.Add(0, 0, 400, 300);
Chart chart = chartObj.Chart;
//设置图表数据区域。
Range range = workSheet.get_Range("A1", "E10");
chart.ChartWizard(range, XlChartType.xl3DColumn,miss,XlRowCol.xlColumns, 1, 1, true, "标题", "X轴标题", "Y轴标题", miss);
//将图表移到数据区域之下。
chartObj.Left = Convert.ToDouble(range.Left);
chartObj.Top = Convert.ToDouble(range.Top) + Convert.ToDouble(range.Height);
其中设置图表区域比较关键,区域中包含了标题行。ChartWizard的第二个参数指明了图表的类型,第四个参数指明了以行还是列的值作为一个数据系列,第五个参数和第六个参数则指明分别作为横轴坐标和系列名称的单元格。
以Line类型图表为例,数据如下:
|
0 |
1 |
2 |
3 |
4 |
|
1 |
2 |
3 |
4 |
5 |
|
2 |
3 |
4 |
5 |
6 |
|
3 |
4 |
5 |
6 |
7 |
|
4 |
5 |
6 |
7 |
8 |
|
5 |
6 |
7 |
8 |
9 |
|
6 |
7 |
8 |
9 |
10 |
|
7 |
8 |
9 |
10 |
11 |
|
8 |
9 |
10 |
11 |
12 |
|
9 |
10 |
11 |
12 |
13 |
那么根据第四个参数的不同会有两种不同图表:
chart.ChartWizard(range, XlChartType.xlLine,miss,XlRowCol.xlRows, 1, 1, true, "标题", "X轴标题", "Y轴标题", miss);
在PlotBy Row的时候,会以一行的数据为一个数据系列画一条线,并把第每行的一列值作为这一条线的标题,而把第一行的每一列作为该线的横坐标。
chart.ChartWizard(range, XlChartType.xlLine,miss,XlRowCol.xlColumns, 1, 1, true, "标题", "X轴标题", "Y轴标题", miss);
在PlotBy Column的时候,会以一列的数据为一个数据系列画一条线,并把第每列的一行值作为这一条线的标题,而把第一列的每一行作为该线的横坐标。
这两种模式相同点就在于,它们都是以坐标格内的值作为纵坐标的。这里需要注意的是,如果第五和第六个参数改为2,并不是取行的第二列或列的第二行作为标题,而是取前两行或前两列,比如:
chart.ChartWizard(range, XlChartType.xlLine,miss,XlRowCol.xlColumns, 2, 0, true, "标题", "X轴标题", "Y轴标题", miss);
由于系列标题设置为0,所以使用了默认的“系列X”作为标题,而横坐标则取了每列的前两行,所以共有三条线,每条线上10个点。
6.保存Excel
保存前,需要先刷新,使新的记录能被记下:
Workbook workBook = excelApp.Workbooks[1];
workBook.RefreshAll();
之后的保存,也有两种方法:
1.直接保存,当之前通过Open方法创建Excel文件,并没有设为只读时,可以用这种方法,比较简单:
Workbook workBook = excelApp.Workbooks[1];
workBook.Save();
2.这一种方法的比较灵活,就是SaveAs(),相当于界面操作的另存为,但这个方法的问题和创建Workbook时的第一种方法一样,参数比较多,虽然大部分可以用miss。
Workbook workBook = excelApp.Workbooks[1];
object miss = Missing.Value;
workBook.SaveAs(path, miss, miss, miss, miss, miss, XlSaveAsAccessMode.xlNoChange, miss, miss, miss, miss, miss);
保存之后要关闭WorkBook:
workBook.Close(false, miss, miss);
workBook = null;
7.最后需要清空内存
excelApp.Quit();
excelApp = null;
GC.Collect();
================================================
当从浏览器返回一个文件时,需要指定ContentType,以下是Office2007对应的值:
"application/vnd.openxmlformats-officedocument.wordprocessingml.template" (for .dotx files)
"application/vnd.openxmlformats-officedocument.presentationml.presentation" (for .pptx files)
"application/vnd.openxmlformats-officedocument.presentationml.slideshow" (for .ppsx files)
"application/vnd.openxmlformats-officedocument.presentationml.template" (for .potx files)
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" (for .xlsx files)
"application/vnd.openxmlformats-officedocument.spreadsheetml.template" (for .xltx files)
相对于Office2003是这样的
Response.ContentType = "application/vnd.ms-excel
===========================================
为了避免, 谷歌浏览器下, 会关闭当前页面. 采用新窗口来Redirect.
Response.Redirect在新窗口打开 && 3.0扩展方法
Response.Rederect在默认情况下是在本页跳转,所以除了在js中用window.open 或是给A标签添加target属性之外,在后台似乎不能来打开新的页面,其实不然,通过设置form的target属性同样可以让Response.Rederect所指向的url在新的窗口打开。下面用三种方法来实现。
1 .给form指定target属性,那么本页面中所有的Response.Rederect都将在新的窗口中打开。代码如下:
protected void Page_Load(object sender, EventArgs e)
{
form1.Target = "_blank";
}
或
<form id="form2" runat="server" target="_blank">
2 .用脚本针对某个控件来指定form的target,代码如下:
html代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="ResponseRedirectDemo._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>ResponseRedirectDemo</title>
</head>
<body>
<form id="form2" runat="server" target="_blank">
<div>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
Text="OpenNewWindow"/>
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click"
Text="OpenOldWindow" />
</div>
</form>
</body>
</html>
C#代码:
namespace ResponseRedirectDemo
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Button1.Attributes.Add("onclick", "this.form.target='_blank'");
Button2.Attributes.Add("onclick", "this.form.target=''");
}
protected void Button1_Click(object sender, EventArgs e)
{
Response.Redirect("http://oec2003.cnblogs.com");
}
protected void Button2_Click(object sender, EventArgs e)
{
Response.Redirect("http://oec2003.cnblogs.com");
}
}
}
上面的代码中点击button1在新窗口打开,点击button2在本页打开。
3 .除了设置form的target属性,要在新的窗口打开页面就只能用open,可以写个通用的方法来实现,如下:
public class RedirectHelper
{
public static void Redirect(string url,
string target, string windowFeatures)
{
HttpContext context = HttpContext.Current;
if ((String.IsNullOrEmpty(target) ||
target.Equals("_self", StringComparison.OrdinalIgnoreCase)) &&
String.IsNullOrEmpty(windowFeatures))
{
context.Response.Redirect(url);
}
else
{
Page page = (Page)context.Handler;
if (page == null)
{
throw new
InvalidOperationException("Cannot redirect to new window.");
}
url = page.ResolveClientUrl(url);
string script;
if (!String.IsNullOrEmpty(windowFeatures))
{
script = @"window.open(""{0}"", ""{1}"", ""{2}"");";
}
else
{
script = @"window.open(""{0}"", ""{1}"");";
}
script = String.Format(script, url, target, windowFeatures);
page.ClientScript.RegisterStartupScript(page.GetType(),
"Redirect", script, true);
}
}
}
这样就可以在程序中使用RedirectHelper.Redirect("oec2003.aspx", "_blank", ""); 第三个参数为open窗口的一些属性 。但这样好像还不是很方便,在.net3.5中提供了扩展方法的特性,在这里也可以借用一下,将上面的静态方法实现为Response.Redirect的一个重载。具体代码如下:
public static class RedirectHelper
{
public static void Redirect(this HttpResponse response,
string url, string target, string windowFeatures)
{
if ((String.IsNullOrEmpty(target) ||
target.Equals("_self", StringComparison.OrdinalIgnoreCase)) &&
String.IsNullOrEmpty(windowFeatures))
{
response.Redirect(url);
}
else
{
Page page = (Page)HttpContext.Current.Handler; if (page == null)
{
throw new
InvalidOperationException("Cannot redirect to new window .");
}
url = page.ResolveClientUrl(url);
string script;
if (!String.IsNullOrEmpty(windowFeatures))
{
script = @"window.open(""{0}"", ""{1}"", ""{2}"");";
}
else
{
script = @"window.open(""{0}"", ""{1}"");";
}
script = String.Format(script, url, target, windowFeatures);
ScriptManager.RegisterStartupScript(page,
typeof(Page), "Redirect", script, true);
}
}
}
将该类添加到项目中后,在程序中输入Response.Redirect会发现该方法有三个重载了,这样再结合前面的form的target 就非常方便了。
浙公网安备 33010602011771号