寫這個套件的最初的理由是希望一行Code都不改,就可以將清單匯出成Excel,因為原本每一個匯出Excel的需求,都要額外寫一個Action去處理,非常的麻煩,而且大多數的Code都是雷同的,所以就想了個辦法做抽離,讓整個站台完全不用寫任何的額外Code(或是改一點點),再搭配這篇所寫Excel產生器,達到快速匯出的功能。
寫這個套件的最初的理由是希望一行Code都不改,就可以將清單匯出成Excel,因為原本每一個匯出Excel的需求,都要額外寫一個Action去處理,非常的麻煩,而且大多數的Code都是雷同的,所以就想了個辦法做抽離,讓整個站台完全不用寫任何的額外Code(或是改一點點),再搭配這篇所寫Excel產生器,達到快速匯出的功能。
概念
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中有二個固定的參數
- 查尋條件,為了可以依當前的查尋條件匯出資料,必需在input中增加searchAugment class,如:<input class="searchAugment">。
- 匯出主體,當沒有使用範本時,會匯出HTMLH的table,必需加上list class如:<table border="1" class="list">,以便尋找到table。
範例程式
在專案檔中隨附了用ASP.NET MVC 3 Razor抓天氣資料並匯出的範例。
圖一 範例程式畫面
圖二 使用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中處理查尋。