在这个Tip中,Stephen Walther创建了一个自定义的ActionResult,可以由ASP.NET MVC控制器action返回。该ActionResult从一个LINQ to SQL查询生成了一个Excel文档。
原文地址:http://weblogs.asp.net/stephenwalther/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
13
namespace Tip2
14

{
15
public class ExcelResult : ActionResult
16
{
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 FileName
27
{
28
get
{ return _fileName; }
29
}
30
31
public IQueryable Rows
32
{
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 defaults
58
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 HtmlTextWriter
75
StringWriter sw = new StringWriter();
76
HtmlTextWriter tw = new HtmlTextWriter(sw);
77
78
// Build HTML Table from Items
79
if (_tableStyle != null)
80
_tableStyle.AddAttributesToRender(tw);
81
tw.RenderBeginTag(HtmlTextWriterTag.Table);
82
83
// Generate headers from table
84
if (_headers == null)
85
{
86
_headers = _dataContext.Mapping.GetMetaType(_rows.ElementType).PersistentDataMembers.Select(m => m.Name).ToArray();
87
}
88
89
90
// Create Header Row
91
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 Rows
105
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(); // tbody
122
123
tw.RenderEndTag(); // table
124
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#)
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, null, null, null, null);
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, null, null, null);
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
}
52
}
清单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
}
54
}
最后,清单4中的Index.aspx视图展示了如何调用GenerateExcel()控制器action来生成Excel文档。注意其中的三个链接使用了GenerateExcel的三个版本。
清单4 - Index.aspx
1
<%@ 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
li
10
{
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 default
25
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/。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步