原文地址: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
\n
摘要:在这个Tip中,Stephen Walther创建了一个自定义的ActionResult,可以由ASP.NET MVC控制器action返回。该ActionResult从一个LINQ to SQL查询生成了一个Excel文档。
\n
译注:从本篇开始,为了方便,仅保留了C#代码。对VB.NET感兴趣的朋友可以参见原文。
\n
在MVC应用程序中,控制器action可以返回一个ActionResult。特别是,他能够返回一些从ActionResult基类继承的东西——
\n
- ViewResult
- EmptyResult
- RedirectResult
- RedirectToRouteResult
- JsonResult
- ContentResult
例如,你可以使用ViewResult向浏览器返回一个特定的视图,使用ContentResult向浏览器返回文本内容。
\n
但是,如果你想向浏览器返回其他类型的内容——如图片、PDF文件或Excel文档呢?在这些情况下,你可以创建自己的ActionResult。在这个Tip中,我会想你展示如何创建一个能返回Excel文档的ActionResult。
\n
清单1包含了ExcelResult的代码。
\n
清单1 – ExcelResult.cs
\n
1using System;
2using System.Web.Mvc;
3using System.Data.Linq;
4using System.Collections;
5using System.IO;
6using System.Web.UI.WebControls;
7using System.Linq;
8using System.Web;
9using System.Web.UI;
10using System.Drawing;
11
12
13namespace 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的结果产生的内容。
\n
在清单1中,Execute()方法用于从Linq to SQL查询生成Excel文档。Execute()方法会调用WriteFile()方法将生成的Excel文档以正确的MIME类型写入到浏览器中。
\n
通常,你不会从控制器action中直接返回一个ActionResult,而是利用Controller类提供的某个方法——
\n
- View()
- Redirect()
- RedirectToAction()
- RedirectToRoute()
- Json()
- Content()
例如,如果你想从一个控制器action中返回一个视图,不要直接返回一个ViewResult,而是调用View()方法。View()方法会实例化一个ViewResult并将这个新的ViewResult返回给浏览器。
\n
清单2中的代码包含三个应用于Controller类的扩展方法。这些扩展方法向Controller类添加了一个名为Excel()的方法。Excel()方法会返回一个ExcelResult。
\n
清单2 – ExcelControllerExtensions.cs (C#)
\n
1using System;
2using System.Web.Mvc;
3using System.Data.Linq;
4using System.Collections;
5using System.Web.UI.WebControls;
6using System.Linq;
7
8namespace 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数据表生成的。
\n
清单3 – HomeController.cs (C#)
\n
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Data.Linq;
5using System.Data.Linq.Mapping;
6using System.Web.UI.WebControls;
7using System.Web;
8using System.Web.Mvc;
9using Tip2.Models;
10using Tip2;
11
12namespace 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的三个版本。
\n
清单4 – Index.aspx
\n
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所示的页面
\n
图1 – Index.aspx视图
\n
当单击其中一个Generate Excel链接后,你可以得到不同的Excel文档。例如,单击第一个链接之后,你会得到如图2所示的Excel文档。
\n
图2 – Data.xls
\n
一点小瑕疵。单击了某个链接生成Excel文档后,你会看到一个如图3所示的警告。不幸的是,没有什么办法绕过这个警告(关于该警告的更多信息,请参见:http://blogs.msdn.com/vsofficedeveloper/pages/Excel-2007-Extension-Warning.aspx)。
\n
图3 – 来自Microsoft Internet Explorer的警告
\n
仿照该Tip介绍的方法,你可以创建各种类型的ActionResult。例如,你可以创建Image ActionResult、Word ActionResult或者PDF ActionResult。
\n
此处下载源代码:http://weblogs.asp.net/blogs/stephenwalther/Downloads/Tip2/Tip2.zip。
来源:http://www.cnblogs.com/AndersLiu