2014年10月7日 星期二

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

經過前面三篇的整理過程之後,在這一篇將會用另一個新的 Controller 來做說明,所以這一篇將會彙整前面的內容並實際應用操作,如果你是一個喜歡結果論的人,只向知道這些功能應該怎麼用的話,那就是看這一篇,而看完了這一篇之後,想要瞭解功能是如何做出來的,那麼就可以在去看前三篇以及再之前的內容。

 


這一篇將會使用 Northwind 的 Product 資料,

image

但如果只是單純的將 Product 資料直接匯出來就有點無趣,所以當中有關聯的 Supplier 與 Category 資料都會帶出關聯資料的名稱,不過老實說起來這其實也跟匯出功能沒有多大的變化,應該算是 AutoMapper 的應用。

 

Step.1

先建立顯示資料列表與匯出資料會使用到的 ViewModel,這邊也一併設定了 Display 與 ExportColumn 的 attribute 內容,

public class ProductViewModel
{
    [Display(Name = "Product ID")]
    public int ProductID { get; set; }
 
    [Display(Name = "產品名稱")]
    [ExportColumn(Name = "產品名稱", Order = 1)]
    public string ProductName { get; set; }
 
    [Display(Name = "供應商")]
    [ExportColumn(Name = "供應商", Order = 2)]
    public string SupplierName { get; set; }
 
    [Display(Name = "產品分類")]
    [ExportColumn(Name = "產品分類", Order = 3)]
    public string CategoryName { get; set; }
 
    [Display(Name = "Quantity Per Unit")]
    [ExportColumn(Name = "Quantity Per Unit", Order = 4)]
    public string QuantityPerUnit { get; set; }
 
    [Display(Name = "Unit Price")]
    [ExportColumn(Name = "Unit Price", Order = 5)]
    public decimal? UnitPrice { get; set; }
 
    [Display(Name = "Units In Stock")]
    [ExportColumn(Name = "Units In Stock", Order = 6)]
    public short? UnitsInStock { get; set; }
 
    [Display(Name = "Units On Order")]
    [ExportColumn(Name = "Units On Order", Order = 7)]
    public short? UnitsOnOrder { get; set; }
 
    [Display(Name = "Reorder Level")]
    [ExportColumn(Name = "Reorder Level", Order = 8)]
    public short? ReorderLevel { get; set; }
 
    [Display(Name = "Discontinued")]
    [ExportColumn(Name = "Discontinued", Order = 9)]
    public string Discontinued { get; set; }
 
}

其中原本是 Boolean 型別的 Discontinued 屬性,為了避免匯出的資料直接顯示「True」「False」而讓一些腦殘又愛硬坳的人有所誤會,所以顯是在頁面上以及匯出到 Excel 的時候就會以這個欄位所表示的是否已經停止供貨的意思來做表示。

 

Step.2

建立 ProductController 以及相關的 Action 方法,

public class ProductController : Controller
{
    NorthwindEntities db = new NorthwindEntities();
 
    public ActionResult Index()
    {
        //匯出資料欄位
        var exportColumns =
            ExportColumnAttributeHelper<ProductViewModel>.GetExportColumns()
                .Select(c => new SelectListItem()
                {
                    Value = c.ColumnName,
                    Text = c.Name,
                    Selected = true
                })
                .ToList();
 
        ViewBag.ExportColumns = exportColumns;
 
        //要匯出的資料
        var exportData = db.Products.OrderBy(x => x.ProductID).ToList();
 
        Mapper.CreateMap<Product, ProductViewModel>()
            .ForMember(d => d.SupplierName, o => o.MapFrom(s => s.Supplier.CompanyName))
            .ForMember(d => d.CategoryName, o => o.MapFrom(s => s.Category.CategoryName))
            .ForMember(d => d.Discontinued, o => o.MapFrom(s => s.Discontinued ? "已停止" : "--"));
 
        var result = Mapper.Map<List<ProductViewModel>>(exportData);
 
        return View(result);
    }
 
