前些天写了个导出Excel的公共类,因为项目中也需要上传Excel,没有利用Office组件,(PS:Office在上传文件时候,Excel进程无法关闭,会导致上传和打开失败)有的说利用Kill把进程给关闭,我总觉得这样的方法不好,没有任何原因,关闭一个进程,想想都有点悬。
这个例子是采用NPOI组件,下载地址:http://npoi.codeplex.com/,先不说组件的事,说下实现思路。
把类实体转换表格时候,最头疼莫过于表头和字段的设计,刚开始有几个同事利用解析Html,有几个导出功能就写几个导出函数,最后函数多了就直接构建一个类,这样功能也能在实现,但不是个很好的选择。
在类转为表的时候我们可以考虑利用反射功能把类的属性和值都取出来,这个的话,我们只需要构建一个函数,利用泛型把相应的类型传入就行。
下面我们可以定义一个函数的签名:
public static void ExportExcel<T>(List<T> tList) { }
好了,这样的话T就是我们要处理的类型,利用反射取出字段和值
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();//定义一个工作薄 XSSFSheet xssfSheet = (XSSFSheet)xssfWorkbook.CreateSheet("sheet1");//定义一个表单 NPOI.SS.UserModel.IRow titleRow = xssfSheet.CreateRow();//创建一个行 PropertyInfo[] propertyInfos = typeof(T).GetProperties();//取出字段数组 ;//定义索引 foreach (var propertyInfo in propertyInfos)//对数组进行遍历 { titleRow.CreateCell(index++).SetCellValue(propertyInfo. Name);//赋值 }
我们这样已经把字段取出来了,但是现现在的字段就类的字段,有的时候我们需要定义我们想要的表头,大多数的表头还是中文,还有的我也不要全部反射出来?这个怎么办?(解决方法在后面,千万不要放弃阅读)
好了,表头基本上已经搞定,还有一个悬念,后面我会说。下面是取出数据
], null);//取出第0 行数据 if (obj != null) { Row.CreateCell(index++).SetCellValue(obj.ToString());//设定到相应的列} else { index++; }
一个字段的值取出来了,下面加快工作,代码如下:
;j<tList.Count();j++)//传入的List遍历 { NPOI.SS.UserModel.IRow Row = xssfSheet.CreateRow(j+);//创建列 index = ;//重新设定索引 foreach (var propertyInfo in propertyInfos) { Object[] exportExcelAttributes = propertyInfo.GetCustomAttributes(true); ) { foreach (var exportExcelAttribute in exportExcelAttributes) { var attribute = exportExcelAttribute as ExportExcelAttribute; if (attribute != null) { object obj = propertyInfo.GetValue(tList[j], null);//取出数据 if (obj != null) { Row.CreateCell(index++).SetCellValue(obj.ToString());//设定到相应的列 } else { index++; } } } } } }
到目前已经把一个类实体转为一个Excel了,下面开始解决刚才的那个的疑问。有的表头是中文,有的我想设定的表头和字段不一样怎么办?还有字段我不想全部导出怎么办?
这个时候我们可以考虑使用Attribute (特性),我们可以自定一个特性。
所有特性类型都直接或间接地从 Attribute 类派生。 特性可应用于任何目标元素;多个特性可应用于同一目标元素;并且特性可由从目标元素派生的元素继承。 使用AttributeTargets 类可以指定特性所应用到的目标元素。 -----MSDN
这个话不读也没事,我们只需要第一句,我们要派生一个Attribute类,代码如下:
[AttributeUsageAttribute(AttributeTargets.All, Inherited = true, AllowMultiple = false)] public class ExportExcelAttribute:Attribute { private readonly string titleName; public ExportExcelAttribute(string name) { titleName = name; } public string TitleName { get { return titleName; } } }
代码简单,不多说。这样的话类实体也要修改下,先看下类实体的变化:
public class DriverInfo : BaseEntity { [ExportExcel("姓名")] public string name { get; set; } public string DriverInfoCode { get; set; } [ExportExcel("备注")] public string note { get; set; } [ExportExcel("身份证号")] public string IDCardNum { get; set; } public string DriverNum { get; set; }//驾驶证号 [ExportExcel("出生日期")] public DateTime? BirthDay { get; set; } [ExportExcel("性别")] public string Sex { get; set; } public string Location { get; set; }//归属地 }
看了就该有了三分明白了,我们要在需要导出的字段上加上 [ExportExcel("出生日期")],用来和其它字段区别,有这个特性的我们导出,按着后面给出的名称来设定。下面工作又要回到反射上面:
生成表头:
foreach (var propertyInfo in propertyInfos)//对数组进行遍历 { Object[] exportExcelAttributes = propertyInfo.GetCustomAttributes(true); ) { foreach (var exportExcelAttribute in exportExcelAttributes) { var attribute = exportExcelAttribute as ExportExcelAttribute; if (attribute != null) { titleRow.CreateCell(index++).SetCellValue(attribute.TitleName); } } } }
取值也是同样,代码如下:
;j<tList.Count();j++)//传入的List遍历 { NPOI.SS.UserModel.IRow Row = xssfSheet.CreateRow(j+);//创建列 index = ;//重新设定索引 foreach (var propertyInfo in propertyInfos) { Object[] exportExcelAttributes = propertyInfo.GetCustomAttributes(true); ) { foreach (var exportExcelAttribute in exportExcelAttributes) { var attribute = exportExcelAttribute as ExportExcelAttribute; if (attribute != null) { object obj = propertyInfo.GetValue(tList[j], null);//取出数据 if (obj != null) { Row.CreateCell(index++).SetCellValue(obj.ToString());//设定到相应的列 } else { index++; } } } } } }
这样的话每次要导出数据的时候,我们只需要修改类实体就行了,而不用去做不同的处理。到此,我们的工作完成一半,说好的导出呢?
下面可以来说下导出:说下思路,Excel构建完成后,先把它保存服务器上面的一个相对路径,然后通过Response.Flush();实现下载。
重构下函数签名:
public static void ExportExcel<T>(List<T> tList, HttpContext httpContext) { }
代码:
string DownPath=httpContext.Server.MapPath("~/DownloadPath");//服务器的相对径 MemoryStream memoryStream = new MemoryStream(); xssfWorkbook.Write(memoryStream); string fileName = DownPath + "\\" + DateTime.Now.ToString("yyyyMMddHHssmm") + "_"+typeof(T).Name + ".xlsx"; //定义文件名称和路径 File.WriteAllBytes(fileName, memoryStream.ToArray());//把内存内容写入文件
下面工作就是把文件读取,实现下载:
string fName = new FileInfo(fileName).Name; FileStream fs = new FileStream(fileName, FileMode.Open); byte[] bytes = new byte[(int)fs.Length]; fs.Read(bytes, , bytes.Length); fs.Close(); httpContext.Response.Charset = "UTF-8"; httpContext.Response.ContentEncoding = System.Text.Encoding.GetEncoding("UTF-8"); httpContext.Response.ContentType = "application/octet-stream"; httpContext.Response.AddHeader("Content-Disposition", "attachment; filename=" + fName); httpContext.Response.BinaryWrite(bytes); httpContext.Response.Flush(); httpContext.Response.End();//下载 if (File.Exists(fileName))//完成后删除 { File.Delete(fileName); }
</pre><pre code_snippet_id="371974" snippet_file_name="blog_20140531_9_5695766" name="code" class="csharp">
导出结果:
到此,这个功能算是写完了,我制作个Demo下载:
http://download.csdn.net/detail/fw199006/7430623
有兴趣加群 IT项目 324110381