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壓縮方式的,那么文件下載時無法顯示文件大小的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM