ASP.NET MVC 匯出Excel套件

寫這個套件的最初的理由是希望一行Code都不改,就可以將清單匯出成Excel,因為原本每一個匯出Excel的需求,都要額外寫一個Action去處理,非常的麻煩,而且大多數的Code都是雷同的,所以就想了個辦法做抽離,讓整個站台完全不用寫任何的額外Code(或是改一點點),再搭配這篇所寫Excel產生器,達到快速匯出的功能。

寫這個套件的最初的理由是希望一行Code都不改,就可以將清單匯出成Excel,因為原本每一個匯出Excel的需求,都要額外寫一個Action去處理,非常的麻煩,而且大多數的Code都是雷同的,所以就想了個辦法做抽離,讓整個站台完全不用寫任何的額外Code(或是改一點點),再搭配這篇所寫Excel產生器,達到快速匯出的功能。

 

下載Wade.ExcelGenerater

 

概念

MVC有一個優點就是資料跟資料呈現是分開的,而每一個Action都會回傳一個ActionResult,大部分的ActionResult中都有Model,我利用這個特性,攔截Action所產生的Model來匯出成Excel,如果在寫MVC專案時,List有獨立成為一個Action(這樣才方便AJAX時使用,有任何的異動只呼叫List的Action只做更新List的區塊,不用全個頁面重新載入),那恭喜你,你符合了這個套件基本需求,因為都通常List的Action所產生的Model,也就是要匯出Excel的資料,就不需要在額外寫Action去處理,如果有樣式上的變動需求,可以使用Template。


@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
    Index
</h2>
<div>
    <input name="searchText" id="searchText" type="text" />
    @*產生匯出Excel的連結*@
    @Html.ExportExcelLink("List")
</div>
<div id="listContainor">    
    @{
        //第一次用RenderAction呈現清單
        Html.RenderAction("List");
     }
</div>
<script>
    $(function () {
        $("#search").click(function () {           
            //查尋或分頁時只換掉List,不用整頁重載
            $.post('@Url.Action("List")', $("#searchText").serialize(), function (html) {
                $("#listContainor").html(html);
            });
        });
    });
</script>

 

HelperExtensions

有寫幾個Helper Extension,方便在View中產生HTML。


/// <summary>
/// 產生匯出Excel的Url,使用當前頁面所在的Controller
/// </summary>        
/// <param name="action">資料來源的Action</param>        
public static string ExportExcelAction(this UrlHelper helper, string action)

/// <summary>
/// 產生匯出Excel的Url
/// </summary>
/// <param name="controller">資料來源的Controller</param>
/// <param name="action">資料來源的Action</param>        
/// <param name="template">所使用的Template</param>
/// <returns></returns>
public static string ExportExcelAction(this UrlHelper helper, string controller, string action, string template)

/// 
/// 產生匯出Excel的Url,使用當前頁面所在的Controller
/// 
///資料來源的Action
///所使用的Template
public static string ExportExcelAction(this UrlHelper helper, string action, string template)

/// <summary>
/// 產生匯出Excel的連結,使用當前頁面所在的Controller,以Html的方法匯出
/// </summary>
/// <param name="controller">資料來源的Controller</param>
public static MvcHtmlString ExportExcelLink(this HtmlHelper helper, string action)

/// <summary>
/// 產生匯出Excel的連結,使用當前頁面所在的Controller
/// </summary>
/// <param name="action">資料來源的Action</param>
/// <param name="template">所使用的Template</param>
public static MvcHtmlString ExportExcelLink(this HtmlHelper helper, string action, string template)

/// <summary>
/// 產生匯出Excel的連結,使用當前頁面所在的Controller
/// </summary>
/// <param name="controller">資料來源的Controller</param>
/// <param name="action">資料來源的Action</param>
/// <param name="template">所使用的Template,null為使用HTML方式</param>
/// <param name="displayText">所呈現的文字</param>
public static MvcHtmlString ExportExcelLink(this HtmlHelper helper, string action, string template, string displayText)

/// <summary>
/// 產生匯出Excel的連結,使用當前頁面所在的Controller
/// </summary>
/// <param name="controller">資料來源的Controller</param>
/// <param name="action">資料來源的Action</param>
/// <param name="template">所使用的Template,null為使用HTML方式</param>
/// <param name="displayText">所呈現的文字</param>
/// <param name="customScript">自訂連結的Script</param>
public static MvcHtmlString ExportExcelLink(this HtmlHelper helper, string controller, string action, string template, string displayText, string customScript)

