前言
在WEB中,经常要使用到将数据转换成EXCEL,并进行下载。这里整理资料并封装了一个自定义ActionResult类,便于使用。如果文章对你有帮助,请点个赞。
话不多少,这里转换EXCEL使用的NPOI。还是用了一下反射的知识,便于识别实体类的一些自定义特性。
一、自定义一个Attribute
using System; namespace WebSeat.Entity.Member.Attributes { /// <summary> /// 说明:Excel属性特性 /// 创建日期:2016/12/13 14:24:13 /// 创建人:曹永承 /// </summary> [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true), Serializable] public class ExcelDataOptionAttribute:Attribute { /// <summary> /// 显示列下标 /// </summary> public ushort ColumnIndex { get; set; } /// <summary> /// 显示名称 /// </summary> public string DisplayName { get; set; } /// <summary> /// 列宽 /// </summary> public int ColumnWidth { get; set; } /// <summary> /// 单元格数据格式 /// </summary> public string Formater { get; set; } } }
该Attribute用于标记到实体对象的属性上,后面通过反射来识别具体的值
二、定义一个实体类
using WebSeat.Entity.Member.Attributes; namespace WebSeat.Entity.Member.Excel { /// <summary> /// 说明:市首页数据统计Excel表格样式 /// 创建日期:2016/12/13 14:19:27 /// 创建人:曹永承 /// </summary> public class CityStatics { [ExcelDataOption(ColumnIndex = 0, DisplayName = "时段",Formater ="@", ColumnWidth = 14)] public string DataDuring { get; set; } [ExcelDataOption(ColumnIndex =1,DisplayName ="城市",ColumnWidth =14)] public string City { get; set; } [ExcelDataOption(ColumnIndex = 2, DisplayName = "登录人数", ColumnWidth = 12)] public int StudentLoginedCount { get; set; } [ExcelDataOption(ColumnIndex = 3,DisplayName ="登录次数", ColumnWidth = 12)] public int StudentLoginTimes { get; set; } [ExcelDataOption(ColumnIndex = 4,DisplayName ="登录率",Formater ="0.00%", ColumnWidth = 12)] public decimal StudentLoginRatio { get; set; } [ExcelDataOption(ColumnIndex = 5,DisplayName ="学习节数", ColumnWidth = 12)] public int StudyPeriod { get; set; } [ExcelDataOption(ColumnIndex = 6, DisplayName = "学习次数", ColumnWidth = 12)] public int StudyTimes { get; set; } [ExcelDataOption(ColumnIndex = 7, DisplayName = "人均学习节数(节/人)", Formater = "0.00", ColumnWidth =23)] public decimal StudyRatio { get; set; } [ExcelDataOption(ColumnIndex = 8, DisplayName = "转化率",Formater = "0.00%", ColumnWidth = 12)] public decimal StudyConvertRatio { get; set; } } }
注意:如果属性没有标注ExcelDataOption特性,那么该属性是不会导出到EXCEL中。
ExcelDataOption中Formater属性,是设置单元格数据类型,这里对于excel中单元格的数据显示个数,例如上面"0.00%"表示以百分百的形式显示数字,且保留2位有效小数
三、定义一个Excel导出父类
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; using WebSeat.Site.Member.Helper; namespace WebSeat.Site.Member.CustomResult { /// <summary> /// 说明:导出Excel /// 创建日期:2016/12/13 13:12:37 /// 创建人:曹永承 /// </summary> public abstract class ExcelBaseResult<T> :ActionResult { #region 属性 /// <summary> /// 数据实体 /// </summary> public IList<T> Entity { get; set; } /// <summary> /// 下载文件名称(不包含扩展名) /// </summary> public string FileName { get; set; } /// <summary> /// 是否显示标题 /// </summary> public bool ShowTitle { get; set; } /// <summary> /// 标题 /// </summary> public string Title { get; set; } /// <summary> /// ContentType /// </summary> public string ContentType { get; set; } /// <summary> /// 扩展名 /// </summary> public string ExtName { get; set; } /// <summary> /// 获取下载文件全名 /// </summary> public string FullName { get { return FileName + ExtName; } } #endregion #region 构造函数 public ExcelBaseResult(IList<T> entity, string fileName,bool showTitle,string title) { this.Entity = entity; this.FileName = fileName; this.ShowTitle = showTitle; this.Title = title; } #endregion #region 抽象方法 public abstract MemoryStream GetExcelStream(); #endregion #region 重写ExecuteResult public override void ExecuteResult(ControllerContext context) { using(MemoryStream ms = GetExcelStream()) { context.HttpContext.Response.AddHeader("Content-Length", ms.Length.ToString()); context.HttpContext.Response.ContentType = ContentType; context.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=" + FullName.EncodingDownloadFileName()); ms.Seek(0, SeekOrigin.Begin); Stream output = context.HttpContext.Response.OutputStream; byte[] bytes = new byte[1024 * 10]; int readSize = 0; while ((readSize = ms.Read(bytes, 0, bytes.Length)) > 0) { output.Write(bytes, 0, readSize); context.HttpContext.Response.Flush(); } } } #endregion } }
主要因为Excel有不同版本,所有定义了一个父类,其子类只需要实现方法
public abstract MemoryStream GetExcelStream();
四、定义一个子类继承ExcelBaseResult
这里实现了一个导出.xls格式(2003版本的)到子类
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Web; using System.Web.Mvc; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using WebSeat.Entity.Member.Attributes; using WebSeat.Site.Member.CustomResult; namespace WebSeat.Site.Member.CustomResult { /// <summary> /// 说明:导出成.xls格式的Excel /// 创建日期:2016/12/13 13:51:23 /// 创建人:曹永承 /// </summary> public class Excel2003Result<T>: ExcelBaseResult<T> where T:new() { public Excel2003Result(IList<T> entity, string fileName,bool showTitle,string title) :base(entity, fileName, showTitle, title) { ContentType = "application/vnd.ms-excel"; ExtName = ".xls"; } public override MemoryStream GetExcelStream() { MemoryStream ms = new MemoryStream(); //获取实体属性 PropertyInfo[] propertys = typeof(T).GetProperties(); if (propertys.Count() == 0) { return ms; } //创建Excel对象 IWorkbook book = new HSSFWorkbook(); //添加一个sheet ISheet sheet1 = book.CreateSheet("Sheet1"); var index = ShowTitle ? 1 : 0; //样式设置 IFont cellfont = book.CreateFont(); cellfont.FontHeightInPoints = 11; cellfont.FontName = "宋体"; ICellStyle cellStyle = book.CreateCellStyle(); cellStyle.VerticalAlignment = VerticalAlignment.Center; cellStyle.Alignment = HorizontalAlignment.Center; cellStyle.SetFont(cellfont); IRow rowColumnHead = sheet1.CreateRow(index); IDataFormat format = book.CreateDataFormat(); ushort firstColumn = ushort.MaxValue, lastColumn = ushort.MinValue; //第一列下标和最后一列下标 //添加列头 for (int j = 0; j < propertys.Count(); j++) { ExcelDataOptionAttribute dataOption = propertys[j].GetCustomAttribute<ExcelDataOptionAttribute>(); if (dataOption == null) { continue; } IFont font = book.CreateFont(); font.FontHeightInPoints = 11; font.FontName = "宋体"; ICellStyle style = book.CreateCellStyle(); style.VerticalAlignment = VerticalAlignment.Center; style.Alignment = HorizontalAlignment.Center; style.SetFont(font); if (!string.IsNullOrWhiteSpace(dataOption.Formater)) { style.DataFormat = format.GetFormat(dataOption.Formater); } sheet1.SetDefaultColumnStyle(dataOption.ColumnIndex, style); ICell cell = rowColumnHead.CreateCell(dataOption.ColumnIndex); cell.SetCellValue(dataOption.DisplayName); firstColumn = firstColumn < dataOption.ColumnIndex ? firstColumn : dataOption.ColumnIndex; lastColumn = lastColumn > dataOption.ColumnIndex ? lastColumn : dataOption.ColumnIndex; } index = ShowTitle ? 2 : 1; //将各行数据显示出来 for (int i = 0; i < Entity.Count; i++) { IRow row = sheet1.CreateRow(i + index); //循环各属性,添加列 for (int j = 0; j < propertys.Count(); j++) { ExcelDataOptionAttribute dataOption = propertys[j].GetCustomAttribute<ExcelDataOptionAttribute>(); if (dataOption == null) { continue; } ICell cell = row.CreateCell(dataOption.ColumnIndex); //样式设置 //cell.CellStyle = cellStyle; if (dataOption.ColumnWidth != 0) { sheet1.SetColumnWidth(dataOption.ColumnIndex, dataOption.ColumnWidth*256); } //根据数据类型判断显示格式 if (propertys[j].PropertyType == typeof (int)) { cell.SetCellValue((int)propertys[j].GetValue(Entity[i])); }else if (propertys[j].PropertyType == typeof (decimal) || propertys[j].PropertyType == typeof(double) || propertys[j].PropertyType == typeof(float)) { cell.SetCellValue(Convert.ToDouble(propertys[j].GetValue(Entity[i])) ); } else { cell.SetCellValue(propertys[j].GetValue(Entity[i]).ToString()); } } } //将标题合并 if (ShowTitle) { IRow rowHead = sheet1.CreateRow(0); ICell cellHead = rowHead.CreateCell(firstColumn); cellHead.SetCellValue(Title); //样式设置 IFont font = book.CreateFont(); font.FontHeightInPoints = 14; font.IsBold = true; ICellStyle style = book.CreateCellStyle(); style.VerticalAlignment = VerticalAlignment.Center; style.Alignment = HorizontalAlignment.Center; style.SetFont(font); cellHead.CellStyle = style; rowHead.HeightInPoints = 20.25f; sheet1.AddMergedRegion(new NPOI.SS.Util.CellRangeAddress(0, 0, firstColumn, lastColumn)); } book.Write(ms); ms.Seek(0, System.IO.SeekOrigin.Begin); return ms; } } }
五、下载文件中文名称出现乱码问题
上面第二步,在Excel导出父类中,有这么一句代码
context.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=" + FullName.EncodingDownloadFileName());
其中EncodingDownloadFileName
方法是一个String的扩展类,用于将文件名称进行编码,避免出现乱码的情况。
之前测试过程中,在没有使用转码的过程中,发现IE浏览器在下载时,中文名称出现了乱码的情况,其他浏览器正常(这里只测试了IE浏览器、谷歌浏览器、火狐浏览器、QQ浏览器、360浏览器和360极速浏览器)。后来使用了
HttpContext.Current.Server.UrlEncode(filename)对文件名称进行转码后发现,IE浏览器正常了,除了火狐浏览器,其他浏览器都正常。所有就想到当使用火狐浏览器访问时不对名称进行转码,后来写一个String的扩展方法,方便后期其他下载类使用
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebSeat.Site.Member.Helper { /// <summary> /// 说明:String扩展方法 /// 创建日期:2016/12/19 9:45:10 /// 创建人:曹永承 /// </summary> public static class StringHelperExtend { public static string EncodingDownloadFileName(this string filename) { if (filename == null) { throw new NullReferenceException("filename不能为空"); } string agent = HttpContext.Current.Request.Headers["User-Agent"]; //如果不是火狐浏览器都进行编码 if (agent != null && agent.ToLower().IndexOf("firefox") < 0) { return HttpContext.Current.Server.UrlEncode(filename); } return filename; } } }
六、代码使用
public ActionResult ExportExcel() { //获取数据 IList<CityStatics> list = new List<CityStatics>(); ...... ExcelBaseResult<CityStatics> excel = new Excel2003Result<CityStatics>(list, "1.xls", true, "全市各区数据 名称:成都"); return excel; }
这样就可以了,看看下载后的excel
补充说明:如果使用gzip压缩方式的,那么文件下载时无法显示文件大小的