[翻译] ASP.NET MVC Tip #2 - 创建可以返回Excel文档的自定义ActionResult
摘要:在这个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
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
13
namespace Tip214
{15
public class ExcelResult : ActionResult16
{17
private DataContext _dataContext;18
private string _fileName;19
private IQueryable _rows;20
private string[] _headers = null;21
22
private TableStyle _tableStyle;23
private TableItemStyle _headerStyle;24
private TableItemStyle _itemStyle;25
26
public string FileName27
{28
get { return _fileName; }29
}30
31
public IQueryable Rows32
{33
get { return _rows; }34
}35
36
37
public ExcelResult(DataContext dataContext, IQueryable rows, string fileName)38
:this(dataContext, rows, fileName, null, null, null, null)39
{40
}41
42
public ExcelResult(DataContext dataContext, string fileName, IQueryable rows, string[] headers)43
: this(dataContext, rows, fileName, headers, null, null, null)44
{45
}46
47
public ExcelResult(DataContext dataContext, IQueryable rows, string fileName, string[] headers, TableStyle tableStyle, TableItemStyle headerStyle, TableItemStyle itemStyle)48
{49
_dataContext = dataContext;50
_rows = rows;51
_fileName = fileName;52
_headers = headers;53
_tableStyle = tableStyle;54
_headerStyle = headerStyle;55
_itemStyle = itemStyle;56
57
// provide defaults58
if (_tableStyle == null)59
{60
_tableStyle = new TableStyle();61
_tableStyle.BorderStyle = BorderStyle.Solid;62
_tableStyle.BorderColor = Color.Black;63
_tableStyle.BorderWidth = Unit.Parse("2px");64
}65
if (_headerStyle == null)66
{67
_headerStyle = new TableItemStyle();68
_headerStyle.BackColor = Color.LightGray;69
}70
}71
72
public override void ExecuteResult(ControllerContext context)73
{74
// Create HtmlTextWriter75
StringWriter sw = new StringWriter();76
HtmlTextWriter tw = new HtmlTextWriter(sw);77
78
// Build HTML Table from Items79
if (_tableStyle != null)80
_tableStyle.AddAttributesToRender(tw);81
tw.RenderBeginTag(HtmlTextWriterTag.Table);82
83
// Generate headers from table84
if (_headers == null)85
{86
_headers = _dataContext.Mapping.GetMetaType(_rows.ElementType).PersistentDataMembers.Select(m => m.Name).ToArray();87
}88
89
90
// Create Header Row91
tw.RenderBeginTag(HtmlTextWriterTag.Thead);92
foreach (String header in _headers)93
{94
if (_headerStyle != null)95
_headerStyle.AddAttributesToRender(tw);96
tw.RenderBeginTag(HtmlTextWriterTag.Th);97
tw.Write(header);98
tw.RenderEndTag();99
}100
tw.RenderEndTag();101
102
103
104
// Create Data Rows105
tw.RenderBeginTag(HtmlTextWriterTag.Tbody);106
foreach (Object row in _rows)107
{108
tw.RenderBeginTag(HtmlTextWriterTag.Tr);109
foreach (string header in _headers)110
{111
string strValue = row.GetType().GetProperty(header).GetValue(row, null).ToString();112
strValue = ReplaceSpecialCharacters(strValue);113
if (_itemStyle != null)114
_itemStyle.AddAttributesToRender(tw);115
tw.RenderBeginTag(HtmlTextWriterTag.Td);116
tw.Write( HttpUtility.HtmlEncode(strValue));117
tw.RenderEndTag();118
}119
tw.RenderEndTag();120
}121
tw.RenderEndTag(); // tbody122
123
tw.RenderEndTag(); // table124
WriteFile(_fileName, "application/ms-excel", sw.ToString()); 125
}126
127
128
private static string ReplaceSpecialCharacters(string value)129
{130
value = value.Replace("’", "'");131
value = value.Replace("“", "\"");132
value = value.Replace("”", "\"");133
value = value.Replace("–", "-");134
value = value.Replace("…", "
");135
return value;136
}137
138
private static void WriteFile(string fileName, string contentType, string content)139
{140
HttpContext context = HttpContext.Current;141
context.Response.Clear();142
context.Response.AddHeader("content-disposition", "attachment;filename=" + fileName);143
context.Response.Charset = "";144
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);145
context.Response.ContentType = contentType;146
context.Response.Write(content);147
context.Response.End();148
}149
}150
}所有的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#)
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 Tip29
{10
public static class ExcelControllerExtensions11
{12
13
public static ActionResult Excel14
(15
this Controller controller,16
DataContext dataContext,17
IQueryable rows,18
string fileName19
)20
{21
return new ExcelResult(dataContext, rows, fileName, null, null, null, null);22
}23
24
public static ActionResult Excel25
(26
this Controller controller,27
DataContext dataContext,28
IQueryable rows,29
string fileName,30
string[] headers31
)32
{33
return new ExcelResult(dataContext, rows, fileName, headers, null, null, null);34
}35
36
public static ActionResult Excel37
(38
this Controller controller, 39
DataContext dataContext,40
IQueryable rows, 41
string fileName, 42
string[] headers, 43
TableStyle tableStyle, 44
TableItemStyle headerStyle,45
TableItemStyle itemStyle46
)47
{48
return new ExcelResult(dataContext, rows, fileName, headers, tableStyle, headerStyle, itemStyle);49
}50
51
}52
}清单3中的控制器展示了如何在控制器中使用Excel()扩展方法。该控制器包含三个方法,名字分别是GenerateExcel1()、GenerateExcel2()和GenerateExcel3()。所有这三个控制器action都返回Excel文档,这些Excel是从Movies数据表生成的。
清单3 - HomeController.cs (C#)
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.Controllers13
{14
public class HomeController : Controller15
{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 names26
/// </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 headers34
/// </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 styles43
/// </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
}54
}最后,清单4中的Index.aspx视图展示了如何调用GenerateExcel()控制器action来生成Excel文档。注意其中的三个链接使用了GenerateExcel的三个版本。
清单4 - Index.aspx
<%@ Page Language="VB" AutoEventWireup="false" CodeBehind="Index.aspx.vb" Inherits="Tip2.Index" %>2
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">3
4
<html xmlns="http://www.w3.org/1999/xhtml" >5
<head id="Head1" runat="server">6
<title>Index Page</title>7
<style type="text/css">8
9
li10
{11
margin-bottom: 5px;12
}13
14
</style>15
</head>16
<body>17
<div>18
19
<h1>Generate Microsoft Excel Document</h1>20
21
22
<ul>23
<li>24
<a href="/Home/GenerateExcel1">Generate</a> - Generates an Excel document by using the entity property names for column headings and the default25
formatting. 26
</li>27
<li>28
<a href="/Home/GenerateExcel2">Generate</a> - Generates an Excel document by using supplied header names and default formatting. 29
</li>30
<li>31
<a href="/Home/GenerateExcel3">Generate</a> - Generates an Excel document by using supplied header names and supplied formatting. 32
</li>33
34
</ul>35
36
37
38
39
</div>40
</body>41
</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。
-----
广告:欢迎光临[.NET正则表达式库] http://regex-lib.net/。

浙公网安备 33010602011771号