/// <summary>
/// 產生匯出Excel的連結,使用當前頁面所在的Controller
/// </summary>
/// <param name="controller">資料來源的Controller</param>
/// <param name="action">資料來源的Action</param>
/// <param name="template">所使用的Template,null為使用HTML方式</param>
/// <param name="displayText">所呈現的文字</param>
/// <param name="customScript">自訂連結的Script</param>
public static MvcHtmlString ExportExcelLink(this HtmlHelper helper, string controller, string action, string template, object htmlAttributes, string displayText, string customScript)

 

使用範例


<a href="@Url.ExportExcelAction("List")">匯出Excel</a>
<a href="@Url.ExportExcelAction("List", "Template.xml")">匯出Excel</a>
@Html.ExportExcelLink("List")
@Html.ExportExcelLink("List", "WeatherList.xml", "有範本匯出")
@Html.ExportExcelLink("OtherController", "List", "WeatherList.xml", "有範本匯出", null)

 

固定參數

在View中有二個固定的參數

  1. 查尋條件,為了可以依當前的查尋條件匯出資料,必需在input中增加searchAugment class,如:<input class="searchAugment">。
  2. 匯出主體,當沒有使用範本時,會匯出HTMLH的table,必需加上list class如:<table border="1" class="list">,以便尋找到table。

 

範例程式

在專案檔中隨附了用ASP.NET MVC 3 Razor抓天氣資料並匯出的範例。

image

圖一 範例程式畫面

 

image

圖二 使用Template的匯出結果

 

相關技術

Routing

MVC的是使用Routing去對應出Controller與Action,而我自訂一個Route與HttpHandler來處理匯出,如"ExportExcel/{controller}/{action}/{*template}",用這樣的Url來知道那一個Action要匯出,且是否使用Template。


//global.axcs
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    //註冊匯出的route
    routes.AddExportExcelRoute();

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}

public static void AddExportExcelRoute(this RouteCollection routes)
{
    var defultValues = new RouteValueDictionary();
    routes.Add("ExportExcel", new Route("ExportExcel/{sourceController}/{sourceAction}/{template}", new RouteValueDictionary() { { "template", UrlParameter.Optional } }, new ExportExcelRouteHandler()));
}

 

NOTE:

這個套件的URL我都是用Routing來處理,不熟的朋友可以參考:ASP.NET Routing

 

動態呼叫Action

為了在HttpHandler中取得ActionResult,我用了不少的反射達成。


/// <summary>
/// 動態呼叫Action
/// </summary>
public static T ExecuteAction<T>(this HttpContextBase context, string path, out Controller controller)
    where T : ActionResult
{
    string originPath = context.Request.Path;

    //用重寫改變位址,Request還是使用同一個。
    context.RewritePath(path);

    var routeDate = RouteTable.Routes.GetRouteData(context);
    var requestContext = new RequestContext(context, routeDate);

    string controllerName = routeDate.GetRequiredString(GlobalFields.Controller);
    string actionName = routeDate.GetRequiredString(GlobalFields.Action);

    var factory = ControllerBuilder.Current.GetControllerFactory();
    controller = (Controller)factory.CreateController(requestContext, controllerName);    

    //因為都是私有的Method,只好用反射呼叫
    ReflectionHelper.Invoke(controller, "Initialize", requestContext);

    var invoker = controller.ActionInvoker;
    ControllerDescriptor controllerDescriptor = ReflectionHelper.Invoke<ControllerDescriptor>(invoker, "GetControllerDescriptor", controller.ControllerContext);
    ActionDescriptor actionDescriptor = ReflectionHelper.Invoke<ActionDescriptor>(invoker, "FindAction", controller.ControllerContext, controllerDescriptor, actionName);
    IDictionary<string, object> parameters = ReflectionHelper.Invoke<IDictionary<string, object>>(invoker, "GetParameterValues", controller.ControllerContext, actionDescriptor);

    var result = ReflectionHelper.Invoke<T>(invoker, "InvokeActionMethod", controller.ControllerContext, actionDescriptor, parameters);

    //還原位址
    context.RewritePath(originPath);

    return result;
}

NOTE:

更早一版的匯出,有做只匯出勾選的但因為有點麻煩且只能用在特定的Model(如PK叫ID的Model),所以就在這一版就拿掉了,如果有這樣的需求,可以在checkbox加上searchAugment,在個別的Action中處理查尋。