原文: Chapter 3 - Understanding Controllers
ASP.NET MVC 的控制器负责控制应用程序执行的流程。当你通过浏览器向ASP.NET MVC 应用程序发出一个请求时,控制器负责返回针对请求的响应。
控制器会暴露一个或多个动作。控制器动作可以返回给浏览器不同类型的动作结果,例如,控制器动作可能返回一个视图,可能返回一个文件,或者可以重定向到另一个控制器动作。
在本章中,你会学到如何创建控制器和控制器动作,学到如何返回不同类型的控制器动作结果,你还会学到当一个特定的控制器动作被触发时,如何通过特性来控制它。最后,我们会讨论如何给你的控制器和动作编写单元测试。
Creating a Controller
创建控制器最简单的方式就是在Visual Studio 解决方案窗口中,右键点击Controllers 文件夹,选择菜单Add, Controller ,显示Add Controller 对话框(参见图1 ),如果输入ProductController ,你会得到如列表1 所示的代码:
*** Begin Warning ***
控制器的名字必须以Controller 后缀结束。如果你忘记包含Controller 后缀,那么你就不可能调用到这个控制器。
*** End Warning ***
图1 – 添加控制器对话框
列表1 – Controllers\ProductController.cs [C#]
#
using
System; #
using
System.Collections.Generic; #
using
System.Linq; #
using
System.Web; #
using
System.Web.Mvc; #
using
System.Web.Mvc.Ajax; # #
namespace
MvcApplication1.Controllers # { #
public
class
ProductController : Controller # { #
//
#
//
GET: /Product/
# #
public
ActionResult Index() # { #
return
View(); # } # # } # }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
//
// GET: /Product/
public ActionResult Index()
{
return View();
}
}
}
列表1 – Controllers\ProductController.vb [VB]
#
Public
Class
ProductController #
Inherits
System.Web.Mvc.Controller # #
'
#
'
GET: /Product/
# #
Function
Index()
As
ActionResult #
Return
View() #
End Function
# #
End Class
请注意,控制器只是一个继承自ProductController 的类(Visual Basic 或者C# 类)。Public Class ProductController
Inherits System.Web.Mvc.Controller
'
' GET: /Product/
Function Index() As ActionResult
Return View()
End Function
End Class
控制器中的任何一个共有方法都会被暴露为一个控制器动作。列表1 中的控制器类暴露了一个动作,名为Index() ,Index() 动作时一个默认动作,当动作没有被显示指定时,这个动作就被调用。
*** Begin Warning ***
默认情况下,控制器中包含的公共方法可以被Internet 上的任意位置的人调用,所以要小心那些从控制器中暴露出来的公共方法。你可以通过为方法添加NonAction 特性的方式来阻止控制器中一个公共方法被调用。
*** End Warning ***
请注意Index() 动作返回一个ActionResult ,控制动作总是返回一个ActionResult (即使它看起来不像返回一个ActionResult )。ActionResult 决定了返回给浏览器的响应内容,Index() 控制器返回一个视图,作为ActionResult 。
一个控制器一般会暴露多个动作。你可以通过向控制器中添加新方法的方式来向控制器添加动作。例如,列表2 中经过修改的Product 控制器暴露了3 个动作,分别是Index() 、Help() 和Details() 。
列表 2 – Controllers\ProductController.cs with additional methods [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
ProductController : Controller {
//
//
GET: /Product/
public
ActionResult Index() {
return
View(); }
//
//
GET: /Product/Help
public
ActionResult Help() {
return
View(); }
//
//
GET: /Details/1
public
ActionResult Details(
int
Id) {
return
View(); } } }
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
//
// GET: /Product/
public ActionResult Index()
{
return View();
}
//
// GET: /Product/Help
public ActionResult Help()
{
return View();
}
//
// GET: /Details/1
public ActionResult Details(int Id)
{
return View();
}
}
}
列表2 – Controllers\ProductController.vb with additional methods [VB]
Public
Class
ProductController
Inherits
System.Web.Mvc.Controller
'
'
GET: /Product/
Function
Index()
As
ActionResult
Return
View()
End Function
'
'
GET: /Product/Help
Function
Help()
As
ActionResult
Return
View()
End Function
'
'
GET: /Details/1
Function
Details(
ByVal
id
As
Integer
)
As
ActionResult
Return
View()
End Function
End Class
你可以向浏览器地址栏中输入以下内容来调用不同的动作:Public Class ProductController
Inherits System.Web.Mvc.Controller
'
' GET: /Product/
Function Index() As ActionResult
Return View()
End Function
'
' GET: /Product/Help
Function Help() As ActionResult
Return View()
End Function
'
' GET: /Details/1
Function Details(ByVal id As Integer) As ActionResult
Return View()
End Function
End Class
· /Product/Index – 调用ProductController 的Index() 动作。
· /Product – 调用ProductController 的 Index() 动作。
· /Product/Help – 调用ProductController 的 Help() 动作。
· /Product/Details/34 – 调用ProductController 的 Details() 动作,并以“34 ”作为其Id 参数。
你可以通过一种如下所示的特定模式来调用控制器动作:
{controller}/{action}/{id}
请注意,当你调用一个控制器时,在URL 中并不包括Controller 后缀。例如,调用Product 控制器的URL 形式为/Product/Index ,而不是/ProductController/Index 。
Index() 时默认的控制器动作,因此,/Product/Index 和/Product 都会调用Index() 动作。
当调用控制器时,你可以提供一个可选的Id 参数。例如Details 动作接收一个Id 参数,URL :/Product/Details/2 调用Details 动作,并传递2 作为Id 参数。参数的名称很重要,你必须将其命名为Id 。
*** Begin Note ***
Global.asax 文件中的默认路由定义了调用控制器动作的默认URL 模式。如果想改变调用动作的URL 模式,你需要改动默认路由。可以参考第9 章来了解更多关于创建自定义路由的信息。
*** End Note
Returning Action Results
控制器动作总是返回一个ActionResult ,ASP.NET MVC 框架包括以下类型的ActionResult :
· ViewResult – 表现为一个ASP.NET MVC 视图。
· PartialViewResult – 表现为一个ASP.NET MVC 视图的片段(局部视图)。
· RedirectResult – 表现为重定向到另外一个控制器动作或者URL 。
· ContentResult – 表现为一些未经处理的内容,这些内容被发送给浏览器。
· JsonResult – 表现为一个JavaScript 对象符号结果(用于Ajax 场景中)。
· FileResult – 表现为一个将要被下载的文件。
· EmptyResult – 表现为一个动作没有结果返回。
· HttpUnauthorizedResult – 表现为一个HTTP 未授权的状态代码。
· JavaScriptResult – 表现为一个JavaScript 文件。
· RedirectToRouteResult – 表现为重定向到使用路由值的另一个控制器动作或者URL 。
通常,你不会直接从控制器动作中返回ActionResult ,而是调用一个返回ActionResult 的控制器方法。例如,如果想返回一个ViewResult ,你可以调用控制器的View() 方法。
下面是一个可以返回ActionResult 的控制器方法列表:
· View() – 返回一个ViewResult 。
· PartialView() – 返回一个PartialViewResult 。
· RedirectToAction() – 返回一个RedirectToRouteResult 。
· Redirect() – 返回一个RedirectResult 。
· Content() – 返回一个ContentResult 。
· Json() – 返回一个JsonResult 。
· File() – 返回一个FileResult 。
· JavaScript() – 返回一个JavaScriptResult 。
· RedirectToRoute() – 返回一个RedirectToRouteResult 。
在接下来的章节,我们会对这些ActionResult 中的一些加以详细讨论。
*** Begin Note ***
在第10 章理解视图母页和视图用户控件中,我们会讨论局部视图结果(AKA 视图用户控件或者局部视图)。
*** End Note ***
Returning a View Result
ViewResult 是控制器动作返回的最普遍的ActionResult 。一个ViewResult 表现为一个ASP.NET MVC 视图。当想返回HTML 给浏览器时,你可以选择ViewResult 。
列表3 为Customer 控制器的代码,它暴露了一个名为Details() 的动作,返回一个ViewResult :
列表3 – Controllers\CustomerController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
CustomerController : Controller {
public
ActionResult Details() {
return
View(); } } }
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class CustomerController : Controller
{
public ActionResult Details()
{
return View();
}
}
}
列表列表 列表3 – Controllers\CustomerController.vb [VB]
Public
Class
CustomerController
Inherits
System.Web.Mvc.Controller
Function
Details()
As
ActionResult
Return
View()
End Function
End Class
Details() 方法调用View() 方法返回一个ViewResult 。当调用View() 方法时,你可以通过两种方式来指定视图:隐式和显式。Public Class CustomerController
Inherits System.Web.Mvc.Controller
Function Details() As ActionResult
Return View()
End Function
End Class
在列表3 中,视图的名称是被隐式指定的。ASP.NET MVC 框架从动作的名称中判断视图的名称,在这种情况下,动作返回的视图位置如下所示:
\Views\Customer\Details.aspx
ASP.NET MVC 框架按照以下的模式来决定视图的位置:
\Views\{controller}\{action}.aspx
如果你喜欢,可以显示的指定视图的名称。在列表4 中,View() 方法包括了一个显式的视图名称。
列表4 – Controllers\CustomerController.cs with explicit view [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
CustomerController : Controller {
public
ActionResult Details() {
return
View(
"
Details
"
); } } }
namespace MvcApplication1.Controllers
{
public class CustomerController : Controller
{
public ActionResult Details()
{
return View("Details");
}
}
}
列表ing System.Web.Mvc;
4 – Controllers\CustomerController.vb with explicit view [VB]
Public
Class
CustomerController
Inherits
System.Web.Mvc.Controller
Function
Details()
As
ActionResult
Return
View(
"
Details
"
)
End Function
End Class
列表4 中的View() 方法返回了同一个视图。然而,它式显式制订了视图的名称,请注意,当提供视图名称时,不要包含.aspx 扩展名。Public Class CustomerController
Inherits System.Web.Mvc.Controller
Function Details() As ActionResult
Return View("Details")
End Function
End Class
*** Begin Tip ***
如果打算为你的ASP.NET MVC 应用程序构建单元测试,那么显式指定视图名称就是一个很好的方法,否则,你不能测试带有正确名称的视图是否是从控制器动作返回的。
*** End Tip ***
视图的名字中可以包含相对或者绝对路径,当你指定一个相对路径时,视图的位置是相对于普通位置来计算的。例如,我们从Details() 动作来调用视图(“Subfolder/Details”) 时,会从以下位置返回一个视图:
\Views\Details\Subfolder\Details.aspx
你也可以为视图指定一个绝对路径。如果你从Details() 动作调用视图(“~/Details.aspx”) ,那么就会从以下位置返回一个视图:
\Details.aspx
请注意,当你使用绝对路径时,必须提供.aspx 扩展。
View() 方法有很多重载形式,可以接受不同的参数。下面的列表列出了你可以传递给View() 方法的一些参数
· viewName – 视图的名称(或者视图的路径)。
· masterName – 视图母版页的名称。
· model – 传递给视图的模型类。
在第10 章(理解视图母版页和视图用户控件)中,我们会讨论视图母版页,在下一章(理解视图)中,我们会讨论为视图传递模型。
Returning a Redirect Result
很多情况下,你会不得不从一个控制器动作重定向到另一个控制器动作。你可以使用RedirectToAction() 方法返回一个RedirectResult ,它可以将用户从一个控制器动作重定向到另外一个。
例如,列表5 中的Widget 控制器包含一个Details() 动作,当Details() 动作被调用并且没有为id 参数赋值,这时用户被重定向到Index() 动作。
列表5 – Controllers\WidgetController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
WidgetController : Controller {
//
//
GET: /Widget/
public
ActionResult Index() {
return
View(); }
//
//
POST: /Widget/Create
public
ActionResult Details(
int
?
id) {
if
(
!
id.HasValue)
return
RedirectToAction(
"
Index
"
);
return
View(); } } }
namespace MvcApplication1.Controllers
{
public class WidgetController : Controller
{
//
// GET: /Widget/
public ActionResult Index()
{
return View();
}
//
// POST: /Widget/Create
public ActionResult Details(int? id)
{
if (!id.HasValue)
return RedirectToAction("Index");
return View();
}
}
}
列表using System.Web.Mvc;
5 – Controllers\WidgetController.cs [VB]
Public
Class
WidgetController
Inherits
System.Web.Mvc.Controller
Function
Index()
As
ActionResult
Return
View()
End Function
Function
Details(
ByVal
id
As
Integer
?)
As
ActionResult
If
Not
id.HasValue
Then
Return
RedirectToAction(
"
Index
"
)
End
If
Return
View()
End Function
End Class
Public Class WidgetController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Return View()
End Function
Function Details(ByVal id As Integer?) As ActionResult
If Not id.HasValue Then
Return RedirectToAction("Index")
End If
Return View()
End Function
End Class
*** Begin Note ***
列表5 中的id 参数的类型是可空类型。一个可空整型可以是任何整数或者null 。你可以通过在类型关键字后加?的方式创建一个可空类型。
*** End Note ***
RedirectToAction() 方法有多个重载形式,以下的列表说明了使用RedirectToAction() 时所有可能的参数:
· actionName – 控制器动作的名称。
· controllerName – 控制器的名字。
· routeValues – 传递给动作的路由值。
你可以使用controllerName 参数来将程序从一个控制器动作重定向到另外一个控制器,当你指定controllerName 时,不要包含Controller 后缀。例如,使用Product 而不是ProductController :
[C#]
return RedirectToAction(“Index”, “Product”);
[VB]
Return RedirectToAction(“Index”, “Product”)
当你需要传递id 到控制器动作时,提供路由值时特别重要的。例如,想象一下,当你想从其他某一个控制器动作重定向到Details() 动作并且为id 参数传递一个值,在这种情况下,你可以像以下这种方式来调用RedirectToAction() :
[C#]
return RedirectToAction(“Details”, new {id=53});
[VB]
Return RedirectToAction(“Details”, New With {.id=53})
这种调用RedirectToAction() 方法的形式将53 作为id 参数传递给Index() 动作。
*** Begin Note ***
RedirectToAction() 方法的返回一个302 Found 的HTTP 状态码给浏览器来执行重定向到新动作的操作。执行浏览器重定向的一个好处是它可以将浏览器的地址栏更新成新的URL 地址。
*** End Note ***
Returning a Content Result
列表6 中Hello 控制器暴露出的Say() 动作没有返回ActionResult ,相反,这个动作返回了一个字符串,如果你调用这个动作,那么字符串就被返回给浏览器(参见图2 )
图 2 – Results of invoking Say() action
列表6 – Controllers\HelloController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
HelloController : Controller {
public
string
Say() {
return
"
Hello
"
; } } }
namespace MvcApplication1.Controllers
{
public class HelloController : Controller
{
public string Say()
{
return "Hello";
}
}
}
列表using System.Web.Mvc;
6 – Controllers\HelloController.vb [VB]
Public
Class
HelloController
Inherits
System.Web.Mvc.Controller
Function
Say()
As
String
Return
"
Hello!
"
End Function
End Class
动作方法也可以返回日期值、整型值或者任何.NET 框架支持的类型值。
Inherits System.Web.Mvc.Controller
Function Say() As String
Return "Hello!"
End Function
End Class
在这背后,ASP.NET MVC 框架将任何不是ActionResult 类型的值转换为ActionResult 类型。特别的,ASP.NET MVC 框架将任何不是ActionResult 类型的值转换为ContentResult 类型,它通过对值调用ToString() 方法 将其包装成ContentResult 类型。
如果你愿意,你可以像这样显示的返回一个ContentResult :
[C#]
public
ActionResult Say() {
return
Content(
"
Hello!
"
); }
public ActionResult Say()
{
return Content("Hello!");
}
[VB]
Function Say() As ActionResult
Return Content("Hello!")
End Function
Function
Say()
As
ActionResult
Return
Content(
"
Hello!
"
)
End Function
Content() 方法有多个重载形式,下表列出了你可以传递给这个方法的所有参数形式:
· string – 要提交给浏览器的字符串。
· contentType – 内容的MIME 类型(默认是text/html )。
· contentEncoding – 内容的文本编码格式(例如Unicode 或者ASCII )。
Returning a JSON Result
JavaScript 对象符号(JSON )由Douglas Crockford 发明,主要作为XML 的一种替代方案,在AJAX 应用程序中传递数据。例如,你可以将一个数据库记录的集合转换成JSON 形式,将其从服务器端传给浏览器。
*** Begin Note ***
你可以通过访问JSON.org 来了解更多JSON 相关信息。
*** End Note ***
你可以通过调用Json() 方法从动作中返回JSON 。例如,列表7 所示的控制器返回了一个引用语的集合。
列表7 – Controllers\QuotationController.cs [C#]
using
System.Collections.Generic;
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
QuotationController : Controller {
public
ActionResult Index() {
return
View(); }
public
ActionResult List() { var quotes
=
new
List
<
string
>
{
"
Look before you leap
"
,
"
The early bird gets the worm
"
,
"
All hat, no cattle
"
};
return
Json(quotes); } } }
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class QuotationController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult List()
{
var quotes = new List<string>
{
"Look before you leap",
"The early bird gets the worm",
"All hat, no cattle"
};
return Json(quotes);
}
}
}
using System.Collections.Generic;
列表7 – Controllers\QuotationController.vb [VB]
Public
Class
QuotationController
Inherits
System.Web.Mvc.Controller
Function
Index()
As
ActionResult
Return
View()
End Function
Function
List()
As
ActionResult
Dim
quotes
As
New
List(
Of
String
)() quotes.Add(
"
Look before you leap
"
) quotes.Add(
"
The early bird gets the worm
"
) quotes.Add(
"
All hat, no cattle
"
)
Return
Json(quotes)
End Function
End Class
Public Class QuotationController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Return View()
End Function
Function List() As ActionResult
Dim quotes As New List(Of String)()
quotes.Add("Look before you leap")
quotes.Add("The early bird gets the worm")
quotes.Add("All hat, no cattle")
Return Json(quotes)
End Function
End Class
*** Begin Note ***
在这背后,Json() 方法使用.NET 框架中一个被称为JavaScriptSerializer 的类将一个对象序列化为JSON 形式。你可以通过注册自定义转换器的方式来控制如何序列化对象。
*** End Note ***
当List() 动作被调用时,它返回了如下所示的一个JSON 形式的引用语集合:
["Look before you leap", "The early bird gets the worm", "All hat, no cattle"]
你可以通过执行一个针对服务器的AjaX 请求来调用Index() 方法。列表8 中的视图捕捉到了引用语集合并且随机的显示它们。
图 3 – Using JSON to retrieve quotations.
列表8 – Views\Quotation\Index.aspx
<%
@ Page Title
=
""
Language
=
"
C#
"
MasterPageFile
=
"
~/Views/Shared/Site.Master
"
Inherits
=
"
System.Web.Mvc.ViewPage
"
%>
<
asp:Content
ID
="Content2"
ContentPlaceHolderID
="MainContent"
runat
="server"
>
<
script
src
="http://www.cnblogs.com/Scripts/jquery-1.2.6.js"
type
="text/javascript"
></
script
>
<
script
type
="text/javascript"
>
$(getQuote);
function
getQuote() { $.getJSON(
"
Quotation/List
"
, showQuote); }
function
showQuote(data) {
var
index
=
Math.floor(Math.random()
*
3
); $(
"
#quote
"
).text(data[index]); }
</
script
>
<
p
id
="quote"
></
p
>
<
button
onclick
="getQuote()"
>
Get Quote
</
button
>
</
asp:Content
>
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="http://www.cnblogs.com/Scripts/jquery-1.2.6.js" type="text/javascript"></script>
<script type="text/javascript">
$(getQuote);
function getQuote() {
$.getJSON("Quotation/List", showQuote);
}
function showQuote(data) {
var index = Math.floor(Math.random() * 3);
$("#quote").text(data[index]);
}
</script>
<p id="quote"></p>
<button onclick="getQuote()">Get Quote</button>
</asp:Content>
*** Begin Note ***
列表8 中的视图采用jQuery 来从服务器返回JSON 结果,我们会在第17 章(使用jQuery )中详细讨论jQuery 。
*** End Note ***
Json() 方法有一些重载形式,它支持以下的这些参数形式:
· data – 需要进行序列化的内容。
· contentType – 内容的MIME 类型(默认时application.json )。
· contentEncoding – 内容的文本编码格式(例如Unicode 或者ASCII )。
Returning a File Result
你可以从动作中返回一个文件。例如,你可以返回一个图片文件、一个Microsoft Word 文档或者一个Microsoft Excel 文件。
列表9 所示的控制器中暴露两个动作,分别时Index() 和Download() ,Index() 显示一个视图,它包含一个指向Download() 动作的链接,当点击这个链接时,你会看到一个浏览或者保存文件的对话框(参见图4 )。
图4 – 下载一个文件
列表9 – Controllers\ContentManagerController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
ContentManagerController : Controller {
public
ActionResult Index() {
return
View(); }
public
ActionResult Download() {
return
File(
"
~/Content/CompanyPlans.docx
"
,
"
application/vnd.openxmlformats-officedocument.wordprocessingml.document
"
,
"
CompanyPlans.docx
"
); } } }
namespace MvcApplication1.Controllers
{
public class ContentManagerController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Download()
{
return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx");
}
}
}
列表using System.Web.Mvc;
9 – Controllers\ContentManagerController.vb [VB]
Public
Class
ContentManagerController
Inherits
System.Web.Mvc.Controller
Function
Index()
Return
View()
End Function
Function
Download()
As
ActionResult
Return
File(
"
~/Content/CompanyPlans.docx
"
,
"
application/vnd.openxmlformats-officedocument.wordprocessingml.document
"
,
"
CompanyPlans.docx
"
)
End Function
End Class
Download() 动作返回一个名为CompanyPlans.docx 的Microsoft Word 文档。请注意,File() 方法需要3 个参数:文档的路径、文档的内容类型以及文档的名字。Microsoft Word DOCX 文档的MIME 类型是:Public Class ContentManagerController
Inherits System.Web.Mvc.Controller
Function Index()
Return View()
End Function
Function Download() As ActionResult
Return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx")
End Function
End Class
application/vnd.openxmlformats-officedocument.wordprocessingml.document
File() 方法有多个重载形式,它可以接受以下参数:
· filename – 需要下载的文档的路径。
· contentType – 需要下载的文档的MIME 类型。
· fileDownloadName – 文档的名字,它会显示在浏览器对话中。
· fileContents – 如果不提供需要下载的文件的路径,你可以以字节数组的形式提供文件的实际内容。
· fileStream – 如果不提供需要下载的文件的路径,你可以以文件流的形式提供文件的实际内容。
*** Begin Note ***
File() 方法使用HTTP 头中的Content-Disposition 来设置下载文件的名字。
*** End Note ***
Controlling How Actions are Invoked
ASP.NET MVC 框架关于如何调用动作的默认算法非常简单。例如,当你输入/Product/Details 时,ProductController 控制器中的Details() 方法被执行。
然而,事情很快就变得更复杂。当你有多个方法的名字相同时会发生什么情况?在需要传递表单数据或者其他内容时如何调用动作?当做出一个AjaX 请求时如何调用一个特定的动作?
在本节中,你会了解到如何使用AcceptVerbs 、ActionName 以及ActionMethodSelector 特性来调用一个特定的动作。
Using AcceptVerbs
AcceptVerbs 特性可以是你能够让一个动作只在执行特定的HTTP 操作时才能被调用。例如,你可以使用AcceptVerbs 特性来使得一个动作只能在执行HTTP POST 操作才能被调用。
列表10 中的Employees 控制器暴露了两个名为Create() 的动作,第一个动作用来显示一个创建新员工的HTML 表单,第二个动作向数据库中插入一个新员工。
两个Create() 都装饰了AcceptVerbs 特性。第一个动作只能在执行HTTP GET 操作时才能被调用,第二个动作只能在执行HTTP POST 操作时才能被调用。
列表10 – Controllers\EmployeeController.cs [C#]
using
System.Web.Mvc;
using
MvcApplication1.Models;
namespace
MvcApplication1.Controllers {
public
class
EmployeeController : Controller {
private
EmployeeRepository _repository
=
new
EmployeeRepository();
//
GET: /Employee/
public
ActionResult Index() {
return
View(); }
//
GET: /Employee/Create
[AcceptVerbs(HttpVerbs.Get)]
public
ActionResult Create() {
return
View(); }
//
POST: /Employee/Create
[AcceptVerbs(HttpVerbs.Post)]
public
ActionResult Create(Employee employeeToCreate) {
try
{ _repository.InsertEmployee(employeeToCreate);
return
RedirectToAction(
"
Index
"
); }
catch
{
return
View(); } }
//
DELETE: /Employee/Delete/1
[AcceptVerbs(HttpVerbs.Delete)]
public
ActionResult Delete(
int
id) { _repository.DeleteEmployee(id);
return
Json(
true
); } } }
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class EmployeeController : Controller
{
private EmployeeRepository _repository = new EmployeeRepository();
// GET: /Employee/
public ActionResult Index()
{
return View();
}
// GET: /Employee/Create
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create()
{
return View();
}
// POST: /Employee/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToCreate)
{
try
{
_repository.InsertEmployee(employeeToCreate);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
// DELETE: /Employee/Delete/1
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
_repository.DeleteEmployee(id);
return Json(true);
}
}
}
列表using System.Web.Mvc;
10 – Controllers\EmployeeController.vb [VB]
Public
Class
EmployeeController
Inherits
System.Web.Mvc.Controller
Private
_repository
As
New
EmployeeRepository()
'
GET: /Employee/Create
Function
Index()
As
ActionResult
Return
View()
End Function
'
GET: /Employee/Create
<
AcceptVerbs(HttpVerbs.Get)
>
_
Function
Create()
As
ActionResult
Return
View()
End Function
'
POST: /Employee/Create
<
AcceptVerbs(HttpVerbs.Post)
>
_
Function
Create(
ByVal
employeeToCreate
As
Employee)
As
ActionResult
Try
_repository.InsertEmployee(employeeToCreate)
Return
RedirectToAction(
"
Index
"
)
Catch
Return
View()
End
Try
End Function
'
DELETE: /Employee/Create
<
AcceptVerbs(HttpVerbs.Delete)
>
_
Function
Delete(
ByVal
id
As
Integer
)
As
ActionResult _repository.DeleteEmployee(id)
Return
Json(
True
)
End Function
End Class
大部分人都很熟悉HTTP GET 和HTTP POST ,当你任何时候通过在浏览器中输入地址的方式来获取一个网站的网页时,就是在执行HTTP GET 操作;当你提交一个HTML 表单并带有”method = post” 特性时,就是在执行HTTP POST 操作。Public Class EmployeeController
Inherits System.Web.Mvc.Controller
Private _repository As New EmployeeRepository()
' GET: /Employee/Create
Function Index() As ActionResult
Return View()
End Function
' GET: /Employee/Create
<AcceptVerbs(HttpVerbs.Get)> _
Function Create() As ActionResult
Return View()
End Function
' POST: /Employee/Create
<AcceptVerbs(HttpVerbs.Post)> _
Function Create(ByVal employeeToCreate As Employee) As ActionResult
Try
_repository.InsertEmployee(employeeToCreate)
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
' DELETE: /Employee/Create
<AcceptVerbs(HttpVerbs.Delete)> _
Function Delete(ByVal id As Integer) As ActionResult
_repository.DeleteEmployee(id)
Return Json(True)
End Function
End Class
大部分都没有意识到HTTP 协议支持很多额外的HTTP 操作类型:
· OPTIONS – 返回可用的通信选项的信息。
· GET – 返回请求可以识别出来的所有信息。
· HEAD – 除了不返回消息体外,其他操作和GET 一样。
· POST – 公告新的信息或者更新已经存在的信息。
· PUT – 公告新的信息或者更新已经存在的信息。
· DELETE – 删除信息。
· TRACE – 执行消息跟踪。
· CONNECT – 用来做SSL 通道。
*** Begin Note **
HTTP 操作是作为HTTP1.1 标准的一部分存在的,你可以通过以下地址来获取HTTP 1.1 标准的信息: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html .
*** End Note ***
在执行Ajax 请求时,你可以选择这些额外的HTTP 操作。列表10 中的控制器包括Delete() 动作,它只能在执行HTTP DELETE 操作时被调用,列表11 中的视图包含了一个删除链接,它使用Ajax 来执行一个HTTP DELETE 操作。
列表11 – Views\Employee\Delete.aspx [C#]
<%
@ Page Title
=
""
Language
=
"
C#
"
MasterPageFile
=
"
~/Views/Shared/Site.Master
"
Inherits
=
"
System.Web.Mvc.ViewPage
"
%>
<
asp:Content
ID
="Content2"
ContentPlaceHolderID
="MainContent"
runat
="server"
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"
type
="text/javascript"
></
script
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js"
type
="text/javascript"
></
script
>
<
h2
>
Index
</
h2
>
<%
=
Ajax.ActionLink (
"
Delete
"
,
//
link text
"
Delete
"
,
//
action name
new
{id
=
39
},
//
route values
new
AjaxOptions {HttpMethod
=
"
DELETE
"
, Confirm
=
"
Delete Employee?
"
} )
%>
</
asp:Content
>
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<h2>Index</h2>
<%= Ajax.ActionLink
(
"Delete", // link text
"Delete", // action name
new {id=39}, // route values
new AjaxOptions {HttpMethod="DELETE", Confirm="Delete Employee?"}
) %>
</asp:Content>
列表11 – Views\Employee\Delete.aspx [VB]
<%
@ Page Title
=
""
Language
=
"
VB
"
MasterPageFile
=
"
~/Views/Shared/Site.Master
"
Inherits
=
"
System.Web.Mvc.ViewPage
"
%>
<
asp:Content
ID
="Content2"
ContentPlaceHolderID
="MainContent"
runat
="server"
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"
type
="text/javascript"
></
script
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js"
type
="text/javascript"
></
script
>
<
h2
>
Index
</
h2
>
<%
=
Ajax.ActionLink( _
"
Delete
"
, _
"
Delete
"
, _
New
With
{.id
=
39
}, _
New
AjaxOptions
With
{.HttpMethod
=
"
DELETE
"
, .Confirm
=
"
Delete Employee?
"
} _ )
%>
<%
=
DateTime.Now
%>
</
asp:Content
>
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<h2>Index</h2>
<%=Ajax.ActionLink( _
"Delete", _
"Delete", _
New With {.id = 39}, _
New AjaxOptions With {.HttpMethod = "DELETE", .Confirm = "Delete Employee?"} _
)%>
<%=DateTime.Now%>
</asp:Content>
在列表11 中,Ajax.ActionLink() 方法帮助提交一个链接,用来执行一个HTTP DELETE 操作。这个链接删除了Id 为39 的员工,你可以在Firebug 中证实这个链接执行了HTTP DELETE 操作。
图5 – 执行一个HTTP DELETE 操作
*** Begin Note ***
Firebug 是一个用于调试Ajax 应用程序的重要工具,它是Mozilla Firefox 的一个扩展,你可以从http://getFirebug.com . 上下载到。
*** End Note ***
Using ActionName
ActionName 特性使你能够暴露一个和方法名不同的动作。它主要用于以下两种情况:
首先,当控制器有重载方法时,你可以使用ActionName 特性来区分这两个方法,换言之,你可以使用ActionName 特性来将两个具有相同名字的方法暴露成具有不同名字的动作。
例如,假设你创建了一个Product 控制器,它有两个名为Details() 的重载方法,第一个方法接受一个id 参数,第二个不接受,在这种情况下,你可以使用ActionName 特性来区分这两个Details() 方法,可以将这两个方法暴露成两个具有不同名字的动作。
其次,当一个控制器包括不同名字的方法,但是你想将这些不同的方法暴露成同一个动作时,ActionName 特性也是很有用的。例如,列表12 中的控制器暴露了两个动作,名为Edit() ,它们接受相同的参数:
列表12 – Controllers\MerchandiseController.cs [C#]
using
System.Web.Mvc;
using
MvcApplication1.Models;
namespace
MvcApplication1.Controllers {
public
class
MerchandiseController : Controller {
private
MerchandiseRepository _repository
=
new
MerchandiseRepository();
//
GET: /Merchandise/Edit
[ActionName(
"
Edit
"
)] [AcceptVerbs(HttpVerbs.Get)]
public
ActionResult Edit_GET(Merchandise merchandiseToEdit) {
return
View(merchandiseToEdit); }
//
POST: /Merchandise/Edit
[ActionName(
"
Edit
"
)] [AcceptVerbs(HttpVerbs.Post)]
public
ActionResult Edit_POST(Merchandise merchandiseToEdit) {
try
{ _repository.Edit(merchandiseToEdit);
return
RedirectToAction(
"
Edit
"
); }
catch
{
return
View(); } } } }
列表12 – Controllers\MerchandiseController.vb [VB]
Public
Class
MerchandiseController
Inherits
System.Web.Mvc.Controller
Private
_repository
As
New
MerchandiseRepository()
'
GET: /Merchandise/Edit
<
ActionName(
"
Edit
"
)
>
_
<
AcceptVerbs(HttpVerbs.Get)
>
_
Function
Edit_GET(
ByVal
merchandiseToEdit
As
Merchandise)
As
ActionResult
Return
View(merchandiseToEdit)
End Function
'
POST: /Merchandise/Edit
<
ActionName(
"
Edit
"
)
>
_
<
AcceptVerbs(HttpVerbs.Post)
>
_
Function
Edit_POST(
ByVal
merchandiseToEdit
As
Merchandise)
As
ActionResult
Try
_repository.Edit(merchandiseToEdit)
Return
RedirectToAction(
"
Edit
"
)
Catch
Return
View()
End
Try
End Function
End Class
你不能在一个类中拥有两个具有相同名字和相同参数的方法,然而,你可以拥有两个具有相同名称和相同参数的动作。Public Class MerchandiseController
Inherits System.Web.Mvc.Controller
Private _repository As New MerchandiseRepository()
' GET: /Merchandise/Edit
<ActionName("Edit")> _
<AcceptVerbs(HttpVerbs.Get)> _
Function Edit_GET(ByVal merchandiseToEdit As Merchandise) As ActionResult
Return View(merchandiseToEdit)
End Function
' POST: /Merchandise/Edit
<ActionName("Edit")> _
<AcceptVerbs(HttpVerbs.Post)> _
Function Edit_POST(ByVal merchandiseToEdit As Merchandise) As ActionResult
Try
_repository.Edit(merchandiseToEdit)
Return RedirectToAction("Edit")
Catch
Return View()
End Try
End Function
End Class
列表12 中的两个Edit() 动作时通过AcceptVerbs 特性来进行区分的。第一个Edit() 动作只能在执行HTTP GET 操作时被调用,第二个只能在执行HTTP POST 操作时被调用,ActionName 特性使得你能够以相同的名字暴露这两个动作。
Using ActionMethodSelector
你可以构建你自己的特性来控制如何调用控制器动作,通过继承抽象类ActionMethodSelectorAttribute ,你可以构建自己的特性。
这是一个非常简单的类,它有一个简单的名为IsValidForRequest () 的方法,这是你必须要实现的,如果这个方法返回false ,那么动作方法就不会被调用。
在实现IsValidForRequest () 方法时,你可以使用任何你想用的标准,这些标准可以是时间、随机数字生成器或者当前外面的温度等等。关于如何使用ActionMethod 特性,列表13 中的AjaxMethod 特性时一个更加实用的列子,AjaxMethod 特性保证一个方法只有在执行Ajax 请求时才被调用。
列表13 – Selectors\AjaxMethodAttribute.cs [C#]
using
System.Reflection;
using
System.Web.Mvc;
namespace
MvcApplication1.Selectors {
public
class
AjaxMethod : ActionMethodSelectorAttribute {
public
override
bool
IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
return
controllerContext.HttpContext.Request.IsAjaxRequest(); } } }
using System.Web.Mvc;
namespace MvcApplication1.Selectors
{
public class AjaxMethod : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request.IsAjaxRequest();
}
}
}
using System.Reflection;
列表13 – Selectors\AjaxMethodAttribute.vb [VB]
Imports
System.Reflection
Public
Class
AjaxMethodAttribute
Inherits
ActionMethodSelectorAttribute
Public
Overrides
Function
IsValidForRequest(
ByVal
controllerContext
As
ControllerContext,
ByVal
methodInfo
As
MethodInfo)
As
Boolean
Return
controllerContext.HttpContext.Request.IsAjaxRequest
End Function
End Class
列表13 中的选择器简单的以IsAjaxRequest() 方法的返回值作为选择标准返回。Imports System.Reflection
Public Class AjaxMethodAttribute
Inherits ActionMethodSelectorAttribute
Public Overrides Function IsValidForRequest(ByVal controllerContext As ControllerContext, ByVal methodInfo As MethodInfo) As Boolean
Return controllerContext.HttpContext.Request.IsAjaxRequest
End Function
End Class
列表14 表明如何实用AjaxMethod 特性。
列表14 – Controllers\NewsController.cs [C#]
using
System;
using
System.Collections.Generic;
using
System.Web.Mvc;
using
MvcApplication1.Selectors;
namespace
MvcApplication1.Controllers {
public
class
NewsController : Controller {
private
readonly
List
<
string
>
_news
=
new
List
<
string
>
();
private
Random _rnd
=
new
Random();
public
NewsController() { _news.Add(
"
Moon explodes!
"
); _news.Add(
"
Stock market up 200 percent!
"
); _news.Add(
"
Talking robot created!
"
); }
public
ActionResult Index() { var selectedIndex
=
_rnd.Next(_news.Count); ViewData.Model
=
_news[selectedIndex];
return
View(); } [AjaxMethod] [ActionName(
"
Index
"
)]
public
string
Index_AJAX() { var selectedIndex
=
_rnd.Next(_news.Count);
return
_news[selectedIndex]; } } }
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication1.Selectors;
namespace MvcApplication1.Controllers
{
public class NewsController : Controller
{
private readonly List<string> _news = new List<string>();
private Random _rnd = new Random();
public NewsController()
{
_news.Add("Moon explodes!");
_news.Add("Stock market up 200 percent!");
_news.Add("Talking robot created!");
}
public ActionResult Index()
{
var selectedIndex = _rnd.Next(_news.Count);
ViewData.Model = _news[selectedIndex];
return View();
}
[AjaxMethod]
[ActionName("Index")]
public string Index_AJAX()
{
var selectedIndex = _rnd.Next(_news.Count);
return _news[selectedIndex];
}
}
}
using System;
列表14 – Controllers\NewsController.vb [VB]
Public
Class
NewsController
Inherits
System.Web.Mvc.Controller
Private
ReadOnly
_news
As
New
List(
Of
String
)
Private
_rnd
As
New
Random()
Sub
New
() _news.Add(
"
Moon explodes!
"
) _news.Add(
"
Stock market up 200 percent!
"
) _news.Add(
"
Talking robot created!
"
)
End Sub
Function
Index()
As
ActionResult
Dim
selectedIndex
=
_rnd.Next(_news.Count) ViewData.Model
=
_news(selectedIndex)
Return
View()
End Function
<
AjaxMethod()
>
_
<
ActionName(
"
Index
"
)
>
_
Function
Index_AJAX()
As
String
Dim
selectedIndex
=
_rnd.Next(_news.Count)
Return
_news(selectedIndex)
End Function
End Class
列表14 中的控制器暴露了两个名为Index() 的动作,第一个Index() 动作可以被一个普通的浏览器请求调用,第二个动作可以被一个Ajax 请求调用。Public Class NewsController
Inherits System.Web.Mvc.Controller
Private ReadOnly _news As New List(Of String)
Private _rnd As New Random()
Sub New()
_news.Add("Moon explodes!")
_news.Add("Stock market up 200 percent!")
_news.Add("Talking robot created!")
End Sub
Function Index() As ActionResult
Dim selectedIndex = _rnd.Next(_news.Count)
ViewData.Model = _news(selectedIndex)
Return View()
End Function
<AjaxMethod()> _
<ActionName("Index")> _
Function Index_AJAX() As String
Dim selectedIndex = _rnd.Next(_news.Count)
Return _news(selectedIndex)
End Function
End Class
AjaxMethod 特性被应用于第二个Index() 动作,如果这个动作不被装饰AjaxMethod 特性,那么你会得到一个Ambiguous Match Exception , 因为ASP.NET MVC 框架不能确定要调用哪一个动作( 参见图6) 。
图6 – An Ambiguous Match Exception
列表15 中的视图实用Ajax.ActionLink() 方法来提交Get News 链接,链接用于显示新闻。如果你使用上层浏览器——一种支持基本JavaScript 的浏览器——这时点击链接来向服务器执行一个Ajax 请求,带有AjaxMethod 特性的Index() 方法被调用,页面在没有回传的情况下被更新。
另一方面,如果你使用下层浏览器——一种不支持基本JavaScript 的浏览器——这是点击Get News 链接来执行一个通常的回传操作,页面也会更新一个新项目,但是用户必须忍受糟糕的带有回传的用户体验(参见图7 )。
图 7 – 显示新闻
列表15 – Views\News\Index.aspx [C#]
<%
@ Page Title
=
""
Language
=
"
C#
"
MasterPageFile
=
"
~/Views/Shared/Site.Master
"
Inherits
=
"
System.Web.Mvc.ViewPage
"
%>
<
asp:Content
ID
="Content2"
ContentPlaceHolderID
="MainContent"
runat
="server"
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"
type
="text/javascript"
></
script
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js"
type
="text/javascript"
></
script
>
<%
=
Ajax.ActionLink(
"
Get News
"
,
"
Index
"
,
new
AjaxOptions {UpdateTargetId
=
"
news
"
})
%>
<
span
id
="news"
></
span
>
</
asp:Content
>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<%=Ajax.ActionLink("Get News", "Index", new AjaxOptions {UpdateTargetId = "news"})%>
<span id="news"></span>
</asp:Content>
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
列表15 – Views\News\Index.aspx [VB]
<%
@ Page Title
=
""
Language
=
"
VB
"
MasterPageFile
=
"
~/Views/Shared/Site.Master
"
Inherits
=
"
System.Web.Mvc.ViewPage
"
%>
<
asp:Content
ID
="Content2"
ContentPlaceHolderID
="MainContent"
runat
="server"
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"
type
="text/javascript"
></
script
>
<
script
src
="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js"
type
="text/javascript"
></
script
>
<%
=
Ajax.ActionLink(
"
Get News
"
,
"
Index
"
,
New
AjaxOptions
With
{.UpdateTargetId
=
"
news
"
})
%>
<
span
id
="news"
></
span
>
</
asp:Content
>
<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<%=Ajax.ActionLink("Get News", "Index", New AjaxOptions With {.UpdateTargetId = "news"})%>
<span id="news"></span>
</asp:Content>
Handling Unknown Actions
控制器包含一个名为HandleUnknownAction () 的特殊方法,当控制器找不到和浏览器请求相匹配的动作时,会自动调用这个方法。例如,如果你请求的URL 地址为/Product/DoSomethingCrazy ,而Product 控制器中并不包含名为DoSomethingCrazy () 的方法,这时,Product 控制器的HandleUnknownAction () 被调用。
默认情况下,这个方法抛出一个404 Resource Not Found 的异常,然而,你可以重写这个方法使其做任何你想做的。例如,列表16 中的控制器显示了一个自定义错误信息。
列表16 – Controllers\CatalogController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
CatalogController : Controller {
public
ActionResult Create() {
return
View(); }
public
ActionResult Delete(
int
id) {
return
View(); }
protected
override
void
HandleUnknownAction(
string
actionName) { ViewData[
"
actionName
"
]
=
actionName; View(
"
Unknown
"
).ExecuteResult(
this
.ControllerContext); } } }
namespace MvcApplication1.Controllers
{
public class CatalogController : Controller
{
public ActionResult Create()
{
return View();
}
public ActionResult Delete(int id)
{
return View();
}
protected override void HandleUnknownAction(string actionName)
{
ViewData["actionName"] = actionName;
View("Unknown").ExecuteResult(this.ControllerContext);
}
}
}
using System.Web.Mvc;
列表16 – Controllers\CatalogController.vb [VB]
Public
Class
CatalogController
Inherits
System.Web.Mvc.Controller
Function
Create()
As
ActionResult
Return
View()
End Function
Function
Delete()
As
ActionResult
Return
View()
End Function
Protected
Overrides
Sub
HandleUnknownAction(
ByVal
actionName
As
String
) ViewData(
"
actionName
"
)
=
actionName View(
"
Unknown
"
).ExecuteResult(
Me
.ControllerContext)
End Sub
End Class
Inherits System.Web.Mvc.Controller
Function Create() As ActionResult
Return View()
End Function
Function Delete() As ActionResult
Return View()
End Function
Protected Overrides Sub HandleUnknownAction(ByVal actionName As String)
ViewData("actionName") = actionName
View("Unknown").ExecuteResult(Me.ControllerContext)
End Sub
End Class
如果你请求的URL ublic Class CatalogController
地址是/Catalog/Create 或者 /Catalog/Delete , 那么Catalog 控制器会返回Create 视图或者Delete 视图,如果你请求的URL 地址包含未知的动作,例如/Catalog/Wow 或者 /Catalog/Eeeks ,那么HandleUnknownAction() 方法就不会调用。
在列表16 中,HandleUnknownAction () 方法将动作的明泽添加到视图数据中提交给名为Unknown 的视图(参见图8 )。
图8 – 显示Unknown 视图
Testing Controllers and Actions
ASP.NET MVC 团队努力工作来确保可以很容易对控制器动作进行测试,如果你想测试控制器动作,你只需简单的实例化控制器,然后调用动作方法。
例如,列表17 中的控制器暴露了两个动作,名为Index() 和Details() ,如果你调用Details() 动作时没有传递id 参数,那么你应该将其重定向到Index() 动作。
列表17 – Controllers\PersonController.cs [C#]
using
System.Web.Mvc;
namespace
MvcApplication1.Controllers {
public
class
PersonController : Controller {
public
ActionResult Index() {
return
View(
"
Index
"
); }
public
ActionResult Details(
int
?
id) {
if
(
!
id.HasValue)
return
RedirectToAction(
"
Index
"
);
return
View(
"
Details
"
); } } }
namespace MvcApplication1.Controllers
{
public class PersonController : Controller
{
public ActionResult Index()
{
return View("Index");
}
public ActionResult Details(int? id)
{
if (!id.HasValue)
return RedirectToAction("Index");
return View("Details");
}
}
}
using System.Web.Mvc;
列表17 – Controllers\PersonController.vb [VB]
Public
Class
PersonController
Inherits
System.Web.Mvc.Controller
Function
Index()
As
ActionResult
Return
View(
"
Index
"
)
End Function
Function
Details(
ByVal
id
As
Integer
?)
As
ActionResult
If
Not
id.HasValue
Then
Return
RedirectToAction(
"
Index
"
)
End
If
Return
View(
"
Details
"
)
End Function
End Class
Public Class PersonController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Return View("Index")
End Function
Function Details(ByVal id As Integer?) As ActionResult
If Not id.HasValue Then
Return RedirectToAction("Index")
End If
Return View("Details")
End Function
End Class
*** Begin Warning ***
当动作返回一个视图时,你必须显示的写出视图的名称,否则你不能在单元测试中对视图的名称进行验证。例如,在列表17 中,Index() 方法返回View(“Index”) 而不是View() 。
*** End Warning ***
列表18 中的单元测试说明了如何对Person 控制器暴露出来的动作进行测试,第一个单元测试,名为DetailsWithId () ,证明了在调用Details() 并传递一个值作为id 参数时,返回Details 视图。
第二个单元测试,名为DetailsWithoutId () ,证明了在调用Details ()方法并不传入任何参数时,返回一个RedirectToRouteResult 。
列表18 – Controllers\PersonControllerTest.cs [C#]
using
System.Web.Mvc;
using
Microsoft.VisualStudio.TestTools.UnitTesting;
using
MvcApplication1.Controllers;
namespace
MvcApplication1.Tests.Controllers { [TestClass]
public
class
PersonControllerTest { [TestMethod]
public
void
DetailsWithId() {
//
Arrange
var controller
=
new
PersonController();
//
Act
var result
=
(ViewResult)controller.Details(
33
);
//
Assert
Assert.AreEqual(
"
Details
"
, result.ViewName); } [TestMethod]
public
void
DetailsWithoutId() {
//
Arrange
var controller
=
new
PersonController();
//
Act
var result
=
(RedirectToRouteResult)controller.Details(
null
);
//
Assert
Assert.AreEqual(
"
Index
"
, result.RouteValues[
"
action
"
]); } } }
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcApplication1.Controllers;
namespace MvcApplication1.Tests.Controllers
{
[TestClass]
public class PersonControllerTest
{
[TestMethod]
public void DetailsWithId()
{
// Arrange
var controller = new PersonController();
// Act
var result = (ViewResult)controller.Details(33);
// Assert
Assert.AreEqual("Details", result.ViewName);
}
[TestMethod]
public void DetailsWithoutId()
{
// Arrange
var controller = new PersonController();
// Act
var result = (RedirectToRouteResult)controller.Details(null);
// Assert
Assert.AreEqual("Index", result.RouteValues["action"]);
}
}
}
using System.Web.Mvc;
列表18 – Controllers\PersonControllerTest.vb [VB]
Imports
Microsoft.VisualStudio.TestTools.UnitTesting
Imports
System.Web.Mvc
<
TestClass()
>
Public
Class
PersonControllerTest
<
TestMethod()
>
_
Public
Sub
DetailsWithId()
'
Arrange
Dim
controller
As
New
PersonController()
'
Act
Dim
result
As
ViewResult
=
controller.Details(
33
)
'
Assert
Assert.AreEqual(
"
Details
"
, result.ViewName)
End Sub
<
TestMethod()
>
_
Public
Sub
DetailsWithoutId()
'
Arrange
Dim
controller
As
New
PersonController()
'
Act
Dim
result
As
RedirectToRouteResult
=
controller.Details(
Nothing
)
'
Assert
Assert.AreEqual(
"
Index
"
, result.RouteValues(
"
action
"
))
End Sub
End Class
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc
<TestClass()> Public Class PersonControllerTest
<TestMethod()> _
Public Sub DetailsWithId()
' Arrange
Dim controller As New PersonController()
' Act
Dim result As ViewResult = controller.Details(33)
' Assert
Assert.AreEqual("Details", result.ViewName)
End Sub
<TestMethod()> _
Public Sub DetailsWithoutId()
' Arrange
Dim controller As New PersonController()
' Act
Dim result As RedirectToRouteResult = controller.Details(Nothing)
' Assert
Assert.AreEqual("Index", result.RouteValues("action"))
End Sub
End Class
*** Begin Note ***
如果想了解更多关于创建和运行单元测试的内容,请参考本书的附录B 。
*** End Note ***
Summary
本章致力于讨论ASP.NET MVC 控制器,本章的目标时在如何创建控制器和控制器动作上提供深入的解释。
在本章的第一部分,我们浏览了控制器动作可以返回的不同类型的ActionResult ,你可以学习到如何返回视图、重定向用户到另一个动作、返回JSON 以及返回一个可以下载的文件。
接下来,我们讨论了一些特性,这些特性可以应用在控制器中,对控制器动作如何被调用进行控制的。你可以学习到如何使用AcceptVerbs 和ActionName 特性,你还可以学习到如何创建自定义ActionSelect 特性来使得控制器动作只能在执行Ajax 请求时被调用。
最后,我们讨论如何为控制器构建单元测试。你可以学习到如何测试控制器是否返回诸如ViewResult 或者RedirectToRouteResult 等不同的ActionResult 。