65.9K
CodeProject 正在变化。 阅读更多。
Home

通用的导出数据到 Excel 文件的通用方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (8投票s)

2009 年 11 月 5 日

CPOL

5分钟阅读

viewsIcon

54436

使用枚举、特性、反射和泛型将数据导出到 Excel 文件

引言

本文介绍了如何在表示层使用泛型枚举反射,将业务逻辑层的数据映射并导出为 Excel 报表。

背景

将 Excel 报表导出到客户端是很常见的。简单的方法是获取gridview的导出数据,然后将其转换为 Excel 并返回给客户端。但是,当您禁用gridview viewstate或使用没有viewstate的 Ajax 来提高应用程序性能时,您需要重新从数据库查询数据并生成 Excel 报表。
没有像gridview DataSourceDataBind那样的直接生成 Excel 文件的方法。本文将介绍一种简单的方法,通过使用泛型反射枚举特性,允许您将任何查询结果导出到 Excel 文件。

分步指南

步骤 1

设置您想要导出 Excel 报表的页面,使其继承自ExportExcelPage<T>页面,并使用枚举类型。我将在下面讨论这个基页和泛型类型。例如,如果您有一个名为OrderHistorySalesInformation的页面,它们将被声明为:

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 文件名。

dataCollectionLogic.BusinessObject的集合,必须继承自CollectionBase

totalRowfirstRowlastRow是表示结果分页信息的可空参数。firstRowlastRow代表当前结果页的第一行和最后一行编号。totalRow代表查询结果的总数。如果您进行分页并在报表中报告索引和页脚,它们是可选的。

data:您业务逻辑层中的一个空业务对象。此空对象的目的是获取 BLL 对象类型以进行反射。为什么不直接传递DataType而不是空对象?因为您只能通过传递对象来设置约束,而不能通过类型。

notExportFieldsArrayList包含根据特定条件不导出的属性。它与枚举成员映射为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方法以及notExportFieldsarrayList。就是这样。

//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 日:首次发布
© . All rights reserved.