    [HttpPost]
    public ActionResult HasData()
    {
        JObject jo = new JObject();
        bool result = !db.Products.Count().Equals(0);
        jo.Add("Msg", result.ToString());
        return Content(JsonConvert.SerializeObject(jo), "application/json");
    }
 
 
    public ActionResult Export(string fileName, string selectedColumns)
    {
        var exportData = db.Products.OrderBy(x => x.ProductID).ToList();
 
        Mapper.CreateMap<Product, ProductViewModel>()
            .ForMember(d => d.SupplierName, o => o.MapFrom(s => s.Supplier.CompanyName))
            .ForMember(d => d.CategoryName, o => o.MapFrom(s => s.Category.CategoryName))
            .ForMember(d => d.Discontinued, o => o.MapFrom(s => s.Discontinued ? "已停止" : "--"));
 
        var result = Mapper.Map<List<ProductViewModel>>(exportData);
 
        var dt = ExportDataHelper.GetExportDataTable(result, selectedColumns);
 
        var exportFileName = string.IsNullOrWhiteSpace(fileName)
            ? string.Concat(
                "ProductData_",
                DateTime.Now.ToString("yyyyMMddHHmmss"),
                ".xlsx")
            : string.Concat(fileName, ".xlsx");
 
        return new ExportExcelResult
        {
            SheetName = "產品資料",
            FileName = exportFileName,
            ExportData = dt
        };
    }
}

其中就有使用 AutoMapper 來做轉換,並且將其中的三個欄位做自訂轉換的設定,當然這些設定也是可以抽離出來然後放在個別的 Profile 裡並使用 Configuration 來做設定,不過這邊的範例就沒有這麼做,有關 AutoMapper 的 Configuration 設定方式,可以參閱以下的文章:

mrkt 的程式學習筆記: AutoMapper 的設定 (Configuration)

 

Step.3

將 Controller 與 Action 方法建立完成之後,再來就是建立相關的 View,

~/Views/Product/Index.cshtml

@model IEnumerable<ExportSelectColumns.Infrastructure.ViewModels.ProductViewModel>
 
@{
    ViewBag.Title = "Product - List";
}
 
<h2>Product - List</h2>
 
<div class="well">
    <button class="btn btn-primary" id="ButtonExport" name="ButtonExport">
        匯出資料
    </button>
</div>
 
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.ProductID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.ProductName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.SupplierName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.CategoryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.QuantityPerUnit)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UnitPrice)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UnitsInStock)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UnitsOnOrder)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.ReorderLevel)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Discontinued)
        </th>
    </tr>
 
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.ProductID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ProductName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.SupplierName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.CategoryName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.QuantityPerUnit)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UnitPrice)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UnitsInStock)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UnitsOnOrder)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReorderLevel)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Discontinued)
            </td>
        </tr>
    }
 
</table>
   
<!-- ExportData Dialog -->
@Html.Partial("_ExportDataDialog")
<!-- ExportData Dialog -->   

~/Views/Product/_ExportDataDialog.cshtml

<div id="ExportDataDialog" class="modal fade" tabindex="-1" data-width="600px" data-height="500px" style="display: none;">
    <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
        <h4 id="ExportDataDialog">匯出產品資料</h4>
    </div>
    <div class="modal-body" style="margin-left: 5px">
        <div class="row">
            <div class="form-group">
                <div class="col-lg-12">
                    <label for="ExportFileName">匯出資料檔名(可不填)</label>
                    <input type="text" id="ExportFileName" name="ExportFileName" class="form-control" placeholder="可輸入中文, 不含副檔名." />
                </div>
            </div>
        </div>
        <div class="row">
            <div class="form-group">
                <div class="col-lg-12">
                    <label>
                        <strong>匯出欄位</strong> (
                        <a id="SelectAllColumns" style="cursor: pointer;">
                            <span class="glyphicon glyphicon-ok-sign"></span> 選取全部欄位
                        </a>
                        <a id="UnselectAllColumns" style="cursor: pointer;">
                            <span class=" glyphicon glyphicon-remove-sign"></span> 不選取全部欄位
                        </a> )
                    </label>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-lg-12">
                <div class="well">
                    @foreach (var item in (List<SelectListItem>)ViewBag.ExportColumns)
                    {
                        <div class="form-group">
                            <label class="checkbox">
                                <input type="checkbox" id="Checkbox_ExportColumns" name="Checkbox_ExportColumns" value="@(item.Value)" checked="checked" />
                                @(item.Text)
                            </label>
                        </div>
                    }
                </div>
            </div>
        </div>
    </div>
    <div class="modal-footer">
        <button id="ButtonExecuteExport" class="btn btn-primary">匯出資料</button>
        <button id="ButtonCancel" class="btn" data-dismiss="modal" aria-hidden="true">取消</button>
    </div>
</div>

 

Step.4

