2014年10月5日 星期日

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

前一篇「ASP.NET MVC 匯出 Excel - 讓使用者挑選要匯出的資料欄位 Part.1」已經完成了可以讓使用者自行挑選要匯出的資料欄位功能,不過在文末也提到該篇文章內的做法其實並不是很好,問題就在於要匯出的欄位還需要另外用去設定匯出欄位的資料內容,而且匯出的欄位名稱是使用原始的物件屬性名稱,比較好的做法應該是匯出的欄位名稱也應該是以中文或是自訂文字的內容來顯示。

所以接下來就來做些改變,讓這個功能的設定可以簡單一些而不用這麼繁瑣。

 


其實我對於另外用 Dictionary 類別去放置匯出欄位資料的做法不是很喜歡,因為建立這種資料還蠻繁瑣的,再來就是這種與模型資料很相關的資料就必須另外獨立在另外的地方,

image

在這個範例所使用的是 Customer 類別,本身欄位就不是很多,如果今天要做匯出資料的類別有很多欄位的時候,這種使用 Dictionary 放置匯出欄位設定資料的做法就法不是很適當,再來一個延伸的問題就是當模型類別的欄位有所改變的時候就必須也要跟著去改變這個 Dictionary 的內容,為了避免維護上以及設定上的困難,所以就要思考別的解決方式。

 

這邊我所要做的就是建立一個用來設定匯出資料的 Attribute 類別,而這個 Attribute 類別將會是使用在模型類別的屬性上,當要匯出某個類別的資料時,只要去判別標示在類別屬性上的 Attribute 就可以知道哪些屬性是能夠匯出、匯出的欄位名稱為何,甚至於也可以決定匯出欄位的順序。

ExportColumnAttributes.cs

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class ExportColumnAttribute : Attribute
{
    /// <summary>
    /// 欄位顯示名稱.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public string Name { get; set; }
 
    /// <summary>
    /// 欄位顯示順序.
    /// </summary>
    /// <value>
    /// The order.
    /// </value>
    public int Order { get; set; }
 
    public ExportColumnAttribute()
    {
    }
}

image

 

而為了方便取得在模型類別裡有標示 ExportColumnAttribute 的屬性與設定內容,另外建立一個 ExportColumnAttributeHelper 類別,

ExportColumnAttributeHelper.cs

public class ExportColumnAttributeHelper<T> where T : class
{
    /// <summary>
    /// Gets the export columns.
    /// </summary>
    /// <returns></returns>
    public static List<ExportColumnObject> GetExportColumns()
    {
        var type = typeof(T);
        var infos = type.GetProperties();
 
        var result = infos.Select(x => x.Name)
                          .Select(propertyName => GetProperty(type, propertyName))
                          .Select(GetExportColumnInstance)
                          .Where(instance => instance != null)
                          .ToList();
 
        return result.OrderBy(x => x.Order).ToList();
    }
 
    /// <summary>
    /// Gets the export column instance.
    /// </summary>
    /// <param name="pInfo">The p information.</param>
    /// <returns></returns>
    public static ExportColumnObject GetExportColumnInstance(PropertyInfo pInfo)
    {
        if (null == pInfo) return null;
        try
        {
            var arr = pInfo.GetCustomAttributes(typeof(ExportColumnAttribute), true);
 
            if (arr.Length.Equals(0)) return null;
 
            var attr = arr[0] as ExportColumnAttribute;
 
            var instance = new ExportColumnObject()
            {
                ColumnName = pInfo.Name,
                Name = attr.Name,
                Order = attr.Order
            };
 
            return instance;
        }
        catch
        {
            return null;
        }
    }
 
    /// <summary>
    /// Gets the property.
    /// </summary>
    /// <param name="type">The type.</param>
    /// <param name="propName">Name of the property.</param>
    /// <returns></returns>
    private static PropertyInfo GetProperty(Type type, string propName)
    {
        try
        {
            var infos = type.GetProperties()
                            .Where(info => propName.ToLower().Equals(info.Name.ToLower()));
 
            foreach (var info in infos)
            {
                return info;
            }
        }
        catch
        {
            throw;
        }
        return null;
    }
}
ExportColumnObject.cs
public class ExportColumnObject
{
    public Guid ID { get; set; }
 
    /// <summary>
    /// 欄位輸出名稱.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public string Name { get; set; }
 
    /// <summary>
    /// 欄位順序.
    /// </summary>
    /// <value>
    /// The order.
    /// </value>
    public int Order { get; set; }
 
    /// <summary>
    /// 欄位(屬性)名稱.
    /// </summary>
    /// <value>
    /// The name of the column.
    /// </value>
    public string ColumnName { get; set; }
 
