通用的导出数据到 Excel 文件的通用方法
使用枚举、特性、反射和泛型将数据导出到 Excel 文件
引言
本文介绍了如何在表示层使用泛型
和枚举反射
,将业务逻辑层的数据映射并导出为 Excel 报表。
背景
将 Excel 报表导出到客户端是很常见的。简单的方法是获取gridview
的导出数据,然后将其转换为 Excel 并返回给客户端。但是,当您禁用gridview viewstate
或使用没有viewstate
的 Ajax 来提高应用程序性能时,您需要重新从数据库查询数据并生成 Excel 报表。
没有像gridview DataSource
和DataBind
那样的直接生成 Excel 文件的方法。本文将介绍一种简单的方法,通过使用泛型
、反射
、枚举
和特性
,允许您将任何查询结果导出到 Excel 文件。
分步指南
步骤 1
设置您想要导出 Excel 报表的页面,使其继承自ExportExcelPage<T>
页面,并使用枚举
类型。我将在下面讨论这个基页和泛型类型。例如,如果您有一个名为OrderHistory
或SalesInformation
的页面,它们将被声明为:
public class OrderHistory : ExportExcelPage<OrderHistory.ExportFields>
public class SalesInformation: ExportExcelPage< SalesInformation.ExportFields>
ExportFields
是一个枚举
,用于映射业务逻辑层中用于绑定到导出字段的属性。
#region Enums
//All enum members name are copied from BLL class properties name
//to do mapping except those with [Description(CUSTOM_COL)].
//HearderTxt attribute excel columns title.
//Description attribute for customizing result format in excel.
public enum ExportFields
{
[HearderTxt("lbl_OrderID")]OrderID,
[HearderTxt("lbl_CreateDate")]OrderCreateDate,
[HearderTxt("lbl_TotalPrice")][Description(PRICE_COL)]Price,
[HearderTxt("lbl_LineNum")]LineNumber,
[HearderTxt("lbl_Manufacturer")]Manufacturer,
[HearderTxt("lbl_MfgPartNo")]MfgPartNo,
[HearderTxt("lbl_OrderQty")][Description(QTY_COL)]OrderQty,
[HearderTxt("lbl_UnitPrice")][Description(PRICE_COL)]UnitPrice,
[HearderTxt("lbl_Custom")][Description(CUSTOM_COL)]CustomFieldName,
......
}
#endregion
HearderTxt
是一个特性类,用于本地化您的 Excel 列标题。Description
是一个内置的特性类,允许您自定义结果格式。
将您想导出到 Excel 的 BLL 类的所有属性复制粘贴过来很容易。对于一些特殊列,只需自定义名称,并在代码中使用[Description(CUSTOM_COL)]
特性进行处理。
第二步
构建一个继承自Attribute
类的HearderTxtAttribute
。这很简单,只需从资源文件获取本地化列标题,或直接返回您想设置的标题。
public class HearderTxtAttribute : Attribute
{
public string HeaderTxt { get; protected set; }
public HearderTxtAttribute(string header) {
string headerStr = string.Empty;
//localize header
headerStr = Global.YourGetResourceMethod(header);
//if not found in resource file, use the value that pass in
this.HeaderTxt = (string.Empty == headerStr) ? header : headerStr;
}
}
步骤 3
构建一个泛型的ExportExcelPage
类,它继承自System.Web.UI.Page
,包含约束和构造函数,并且该类有一个泛型的ExcelExport
方法,用于将您的exportField 枚举
与目标对象属性绑定并生成 Excel 样式表。
在声明之前,类中需要这些用于反射的内容。
using System;
using System.Text;
using System.Collections;
using System.Reflection;
using System.ComponentModel;
声明
public class ExportExcelPage<T> : System.Web.UI.Page where T:struct
构造函数
#region Constructor
public ExportExcelPage() {
if (!typeof(T).IsEnum)
throw new InvalidOperationException(typeof(T).Name+"is not an enum");
}
#endregion
约束强制继承类具有结构体
声明,并在构造函数中检查此结构体
是否为枚举
。为什么不直接将约束设置为枚举
类型?原因是 Microsoft 在 .NET 2.0、3.0、4.0 中尚未提供此类功能。希望我们能在 .NET 5.0 中实现。
接下来,为具有特殊格式的不同类型的列声明常量。
#region Constants
protected const string QTY_COL = "QtyCol";
protected const string PRICE_COL = "PriceCol";
//your can create more like:
protected const string BOOL_COL = "BoolCol";
......
protected const string CUSTOM_COL = "CustomCol";
#endregion
然后,创建重载的泛型方法来导出数据。
#region Helper Methods
protected void ExcelExport<E, M>(string exportFileName,
E dataColloection, M data, ArrayList notExportFields)
where E : CollectionBase
where M : YourNameSpace.Logic.BusinessObject
{
ExcelExport(exportFileName, dataColloection, null,
null, null, data, notExportFields);
}
protected void ExcelExport<E, M>(string exportFileName,
E dataColloection, int? totalRow, int? firstRow, int?
lastRow, M data, ArrayList notExportFields)
where E : CollectionBase
where M : YourNameSpace.Logic.BusinessObject
{
int idx = (firstRow.HasValue)?firstRow.Value:1;
string exportStr = string.Empty;
string attributeInfo = string.Empty;
StringBuilder sb = new StringBuilder();
//create header
sb.Append("\t");
foreach (string s in Enum.GetNames(typeof(T)))
{
if (!notExportFields.Contains(s))
{
FieldInfo fi = typeof(T).GetField(s);
HearderTxtAttribute[] attributes =
(HearderTxtAttribute[])fi.GetCustomAttributes
(typeof(HearderTxtAttribute), false);
attributeInfo = (attributes.Length > 0) ?
attributes[0].HeaderTxt : string.Empty;
sb.Append(attributeInfo);
sb.Append("\t");
}
}
sb.Append("\r\n");
//load data
foreach (M dataItem in dataColloection)
{
//add index
sb.Append(idx.ToString());
sb.Append("\t");
//add export fields
Type objType = dataItem.GetType();
PropertyInfo[] properties = objType.GetProperties();
foreach (string s in Enum.GetNames(typeof(T)))
{
//get enum attribute
FieldInfo fi = typeof(T).GetField(s);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes
(typeof(DescriptionAttribute), false);
attributeInfo = (attributes.Length > 0) ?
attributes[0].Description : string.Empty;
exportStr = string.Empty;
//mapping with dataItem property
PropertyInfo p = typeof(M).GetProperty(s);
if (!notExportFields.Contains(s))
{
switch (attributeInfo)
{
case QTY_COL:
exportStr = Global.YourConvertPrecisionNumberMethod
((decimal)p.GetValue(dataItem, null), QtyPrecision);
break;
case PRICE_COL:
exportStr = Global. YourConvertPrecisionNumberMethod
((decimal)p.GetValue(dataItem, null), MoneyPrecision);
break;
case CUSTOM_COL:
exportStr = buildCustomCol(s, dataItem);
break;
default:
exportStr = Convert.ToString(p.GetValue(dataItem, null));
break;
}
sb.Append(exportStr);
sb.Append("\t");
}
}
sb.Append("\r\n");
++idx;
}
//create footer
if (firstRow.HasValue && lastRow.HasValue && totalRow.HasValue)
{
lastRow = (lastRow > totalRow) ? totalRow : lastRow;
sb.Append(string.Format(Global.YourGetResourceStringMethod("PagesExcel"),
firstRow.ToString(), lastRow.ToString(), totalRow));
sb.Append("\r\n");
}
string sReport = sb.ToString();
byte[] ByteArray = System.Text.Encoding.UTF8.GetBytes(sReport);
Page.Response.ClearContent();
Page.Response.ClearHeaders();
Page.Response.ContentType = "application/vnd.ms-excel";
Page.Response.AddHeader("Content-disposition", "attachment;
filename=" + exportFileName);
Page.Response.BinaryWrite(ByteArray);
Page.Response.Flush();
Page.Response.End();
return;
}
public virtual string buildCustomCol(string s,
YourNameSpace.Logic.BusinessObject dataItem) { return string.Empty; }
#endregion
让我们来讨论这些方法
protected void ExcelExport<E, M>(string exportFileName,
E dataColloection, int? totalRow, int? firstRow, int?
lastRow, M data, ArrayList notExportFields)
where E : CollectionBase
where M : YourNameSpace.Logic.BusinessObject
Generic ExcelExport<E,M>:
具有 2 个带约束的泛型数据类型。
E
必须是一个CollectionBase
对象,它是您从数据库查询的数据对象(YourNameSpace.Logic.BusinessObject
)的集合。对于大多数具有相似结构的工程来说,这是通用的。
M
必须是一个YourNameSpace.Logic.BusinessObject
对象,它是您 BLL 层中的基类,包含查询数据。
exportFileName
:您计划导出的 Excel 文件名。
dataCollection
:Logic.BusinessObject
的集合,必须继承自CollectionBase
。
totalRow
、firstRow
、lastRow
是表示结果分页信息的可空
参数。firstRow
和lastRow
代表当前结果页的第一行和最后一行编号。totalRow
代表查询结果的总数。如果您进行分页并在报表中报告索引和页脚,它们是可选的。
data
:您业务逻辑层中的一个空业务对象。此空对象的目的是获取 BLL 对象类型以进行反射。为什么不直接传递DataType
而不是空对象?因为您只能通过传递对象来设置约束,而不能通过类型。
notExportFields
:ArrayList
包含根据特定条件不导出的属性。它与枚举
成员映射为ExportFields
。
方法如何工作?
当我们调用导出方法时,我们拥有来自查询的数据集合、所有业务对象的已导出属性名称以及一个空的业务对象。我们需要通过反射知道 Excel 列的标题和数据值来构建 Excel 文件。
构建标题行
循环遍历ExportFields
,通过反射获取成员名称,检查不在notExportFields
中,并获取HearderTxtAttribute
特性中的标题。
foreach (string s in Enum.GetNames(typeof(T)))
{
if (!notExportFields.Contains(s))
{
FieldInfo fi = typeof(T).GetField(s);
HearderTxtAttribute[] attributes =
(HearderTxtAttribute[])fi.GetCustomAttributes
(typeof(HearderTxtAttribute), false);
attributeInfo = (attributes.Length > 0) ?
attributes[0].HeaderTxt : string.Empty;
sb.Append(attributeInfo);
sb.Append("\t");
}
}
构建数据行
循环遍历结果中的数据集合
foreach (M dataItem in dataColloection)
然后获取对象的类型及其属性
Type objType = dataItem.GetType();
PropertyInfo[] properties = objType.GetProperties();
然后通过反射循环遍历ExportFields
中的所有成员
foreach (string s in Enum.GetNames(typeof(T)))
获取每个枚举
成员的描述特性
//get enum attribute
FieldInfo fi = typeof(T).GetField(s);
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
attributeInfo = (attributes.Length > 0) ? attributes[0].Description : string.Empty;
使用反射获取对象属性信息集合
//mapping with dataItem property
PropertyInfo p = typeof(M).GetProperty(s);
检查该字段是否在notExportFields
中以及是否需要格式化
if (!notExportFields.Contains(s))
switch (attributeInfo)
通过反射从属性信息集合中获取属性值
p.GetValue(dataItem, null)
通过调用您页面中的重写方法来自定义结果格式。
exportStr = buildCustomCol(s, dataItem);
buildCustomCol
方法必须在继承的页面中重写,以通过传递属性名称和对象数据来处理实际结果的格式。
使用相同的反射方式,获取属性值,如同上面的代码。
最后,构建可选的页脚,然后生成 Excel 文件并将其发送回客户端。
string sReport = sb.ToString();
byte[] ByteArray = System.Text.Encoding.UTF8.GetBytes(sReport);
Page.Response.ClearContent();
Page.Response.ClearHeaders();
Page.Response.ContentType = "application/vnd.ms-excel";
Page.Response.AddHeader("Content-disposition",
"attachment; filename=" + exportFileName);
Page.Response.BinaryWrite(ByteArray);
Page.Response.Flush();
Page.Response.End();
如何在您的页面中调用导出方法?
首先,您的页面必须继承自ExportExcelPage
并具有用于映射的ExportFields枚举
。接下来,从数据库查询数据,获取返回的业务对象集合。最后,将导出文件名、数据集合传递给ExcelExport
方法以及notExportFields
arrayList。就是这样。
//get the result collection
Logic.OrderHistory.DataCollection data =
Logic.OrderHistory.YourLookupOrderHistoryMethod(spParams);
int totalRow = data.totalRow;
int lastRow = CodeTakeCareByYourSelf();
int firstRow = CodeTakeCareByYourSelf();
ArrayList notExportFields = new ArrayList();
//if your don't want some field to export,
//add to notExportFields from exportFields enum
//notExportFields.add(ExportFields. OrderCreateDate.ToString());
ExcelExport("OrderHistory.xls", data, totalRow, firstRow,
lastRow, new Logic.OrderHistory.DataItem(), notExportFields);
享受吧。
历史
- 2009 年 11 月 5 日:首次发布