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

原文地址:https://www.cnblogs.com/caoyc/p/6202256.html