    public ExportColumnObject()
    {
        this.ID = Guid.NewGuid();
    }
 
}

image

 

建立好以上的幾個類別之後,接著就是實際的應用,前面有說過建立 ExportColumnAttribute 類別是要用在模型類別裡的屬性上,但是我並不會直接放在 Entity Framework 所建立的原生模型類別裡,無論是 Code First 或是 Datebase First 所建立的模型類別,我會建議使用 ExportColumnAttribute 的地方是在 ViewModel 類別。

ViewModel 類別是用於 View 的顯示之用,單純只有屬性、欄位而沒有任何的行為、方法,而且有時候我們所要匯出的資料並不是從單一個類別而來,會是從各個類別所來的資料組合而成,而匯出資料的處理是經過多個操作與彙整,所以我們要看待的並不是原生的模型類別,而是最後匯出資料的類別。

我們已經在頁面上顯示了資料呈現的結果,而使用者預期也是想要將頁面上所呈現的資料給匯出為 Excel 檔案,所以使用 ExportColumnAttribute 的地方就應該是在 ViewModel 裡。

如果今天要做的匯出處理並沒有頁面先呈現資料,而是讓使用者直接匯出或是也一樣可以讓使用者自己挑欄位的話,我也是建立一個 ViewModel 來做處理,ViewModel 只是一個單純的類別,用來裝載資料然後用於傳遞以及顯示之用,而且也不會讓原生的模型類別加上太多的責任。

 

CustomerViewModel.cs

public class CustomerViewModel
{
    [Display(Name = "Customer ID")]
    public string CustomerID { get; set; }
 
    [Display(Name = "公司名稱")]
    [ExportColumn(Name = "公司名稱", Order = 1)]
    public string CompanyName { get; set; }
 
    [Display(Name = "聯絡者姓名")]
    [ExportColumn(Name = "聯絡者姓名", Order = 2)]
    public string ContactName { get; set; }
 
    [Display(Name = "聯絡者職稱")]
    [ExportColumn(Name = "聯絡者職稱", Order = 3)]
    public string ContactTitle { get; set; }
 
    [Display(Name = "地址")]
    [ExportColumn(Name = "地址", Order = 4)]
    public string Address { get; set; }
 
    [Display(Name = "城市")]
    [ExportColumn(Name = "城市", Order = 5)]
    public string City { get; set; }
 
    [Display(Name = "區域")]
    [ExportColumn(Name = "區域", Order = 6)]
    public string Region { get; set; }
 
    [Display(Name = "郵遞區號")]
    [ExportColumn(Name = "郵遞區號", Order = 7)]
    public string PostalCode { get; set; }
 
    [Display(Name = "國家")]
    [ExportColumn(Name = "國家", Order = 8)]
    public string Country { get; set; }
 
    [Display(Name = "電話號碼")]
    [ExportColumn(Name = "電話號碼", Order = 9)]
    public string Phone { get; set; }
 
    [Display(Name = "傳真號碼")]
    [ExportColumn(Name = "傳真號碼", Order = 10)]
    public string Fax { get; set; }
}

image

 

最後重新整理一下 CustomerController 與 View 的內容,原本使用原生模型類別的部分就改用 CustomerViewModel,並且使用 AutoMapper 來做類別的資料對應轉換,然後取得匯出欄位的資料改用新建立的方法。

image

image

~/Views/Customer/Index.cshtml

image

 

在 CustomerController 的 Export action 方法也是需要做個修改,

image

 

執行結果

頁面裡的資料列表顯示是使用 CustomerViewModel,

image

匯出會員資料的對話視窗,匯出欄位的資料則是從標示在 CustomerViewModel 裡的 ExportColumnAttrobute,

image

最後匯出的 Excel 檔案內容

image

等等… 不是說匯出的 Excel 的欄位名稱也是要使用我們在 ExportColumnAttrobute 裡所設定的名稱(Name)嗎?怎麼現在出來的結果還是一樣是顯示原來的欄位名稱呢?

其實上面的修改都沒有問題,只是我們最後的輸出還是沿用了原來的資料輸出方法,

image

而這個 GetExportDataWithAllColumns() 方法裡仍就是使用原來手動加入匯出欄位與資料的方式,與前面所建立的 ExportColumnAttribute 類別並沒有任何的關聯,

image

在下一篇裡就要針對最後資料輸出的部分來做修改(這一篇已經很長了…)。

 


延伸閱讀

使用 AutoMapper 處理類別之間的對映轉換

 

以上

沒有留言:

張貼留言

提醒

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