zoukankan      html  css  js  c++  java
  • C# MVC 自定义ActionResult实现EXCEL下载

    前言

      在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压缩方式的,那么文件下载时无法显示文件大小的

  • 相关阅读:
    40 +必不可少的前端Web开发备忘单
    web前端开发必读的HTML5的书籍
    Java String的内存机制
    使用C#通过Oracle.DataAccess连接Oracle,部署时需要注意版本问题
    CI(CodeIgniter)的"Disallowed Key Characters."异常处理
    开源软件许可协议简介
    读《考拉小巫的英语学习日记》有感
    vi的复制、粘贴、查找、删除等常用命令
    读《马云创业启示录》有感
    jQuery ajax 同步失效?
  • 原文地址:https://www.cnblogs.com/caoyc/p/6202256.html
Copyright © 2011-2022 走看看