摘要:在这个Tip中,Stephen Walther创建了一个自定义的ActionResult,可以由ASP.NET MVC控制器action返回。该ActionResult从一个LINQ to SQL查询生成了一个Excel文档。
译注:从本篇开始,为了方便,仅保留了C#代码。对VB.NET感兴趣的朋友可以参见原文。
在MVC应用程序中,控制器action可以返回一个ActionResult。特别是,他能够返回一些从ActionResult基类继承的东西——
例如,你可以使用ViewResult向浏览器返回一个特定的视图,使用ContentResult向浏览器返回文本内容。
但是,如果你想向浏览器返回其他类型的内容——如图片、PDF文件或Excel文档呢?在这些情况下,你可以创建自己的ActionResult。在这个Tip中,我会想你展示如何创建一个能返回Excel文档的ActionResult。
清单1包含了ExcelResult的代码。
清单1 - ExcelResult.cs
- using System;
- using System.Web.Mvc;
- using System.Data.Linq;
- using System.Collections;
- using System.IO;
- using System.Web.UI.WebControls;
- using System.Linq;
- using System.Web;
- using System.Web.UI;
- using System.Drawing;
- namespace Tip2
- {
- public class ExcelResult : ActionResult
- {
- private DataContext _dataContext;
- private string _fileName;
- private IQueryable _rows;
- private string[] _headers = null;
- private TableStyle _tableStyle;
- private TableItemStyle _headerStyle;
- private TableItemStyle _itemStyle;
- public string FileName
- {
- get { return _fileName; }
- }
- public IQueryable Rows
- {
- get { return _rows; }
- }
- public ExcelResult(DataContext dataContext, IQueryable rows, string fileName)
- :this(dataContext, rows, fileName, null, null, null, null)
- {
- }
- public ExcelResult(DataContext dataContext, string fileName, IQueryable rows, string[] headers)
- : this(dataContext, rows, fileName, headers, null, null, null)
- {
- }
- public ExcelResult(DataContext dataContext, IQueryable rows, string fileName, string[] headers, TableStyle tableStyle, TableItemStyle headerStyle, TableItemStyle itemStyle)
- {
- _dataContext = dataContext;
- _rows = rows;
- _fileName = fileName;
- _headers = headers;
- _tableStyle = tableStyle;
- _headerStyle = headerStyle;
- _itemStyle = itemStyle;
- // provide defaults
- if (_tableStyle == null)
- {
- _tableStyle = new TableStyle();
- _tableStyle.BorderStyle = BorderStyle.Solid;
- _tableStyle.BorderColor = Color.Black;
- _tableStyle.BorderWidth = Unit.Parse("2px");
- }
- if (_headerStyle == null)
- {
- _headerStyle = new TableItemStyle();
- _headerStyle.BackColor = Color.LightGray;
- }
- }
- public override void ExecuteResult(ControllerContext context)
- {
- // Create HtmlTextWriter
- StringWriter sw = new StringWriter();
- HtmlTextWriter tw = new HtmlTextWriter(sw);
- // Build HTML Table from Items
- if (_tableStyle != null)
- _tableStyle.AddAttributesToRender(tw);
- tw.RenderBeginTag(HtmlTextWriterTag.Table);
- // Generate headers from table
- if (_headers == null)
- {
- _headers = _dataContext.Mapping.GetMetaType(_rows.ElementType).PersistentDataMembers.Select(m => m.Name).ToArray();
- }
- // Create Header Row
- tw.RenderBeginTag(HtmlTextWriterTag.Thead);
- foreach (String header in _headers)
- {
- if (_headerStyle != null)
- _headerStyle.AddAttributesToRender(tw);
- tw.RenderBeginTag(HtmlTextWriterTag.Th);
- tw.Write(header);
- tw.RenderEndTag();
- }
- tw.RenderEndTag();
- // Create Data Rows
- tw.RenderBeginTag(HtmlTextWriterTag.Tbody);
- foreach (Object row in _rows)
- {
- tw.RenderBeginTag(HtmlTextWriterTag.Tr);
- foreach (string header in _headers)
- {
- string strValue = row.GetType().GetProperty(header).GetValue(row, null).ToString();
- strValue = ReplaceSpecialCharacters(strValue);
- if (_itemStyle != null)
- _itemStyle.AddAttributesToRender(tw);
- tw.RenderBeginTag(HtmlTextWriterTag.Td);
- tw.Write( HttpUtility.HtmlEncode(strValue));
- tw.RenderEndTag();
- }
- tw.RenderEndTag();
- }
- tw.RenderEndTag(); // tbody
- tw.RenderEndTag(); // table
- WriteFile(_fileName, "application/ms-excel", sw.ToString());
- }
- private static string ReplaceSpecialCharacters(string value)
- {
- value = value.Replace("’", "'");
- value = value.Replace("“", "\"");
- value = value.Replace("”", "\"");
- value = value.Replace("–", "-");
- value = value.Replace("…", "...");
- return value;
- }
- private static void WriteFile(string fileName, string contentType, string content)
- {
- HttpContext context = HttpContext.Current;
- context.Response.Clear();
- context.Response.AddHeader("content-disposition", "attachment;filename=" + fileName);
- context.Response.Charset = "";
- context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
- context.Response.ContentType = contentType;
- context.Response.Write(content);
- context.Response.End();
- }
- }
- }
在清单1中,Execute()方法用于从Linq to SQL查询生成Excel文档。Execute()方法会调用WriteFile()方法将生成的Excel文档以正确的MIME类型写入到浏览器中。
通常,你不会从控制器action中直接返回一个ActionResult,而是利用Controller类提供的某个方法——
例如,如果你想从一个控制器action中返回一个视图,不要直接返回一个ViewResult,而是调用View()方法。View()方法会实例化一个ViewResult并将这个新的ViewResult返回给浏览器。
清单2中的代码包含三个应用于Controller类的扩展方法。这些扩展方法向Controller类添加了一个名为Excel()的方法。Excel()方法会返回一个ExcelResult。
清单2 - ExcelControllerExtensions.cs (C#)
- using System;
- using System.Web.Mvc;
- using System.Data.Linq;
- using System.Collections;
- using System.Web.UI.WebControls;
- using System.Linq;
- namespace Tip2
- {
- public static class ExcelControllerExtensions
- {
- public static ActionResult Excel
- (
- this Controller controller,
- DataContext dataContext,
- IQueryable rows,
- string fileName
- )
- {
- return new ExcelResult(dataContext, rows, fileName, null, null, null, null);
- }
- public static ActionResult Excel
- (
- this Controller controller,
- DataContext dataContext,
- IQueryable rows,
- string fileName,
- string[] headers
- )
- {
- return new ExcelResult(dataContext, rows, fileName, headers, null, null, null);
- }
- public static ActionResult Excel
- (
- this Controller controller,
- DataContext dataContext,
- IQueryable rows,
- string fileName,
- string[] headers,
- TableStyle tableStyle,
- TableItemStyle headerStyle,
- TableItemStyle itemStyle
- )
- {
- return new ExcelResult(dataContext, rows, fileName, headers, tableStyle, headerStyle, itemStyle);
- }
- }
- }
清单3 - HomeController.cs (C#)
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Data.Linq;
- using System.Data.Linq.Mapping;
- using System.Web.UI.WebControls;
- using System.Web;
- using System.Web.Mvc;
- using Tip2.Models;
- using Tip2;
- namespace Tip2.Controllers
- {
- public class HomeController : Controller
- {
- private MovieDataContext db = new MovieDataContext();
- public ActionResult Index()
- {
- return View();
- }
- /// <summary>
- /// Generates Excel document using headers grabbed from property names
- /// </summary>
- public ActionResult GenerateExcel1()
- {
- return this.Excel(db, db.Movies, "data.xls");
- }
- /// <summary>
- /// Generates Excel document using supplied headers
- /// </summary>
- public ActionResult GenerateExcel2()
- {
- var rows = from m in db.Movies select new {Title=m.Title, Director=m.Director};
- return this.Excel(db, rows, "data.xls", new[] { "Title", "Director" });
- }
- /// <summary>
- /// Generates Excel document using supplied headers and using supplied styles
- /// </summary>
- public ActionResult GenerateExcel3()
- {
- var rows = from m in db.Movies select new { Title = m.Title, Director = m.Director };
- var headerStyle = new TableItemStyle();
- headerStyle.BackColor = System.Drawing.Color.Orange;
- return this.Excel(db, rows, "data.xls", new[] { "Title", "Director" }, null, headerStyle, null);
- }
- }
- }
最后,清单4中的Index.aspx视图展示了如何调用GenerateExcel()控制器action来生成Excel文档。注意其中的三个链接使用了GenerateExcel的三个版本。
清单4 - Index.aspx
- <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Tip2.Views.Home.Index" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" >
- <head runat="server">
- <title>Index Page</title>
- <style type="text/css">
- li
- {
- margin-bottom: 5px;
- }
- </style>
- </head>
- <body>
- <div>
- <h1>Generate Microsoft Excel Document</h1>
- <ul>
- <li>
- <a href="/Home/GenerateExcel1">Generate</a> - Generates an Excel document by using the entity property names for column headings and the default
- formatting.
- </li>
- <li>
- <a href="/Home/GenerateExcel2">Generate</a> - Generates an Excel document by using supplied header names and default formatting.
- </li>
- <li>
- <a href="/Home/GenerateExcel3">Generate</a> - Generates an Excel document by using supplied header names and supplied formatting.
- </li>
- </ul>
- </div>
- </body>
- </html>
图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。