現在已經完成了 View 與 Controller 的部分,但是現在還缺少的就是前端頁面的操作行為,也就是還沒有建立專屬的 Javascript 檔案,現在就建立這個 Product Export 的 JS 檔案,

~/Scripts/Product-Export.js

;
(function (windows) {
    if (typeof (jQuery) === 'undefined') { alert('jQuery Library NotFound.'); return; }
 
    var HasData = 'False';
 
    $(function () {
 
        //顯示匯出選項視窗
        $('#ButtonExport').click(function () {
            $('#ExportDataDialog').modal('show');
        });
 
        $('#SelectAllColumns').unbind('click').click(function () {
            //選取全部欄位
            $('input:checkbox[name=Checkbox_ExportColumns]').prop('checked', 'checked');
        });
 
        $('#UnselectAllColumns').unbind('click').click(function () {
            //不選取全部欄位
            $('input:checkbox[name=Checkbox_ExportColumns]').removeAttr('checked');
        });
 
        //匯出資料
        $('#ButtonExecuteExport').click(function () {
            //匯出 Excel 檔名
            var exportFileName = $.trim($('#ExportFileName').val());
 
            //匯出的資料欄位
            var selectedColumns = $('input:checkbox[name=Checkbox_ExportColumns]:checked').map(function () {
                return $(this).val();
            }).get().join(',');
 
            if (selectedColumns.length == 0) {
                alert("必須選取匯出資料的欄位.");
                return false;
            }
 
            ExportData(exportFileName, selectedColumns);
        });
 
    });
 
    function ExportData(exportFileName, selectedColumns) {
        /// <summary>
        /// 資料匯出
        /// </summary>
 
        $.ajax({
            type: 'post',
            url: Router.action('Product', 'HasData'),
            dataType: 'json',
            cache: false,
            async: false,
            success: function (data) {
                if (data.Msg) {
                    HasData = data.Msg;
                    if (HasData == 'False') {
                        alert("尚未建立任何資料, 無法匯出資料.");
                    }
                    else {
                        window.location = exportFileName.length == 0
                            ? Router.action('Product', 'Export', { selectedColumns: selectedColumns })
                            : Router.action('Product', 'Export', { fileName: exportFileName, selectedColumns: selectedColumns });
 
                        $('#ExportFileName').val('');
                        $('#ExportDataDialog').modal('hide');
                    }
                }
            },
            error: function (xhr, textStatus, errorThrown) {
                alert("資料匯出錯誤");
            }
        });
    }
})
(window);

記得在 Index.cshtml 的最下方要加入 Product-Export.js 的引用

image

 

執行結果

檢視頁面

image

匯出產品資料的對話視窗,

image

先填入要匯出的 Excel 檔案名稱並且選擇全部的欄位,

image

匯出結果

image

匯出的 Excel 內容

image

 

這次換輸入另外的匯出檔名,然後只有勾選幾個欄位做匯出,

image

匯出的 Excel 內容

image

 



如果你有從第一篇一直看到這裡的話,相信你就會覺得最後這一篇的處理真的相當簡單與方便,並沒有什麼太過於複雜的設定,因為都已經在前面的幾篇所建立的類別裡去處理了,而且不限定哪一種型別,也不需要因為型別的不同而要做繁瑣的轉換,只需要搭配 AutoMapper 的使用就可以。

這一系列的「ASP.NET MVC 匯出 Excel - 讓使用者挑選要匯出的資料欄位」就到這篇告一段落,而原始碼會在近期發佈到 GitHub 上,將會沿用 MVC-Excel-Import-Export 這一個 Repository。

接著下來依據這一個基礎還可以做一些不一樣處理,例如除了讓使用者可以挑選要匯出的欄位之外,也希望可以讓使用者自己挑選要匯出的資料,甚至於加入了查詢功能,也可以讓使用者自己決定要匯出的資料是全部的資料還是查詢後的資料結果;能做的功能還有很多,在往後的文章再慢慢向各位說明。

 

延伸閱讀

ASP.NET MVC 匯入 Excel 簡單做 - Part.1 檔案上傳

ASP.NET MVC 匯入 Excel 簡單做 - Part.2 匯入資料

ASP.NET MVC 匯出 Excel 簡單做 - 使用 ClosedXML

ASP.NET MVC 匯出 Excel 簡單做 - 自訂匯出 Excel 檔案的檔名

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

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

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

 

以上

沒有留言:

張貼留言

提醒

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