原文地址:http://stephenwalther.com/blog/archive/2008/06/16/asp-net-mvc-tip-2-create-a-custom-action-result-that-returns-microsoft-excel-documents.aspx

摘要:在这个Tip中,Stephen Walther创建了一个自定义的ActionResult,可以由ASP.NET MVC控制器action返回。该ActionResult从一个LINQ to SQL查询生成了一个Excel文档。

译注:从本篇开始,为了方便,仅保留了C#代码。对VB.NET感兴趣的朋友可以参见原文。

在MVC应用程序中,控制器action可以返回一个ActionResult。特别是,他能够返回一些从ActionResult基类继承的东西——

  • - ViewResult
  • - EmptyResult
  • - RedirectResult
  • - RedirectToRouteResult
  • - JsonResult
  • - ContentResult

例如,你可以使用ViewResult向浏览器返回一个特定的视图,使用ContentResult向浏览器返回文本内容。

但是,如果你想向浏览器返回其他类型的内容——如图片、PDF文件或Excel文档呢?在这些情况下,你可以创建自己的ActionResult。在这个Tip中,我会想你展示如何创建一个能返回Excel文档的ActionResult。

清单1包含了ExcelResult的代码。 

清单1 - ExcelResult.cs

  1. using System;  
  2. using System.Web.Mvc;  
  3. using System.Data.Linq;  
  4. using System.Collections;  
  5. using System.IO;  
  6. using System.Web.UI.WebControls;  
  7. using System.Linq;  
  8. using System.Web;  
  9. using System.Web.UI;  
  10. using System.Drawing;  
  11.  
  12. namespace Tip2  
  13. {  
  14.     public class ExcelResult : ActionResult  
  15.     {  
  16.         private DataContext _dataContext;  
  17.         private string _fileName;  
  18.         private IQueryable _rows;  
  19.         private string[] _headers = null;  
  20.  
  21.         private TableStyle _tableStyle;  
  22.         private TableItemStyle _headerStyle;  
  23.         private TableItemStyle _itemStyle;  
  24.  
  25.         public string FileName  
  26.         {  
  27.             get { return _fileName; }  
  28.         }  
  29.  
  30.         public IQueryable Rows  
  31.         {  
  32.             get { return _rows; }  
  33.         }  
  34.  
  35.  
  36.         public ExcelResult(DataContext dataContext, IQueryable rows, string fileName)  
  37.             :this(dataContext, rows, fileName, nullnullnullnull)  
  38.         {  
  39.         }  
  40.  
  41.         public ExcelResult(DataContext dataContext, string fileName, IQueryable rows, string[] headers)  
  42.             : this(dataContext, rows, fileName, headers, nullnullnull)  
  43.         {  
  44.         }  
  45.  
  46.         public ExcelResult(DataContext dataContext, IQueryable rows, string fileName, string[] headers, TableStyle tableStyle, TableItemStyle headerStyle, TableItemStyle itemStyle)  
  47.         {  
  48.             _dataContext = dataContext;  
  49.             _rows = rows;  
  50.             _fileName = fileName;  
  51.             _headers = headers;  
  52.             _tableStyle = tableStyle;  
  53.             _headerStyle = headerStyle;  
  54.             _itemStyle = itemStyle;  
  55.  
  56.             // provide defaults  
  57.             if (_tableStyle == null)  
  58.             {  
  59.                 _tableStyle = new TableStyle();  
  60.                 _tableStyle.BorderStyle = BorderStyle.Solid;  
  61.                 _tableStyle.BorderColor = Color.Black;  
  62.                 _tableStyle.BorderWidth = Unit.Parse("2px");  
  63.             }  
  64.             if (_headerStyle == null)  
  65.             {  
  66.                 _headerStyle = new TableItemStyle();  
  67.                 _headerStyle.BackColor = Color.LightGray;  
  68.             }  
  69.         }  
  70.  
  71.         public override void ExecuteResult(ControllerContext context)  
  72.         {  
  73.             // Create HtmlTextWriter  
  74.             StringWriter sw = new StringWriter();  
  75.             HtmlTextWriter tw = new HtmlTextWriter(sw);  
  76.  
  77.             // Build HTML Table from Items  
  78.             if (_tableStyle != null)  
  79.                 _tableStyle.AddAttributesToRender(tw);  
  80.             tw.RenderBeginTag(HtmlTextWriterTag.Table);  
  81.  
  82.             // Generate headers from table  
  83.             if (_headers == null)  
  84.             {  
  85.                 _headers = _dataContext.Mapping.GetMetaType(_rows.ElementType).PersistentDataMembers.Select(m => m.Name).ToArray();  
  86.             }  
  87.  
  88.  
  89.             // Create Header Row  
  90.             tw.RenderBeginTag(HtmlTextWriterTag.Thead);  
  91.             foreach (String header in _headers)  
  92.             {  
  93.                 if (_headerStyle != null)  
  94.                     _headerStyle.AddAttributesToRender(tw);  
  95.                 tw.RenderBeginTag(HtmlTextWriterTag.Th);  
  96.                 tw.Write(header);  
  97.                 tw.RenderEndTag();  
  98.             }  
  99.             tw.RenderEndTag();  
  100.  
  101.               
  102.  
  103.             // Create Data Rows  
  104.             tw.RenderBeginTag(HtmlTextWriterTag.Tbody);  
  105.             foreach (Object row in _rows)  
  106.             {  
  107.                 tw.RenderBeginTag(HtmlTextWriterTag.Tr);  
  108.                 foreach (string header in _headers)  
  109.                 {  
  110.                     string strValue = row.GetType().GetProperty(header).GetValue(row, null).ToString();  
  111.                     strValue = ReplaceSpecialCharacters(strValue);  
  112.                     if (_itemStyle != null)  
  113.                         _itemStyle.AddAttributesToRender(tw);  
  114.                     tw.RenderBeginTag(HtmlTextWriterTag.Td);  
  115.                     tw.Write( HttpUtility.HtmlEncode(strValue));  
  116.                     tw.RenderEndTag();  
  117.                 }  
  118.                 tw.RenderEndTag();  
  119.             }  
  120.             tw.RenderEndTag(); // tbody  
  121.  
  122.             tw.RenderEndTag(); // table  
  123.             WriteFile(_fileName, "application/ms-excel", sw.ToString());              
  124.         }  
  125.  
  126.  
  127.         private static string ReplaceSpecialCharacters(string value)  
  128.         {  
  129.             value = value.Replace("’""'");  
  130.             value = value.Replace("“""\"");  
  131.             value = value.Replace("”""\"");  
  132.             value = value.Replace("–""-");  
  133.             value = value.Replace("…""...");  
  134.             return value;  
  135.         }  
  136.  
  137.         private static void WriteFile(string fileName, string contentType, string content)  
  138.         {  
  139.             HttpContext context = HttpContext.Current;  
  140.             context.Response.Clear();  
  141.             context.Response.AddHeader("content-disposition""attachment;filename=" + fileName);  
  142.             context.Response.Charset = "";  
  143.             context.Response.Cache.SetCacheability(HttpCacheability.NoCache);  
  144.             context.Response.ContentType = contentType;  
  145.             context.Response.Write(content);  
  146.             context.Response.End();  
  147.         }  
  148.     }  
所有的ActionResult都必须直接或间接继承自ActionResult基类。清单1中的ExcelResult就是这样,实际上,它直接继承了ActionResult类。ActionResult基类中有一个方法是必须实现的——Execute()方法。调用Execute()方法会生成ActionResult的结果产生的内容。

在清单1中,Execute()方法用于从Linq to SQL查询生成Excel文档。Execute()方法会调用WriteFile()方法将生成的Excel文档以正确的MIME类型写入到浏览器中。

通常,你不会从控制器action中直接返回一个ActionResult,而是利用Controller类提供的某个方法——

  • - View()
  • - Redirect()
  • - RedirectToAction()
  • - RedirectToRoute()
  • - Json()
  • - Content()

例如,如果你想从一个控制器action中返回一个视图,不要直接返回一个ViewResult,而是调用View()方法。View()方法会实例化一个ViewResult并将这个新的ViewResult返回给浏览器。

清单2中的代码包含三个应用于Controller类的扩展方法。这些扩展方法向Controller类添加了一个名为Excel()的方法。Excel()方法会返回一个ExcelResult。

清单2 - ExcelControllerExtensions.cs (C#) 

  1. using System;  
  2. using System.Web.Mvc;  
  3. using System.Data.Linq;  
  4. using System.Collections;  
  5. using System.Web.UI.WebControls;  
  6. using System.Linq;  
  7.  
  8. namespace Tip2  
  9. {  
  10.     public static class ExcelControllerExtensions  
  11.     {  
  12.  
  13.         public static ActionResult Excel  
  14.         (  
  15.             this Controller controller,  
  16.             DataContext dataContext,  
  17.             IQueryable rows,  
  18.             string fileName  
  19.         )  
  20.         {  
  21.             return new ExcelResult(dataContext, rows, fileName, nullnullnullnull);  
  22.         }  
  23.  
  24.         public static ActionResult Excel  
  25.         (  
  26.             this Controller controller,  
  27.             DataContext dataContext,  
  28.             IQueryable rows,  
  29.             string fileName,  
  30.             string[] headers  
  31.         )  
  32.         {  
  33.             return new ExcelResult(dataContext, rows, fileName, headers, nullnullnull);  
  34.         }  
  35.  
  36.         public static ActionResult Excel  
  37.         (  
  38.             this Controller controller,   
  39.             DataContext dataContext,  
  40.             IQueryable rows,   
  41.             string fileName,   
  42.             string[] headers,   
  43.             TableStyle tableStyle,   
  44.             TableItemStyle headerStyle,  
  45.             TableItemStyle itemStyle  
  46.         )  
  47.         {  
  48.             return new ExcelResult(dataContext, rows, fileName, headers, tableStyle, headerStyle, itemStyle);  
  49.         }  
  50.  
  51.     }  
清单3中的控制器展示了如何在控制器中使用Excel()扩展方法。该控制器包含三个方法,名字分别是GenerateExcel1()、GenerateExcel2()和GenerateExcel3()。所有这三个控制器action都返回Excel文档,这些Excel是从Movies数据表生成的。

清单3 - HomeController.cs (C#)

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Data.Linq;  
  5. using System.Data.Linq.Mapping;  
  6. using System.Web.UI.WebControls;  
  7. using System.Web;  
  8. using System.Web.Mvc;  
  9. using Tip2.Models;  
  10. using Tip2;  
  11.  
  12. namespace Tip2.Controllers  
  13. {  
  14.     public class HomeController : Controller  
  15.     {  
  16.  
  17.         private MovieDataContext db = new MovieDataContext();  
  18.  
  19.         public ActionResult Index()  
  20.         {  
  21.             return View();  
  22.         }  
  23.  
  24.         /// <summary>  
  25.         /// Generates Excel document using headers grabbed from property names  
  26.         /// </summary>  
  27.         public ActionResult GenerateExcel1()  
  28.         {  
  29.             return this.Excel(db, db.Movies, "data.xls");              
  30.         }  
  31.  
  32.         /// <summary>  
  33.         /// Generates Excel document using supplied headers  
  34.         /// </summary>  
  35.         public ActionResult GenerateExcel2()  
  36.         {  
  37.             var rows = from m in db.Movies select new {Title=m.Title, Director=m.Director};  
  38.             return this.Excel(db, rows, "data.xls"new[] { "Title""Director" });  
  39.         }  
  40.  
  41.         /// <summary>  
  42.         /// Generates Excel document using supplied headers and using supplied styles  
  43.         /// </summary>  
  44.         public ActionResult GenerateExcel3()  
  45.         {  
  46.             var rows = from m in db.Movies select new { Title = m.Title, Director = m.Director };  
  47.             var headerStyle = new TableItemStyle();  
  48.             headerStyle.BackColor = System.Drawing.Color.Orange;  
  49.             return this.Excel(db, rows, "data.xls"new[] { "Title""Director" }, null, headerStyle, null);  
  50.         }  
  51.       
  52.  
  53.     }  

最后,清单4中的Index.aspx视图展示了如何调用GenerateExcel()控制器action来生成Excel文档。注意其中的三个链接使用了GenerateExcel的三个版本。

清单4 - Index.aspx

  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Tip2.Views.Home.Index" %> 
  2.  
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
  4.  
  5. <html xmlns="http://www.w3.org/1999/xhtml" > 
  6. <head runat="server"> 
  7.     <title>Index Page</title> 
  8.     <style type="text/css"> 
  9.       
  10.     li  
  11.     {  
  12.         margin-bottom: 5px;  
  13.     }  
  14.       
  15.     </style> 
  16. </head> 
  17. <body> 
  18.     <div> 
  19.       
  20.     <h1>Generate Microsoft Excel Document</h1> 
  21.       
  22.       
  23.     <ul> 
  24.         <li> 
  25.         <a href="/Home/GenerateExcel1">Generate</a> - Generates an Excel document by using the entity property names for column headings and the default  
  26.         formatting.          
  27.         </li> 
  28.         <li> 
  29.         <a href="/Home/GenerateExcel2">Generate</a> - Generates an Excel document by using supplied header names and default formatting.          
  30.         </li> 
  31.         <li> 
  32.         <a href="/Home/GenerateExcel3">Generate</a> - Generates an Excel document by using supplied header names and supplied formatting.          
  33.         </li> 
  34.  
  35.     </ul>    
  36.     </div> 
  37. </body> 
  38. </html> 
打开Index视图,可以看到如图1所示的页面

图1 - Index.aspx视图

当单击其中一个Generate Excel链接后,你可以得到不同的Excel文档。例如,单击第一个链接之后,你会得到如图2所示的Excel文档。

图2 - Data.xls

一点小瑕疵。单击了某个链接生成Excel文档后,你会看到一个如图3所示的警告。不幸的是,没有什么办法绕过这个警告(关于该警告的更多信息,请参见:http://blogs.msdn.com/vsofficedeveloper/pages/Excel-2007-Extension-Warning.aspx)。

图3 - 来自Microsoft Internet Explorer的警告

仿照该Tip介绍的方法,你可以创建各种类型的ActionResult。例如,你可以创建Image ActionResult、Word ActionResult或者PDF ActionResult。

此处下载源代码:http://weblogs.asp.net/blogs/stephenwalther/Downloads/Tip2/Tip2.zip

posted on 2010-03-25 09:56  Atom Yan  阅读(306)  评论(0编辑  收藏  举报