2014年10月6日 星期一

ASP.NET MVC 匯出 Excel - 讓使用者挑選要匯出的資料欄位 Part.3

前一篇「ASP.NET MVC 匯出 Excel - 讓使用者挑選要匯出的資料欄位 Part.2」最後有說到,在最後的匯出時還沒有與我們所使用的 ExportColumnAttribute 類別有所關聯,使得匯出的 Excel 檔案裡面的表格欄位名稱依舊是原本的物件屬姓名稱。

另外就是我們一開始所建立專門用來處理最後匯出結果的 ExportExcelResult,在這個 actionResult 類別裡還必須要去執行匯出資料移除欄位的處理,也許有些人會覺得這沒有什麼,不過我是認為從匯出的原始資料將不要的欄位給移除的這個處理不應該是 ExportExcelResult 的責任,ExportExcelResult 只應該去關注處理接收進來的匯出資料、匯出後的檔案名稱、Sheet 名稱等,而匯出資料的整理應該是在之前就應該要先處理好。

這一篇就來整理這最後的匯出處理的部分。

 


首先來整理的是匯出資料的的部分,原本的做法是將不要匯出的資料欄位先找出來,然後再傳給 ExportExcelResult,而最後才是在 ExportExcelResult 裡去把匯出原始資料再把不要匯出的欄位給移除,其實這一步份的處理應該是要在進入 ExportExcelResult 前就要先做好的,先把要匯出的資料整理好也是比較合理的做法。

在 ExportDataHelper 類別裡去新增加一個方法 GetExportDataTable(),這個方法要傳入的是要做匯出的原始資料以及使用者選擇要匯出的欄位資料,

/// <summary>
/// Gets the export data table.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="selectedColumns">The selected columns.</param>
/// <returns></returns>
public static DataTable GetExportDataTable<T>(
    IEnumerable<T> source,
    string selectedColumns) where T : class
{
    var exportSource =
        GetExportDataFromSource(source, selectedColumns);
 
    var exportData =
        JsonConvert.DeserializeObject<DataTable>(exportSource.ToString());
 
    return exportData;
}
 
/// <summary>
/// Gets the export data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="selectedColumns">The selected columns.</param>
/// <returns></returns>
private static JArray GetExportDataFromSource<T>(
    IEnumerable<T> source,
    string selectedColumns) where T : class
{
    var jObjects = new JArray();
 
    var columns = selectedColumns.Split(',');
 
    var exportColumns = ExportColumnAttributeHelper<T>
        .GetExportColumns()
        .Where(x => columns.Contains(x.ColumnName))
        .ToList();
 
    foreach (var item in source)
    {
        Type type = typeof(T);
        var pInfos = type.GetProperties();
 
        var rowDatas = new List<Tuple<int, string, object>>();
 
        foreach (var pinfo in pInfos)
        {
            var cInfo = exportColumns.FirstOrDefault(x => x.ColumnName == pinfo.Name);
            if (cInfo == null) continue;
 
            rowDatas.Add(new Tuple<int, string, object>
            (
                item1: cInfo.Order,
                item2: cInfo.Name,
                item3: pinfo.GetValue(item) ?? ""
            ));
        }
 
        var jo = new JObject();
        foreach (var row in rowDatas.OrderBy(x => x.Item1))
        {
            jo.Add(row.Item2, JToken.FromObject(row.Item3));
        }
        jObjects.Add(jo);
    }
 
    return jObjects;
}

 

而在 CustomerController 的 Export action 方法就修改以下的內容,先把原始資料給找出來,然後使用 AutoMapper 將資料對映轉換到指定的類別,然後再使用上面的方法取得已經整理過並且要匯出的資料,

image

 

再來修改 ExportExcelResult 的內容,將原本還需要在裡面把不需要匯出的欄位移除的這部分給拿掉,因為這並不屬於 ExportExcelResult 的責任,而是在之前就需要先處理好(也就是上面我們所增加的 ExportDataHelper.GetExportDataTable() 方法),

ExportExcelResult.cs

public class ExportExcelResult : ActionResult
{
    public string SheetName { get; set; }
 
    public string FileName { get; set; }
 
    public DataTable ExportData { get; set; }
 
    public override void ExecuteResult(ControllerContext context)
    {
        if (ExportData == null)
        {
            throw new InvalidDataException("ExportData");
        }
        if (string.IsNullOrWhiteSpace(this.SheetName))
        {
            this.SheetName = "Sheet1";
        }
        if (string.IsNullOrWhiteSpace(this.FileName))
        {
            this.FileName = string.Concat
            (
                "ExportData_",
                DateTime.Now.ToString("yyyyMMddHHmmss"),
                ".xlsx"
            );
        }
 
        this.ExportExcelEventHandler(context);
    }
 
    /// <summary>
    /// Exports the excel event handler.
    /// </summary>
    /// <param name="context">The context.</param>
    private void ExportExcelEventHandler(ControllerContext context)
    {
        try
        {
            var workbook = new XLWorkbook();
 
            if (this.ExportData != null)
            {
                context.HttpContext.Response.Clear();
 
                // 編碼
                context.HttpContext.Response.ContentEncoding = Encoding.UTF8;
 
                // 設定網頁ContentType
                context.HttpContext.Response.ContentType =
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
 
                // 匯出檔名
                var browser = context.HttpContext.Request.Browser.Browser;
                var exportFileName = browser.Equals("Firefox", StringComparison.OrdinalIgnoreCase)
                    ? this.FileName
                    : HttpUtility.UrlEncode(this.FileName, Encoding.UTF8);
 
                context.HttpContext.Response.AddHeader(
                    "Content-Disposition",
                    string.Format("attachment;filename={0}", exportFileName));
 
                // Add all DataTables in the DataSet as a worksheets
                workbook.Worksheets.Add(this.ExportData, this.SheetName);
 
                using (var memoryStream = new MemoryStream())
                {
                    workbook.SaveAs(memoryStream);
                    memoryStream.WriteTo(context.HttpContext.Response.OutputStream);
                    memoryStream.Close();
                }
            }
            workbook.Dispose();
        }
        catch
        {
            throw;
        }
    }
}

CustomerController - Export action 方法

image

 

執行結果

image

 

而為了要處理匯出 Excel 的操作,所建立的類別如下:

image

 


三篇下來可能有些人會看得一頭霧水,怎麼會跳來跳去的,其實這些都是過程,是要讓大家了解做出一個功能的前後過程與順序,而不是只讓大家看最後的結果,因為只看結果而沒有這中間修改的過程,絕大部分的人都會不知道為什麼要這麼做,我不希望大家只看得結果而忘了中間的思考過程,因為台灣太多這種只想要結果、甚至是直接给他最後解法的工程師了,假如那一天丟出了一顆變化球給這些只想要結果的工程師,我想揮棒落空而被三振的是比比皆是。

透過一個個步驟的整理與修改,然後看到整個功能的建立與改變,我想這中間的過程才是真正應該被注意的,也是真正的價值所在,價值在於「怎麼做出來」而不在於「做出來的結果」。

下一篇文章就單純的用另外一個模型類別從頭到尾的做一次。

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。