首页 > Linux操作系统 > Linux操作系统 > ASP.NET MVC Unleashed (3) (续)

ASP.NET MVC Unleashed (3) (续)

原创 Linux操作系统 作者:geez 时间:2009-03-06 14:05:15 0 删除 编辑
Controlling How Actions are Invoked
The default algorithm for how the ASP.NET MVC framework invokes actions is pretty simple. If you type /Product/Details, for example, then the Details() method of the ProductController class is executed.
However, things can quickly become more complicated. What happens when you have multiple methods with the same name? How do you invoke an action when posting form. data but not otherwise? How do you invoke a particular action when an Ajax request is made?
In this section, you learn how to use the AcceptVerbs, ActionName, and ActionMethodSelector attributes to specify when a particular action gets invoked.
ASP.NET MVC框架调用行为(actions)的默认算法相当简单。比方说,如果您键入/Product/Details,那么ProductController类的Details()方法将被调用、
本节里,您将学习如何使用AcceptVerbs, ActionName, ActionMethodSelector属性来指定某个特定行为的调用方式。

Using AcceptVerbs
The AcceptVerbs attribute enables you to prevent an action from being invoked unless a particular HTTP operation is performed. For example, you can use the AcceptVerbs attribute to prevent an action from being invoked unless an HTTP POST operation is performed.
The Employee controller in Listing 10 exposes two actions named Create(). The first Create() action is used to display an HTML form. for creating a new employee. The second Create() action inserts the new employee into the database.
Both Create() methods are decorated with the AcceptVerbs attribute. The first Create() action can only be invoked by an HTTP GET operation and the second Create() action can only be invoked by an HTTP POST operation.
AcceptVerbs属性能够用来限定某个行为只能通过特定的HTTP操作来调用。例如,您可以使用AcceptVerbs去限定某个行为仅能通过HTTP POST操作才能调用。
Listing 10中的Employee控制器包括两个名为Create()的行为,第一个Create()行为仅能通过HTTP GET操作来调用,而第二个Create()行为仅能通过HTTP POST操作来调用
Listing 10 – Controllers\EmployeeController.cs [C#]
1.	using System.Web.Mvc;  
2.	using MvcApplication1.Models;  
4.	namespace MvcApplication1.Controllers  
5.	{  
6.	    public class EmployeeController : Controller  
7.	    {  
8.	        private EmployeeRepository _repository = new EmployeeRepository();  
10.	        // GET: /Employee/  
11.	        public ActionResult Index()  
12.	        {  
13.	            return View();  
14.	        }   
16.	        // GET: /Employee/Create  
17.	        [AcceptVerbs(HttpVerbs.Get)]  
18.	        public ActionResult Create()  
19.	        {  
20.	            return View();  
21.	        }   
23.	        // POST: /Employee/Create  
24.	        [AcceptVerbs(HttpVerbs.Post)]  
25.	        public ActionResult Create(Employee employeeToCreate)  
26.	        {  
27.	            try  
28.	            {  
29.	                _repository.InsertEmployee(employeeToCreate);  
30.	                return RedirectToAction("Index");  
31.	            }  
32.	            catch  
33.	            {  
34.	                return View();  
35.	            }  
36.	        }  
38.	        // DELETE: /Employee/Delete/1  
39.	        [AcceptVerbs(HttpVerbs.Delete)]  
40.	        public ActionResult Delete(int id)  
41.	        {  
42.	            _repository.DeleteEmployee(id);  
43.	            return Json(true);  
44.	        }  
46.	    }  
47.	}  
Most people are familiar with HTTP GET and HTTP POST operations. You perform. an HTTP GET operation whenever you request a page from a website by typing the address of the page in your web browser. You perform. an HTTP POST operation when you submit an HTML form. that has a method=”post” attribute.
Most people don’t realize that the HTTP protocol supports a number of additional types of HTTP operations:
• OPTIONS – Returns information about the communication options available.
• GET – Returns whatever information is identified by the request.
• HEAD – Performs the same operation as GET without returning the message body.
• POST – Posts new information or updates existing information.
• PUT – Posts new information or updates existing information.
• DELETE – Deletes information.
• TRACE – Performs a message loop back.
• CONNECT – Used for SSL tunneling.
*** Begin Note **
The HTTP operations are defined as part of the HTTP 1.1 standard which you can read about at
*** End Note ***
You can perform. these additional HTTP operations when performing Ajax requests. The controller in Listing 10 includes a Delete() action that can be invoked only with an HTTP DELETE operation. The view in Listing 11 includes a delete link that uses Ajax to perform. an HTTP DELETE operation.
大多数人对HTTP GET 和 HTTP POST操作都比较熟悉,当您在浏览器的地址栏内输入某个Web页面的地址,向某个网站请求该页面时,您就是在执行HTTP GET操作;当您提交一个HTML表单(属性method=”post”)时,您就是在执行HTTP POST操作。
• OPTIONS – 返回可用通讯选项的相关信息.
• GET – 返回请求指定的任何信息.
• HEAD – 与GET操作执行相同的操作,但不返回消息体.
• POST – 提交新的信息或更新现有信息.
• PUT –提交新的信息或更新现有信息.
• DELETE – 删除信息.
• TRACE – 执行一个消息回路.
• CONNECT – SSL隧道使用.
【注释】HTTP操作是HTTP 1.1标准的一部分, 有更多相关信息。
Listing 11 – Views\Employee\Delete.aspx [C#]
1. <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  


10.    <%= Ajax.ActionLink  
11.        (  
12.            "Delete",   // link text   
13.            "Delete",   // action name  
14.            new {id=39}, // route values  
15.            new AjaxOptions {HttpMethod="DELETE", Confirm="Delete Employee?"}  
16.        ) %>  

In Listing 11, the Ajax.ActionLink() helper renders a link that performs an HTTP DELETE operation. The link deletes the employee with Id 39. You can verify that the link performs an HTTP DELETE operation in Firebug (see Figure 5).
Listing 11中,Ajax.ActionLink()在页面上显示一个链接,点击该链接会执行HTTP DELETE操作。该链接删除id=39的员工,通过观察Firebug,您可以对此加以验证(见图5)。

Figure 5 – Performing an HTTP DELETE operation

*** Begin Note ***
Firebug is an essential tool for debugging Ajax applications. Firebug is a Mozilla Firefox extension that you can download from
*** End Note ***
【注释】Firebug是调试Ajax应用必不可少的工具,Firebug是Mozilla Firefox浏览器的一个扩展插件,可以从http://getFirebug.com下载。

Using ActionName
The ActionName attribute enables you to expose an action with a different name than its method name. There are two situations in which the ActionName attribute is useful.
First, when a controller has overloaded methods, you can use the ActionName attribute to distinguish the two methods. In other words, you can use the ActionName attribute to expose two methods with the same name as actions with different names.
For example, imagine that you have created a Product controller that has two overloaded methods named Details(). The first Details() method accepts an id parameter and the second Details() method does not. In that case, you can use the ActionName attribute to distinguish the two Details() methods by exposing the two Details() methods with different action names.
Second, using the ActionName attribute is useful when a controller has methods with different names and you want to expose these methods as actions with the same name. For example, the controller in Listing 12 exposes two actions named Edit() that accept the same parameter.
第二,当控制器包含不同的方法名称,而您希望让它们拥有相同的行为名称时,ActionName属性就很有用。例如,Listing 12中的控制器有两个接受相同参数的Edit()行为。
Listing 12 – Controllers\MerchandiseController.cs [C#]
1. using System.Web.Mvc;  
2. using MvcApplication1.Models;  
4. namespace MvcApplication1.Controllers  
5. {  
6.    public class MerchandiseController : Controller  
7.    {  
8.        private MerchandiseRepository _repository = new MerchandiseRepository();  
10.        // GET: /Merchandise/Edit  
11.        [ActionName("Edit")]  
12.        [AcceptVerbs(HttpVerbs.Get)]  
13.        public ActionResult Edit_GET(Merchandise merchandiseToEdit)  
14.        {  
15.            return View(merchandiseToEdit);  
16.        }  
18.        // POST: /Merchandise/Edit  
19.        [ActionName("Edit")]  
20.        [AcceptVerbs(HttpVerbs.Post)]  
21.        public ActionResult Edit_POST(Merchandise merchandiseToEdit)  
22.        {  
23.            try  
24.            {  
25.                _repository.Edit(merchandiseToEdit);  
26.                return RedirectToAction("Edit");  
27.            }  
28.            catch  
29.            {  
30.                return View();  
31.            }  
32.        }  
33.    }  
34. }  

You can’t have two methods with the same name and the same parameters in the same class. However, you can have two actions that have the same name and the same parameters.
The two Edit() actions in Listing 12 are distinguished by the AcceptVerbs attribute. The first Edit() action can be invoked only by an HTTP GET operation and the second Edit() action can be invoked only by an HTTP POST operation. The ActionName attribute enables you to expose these two actions with the same name.
Listing 12中的两个Edit()行为由AcceptVerbs属性进行区分,第一个Edit()行为仅能通过HTTP GET操作来调用,第二个仅能通过HTTP POST操作来调用,ActionName属性是这两个方法具有相同的行为名称。

Using ActionMethodSelector
You can build your own attributes that you can apply to controller actions to control when the controller actions are invoked. You build your own attributes by deriving a new attribute from the abstract ActionMethodSelectorAttribute class.
This is an extremely simple class. It has a single method that you must implement named IsValidForRequest(). If this method returns false, then the action method won’t be invoked.
You can use any criteria that you want when implementing the IsValidForRequest() method including the time of day, a random number generator, or the current temperature outside. The AjaxMethod attribute in Listing 13 is a more practical sample of how you can use the ActionMethod attribute. This attribute prevents a method from being called in cases in which the request is not an Ajax request.
当实现IsValidForRequest()方法时您可以使用各种条件,包括当前时间、随机数发生器、或当前温度等。Listing 13中的AjaxMethod属性是应用ActionMethod属性的一个更贴切实际的例子,该属性只允许Ajax请求来调用一个方法。
Listing 13 – Selectors\AjaxMethodAttribute.cs [C#]
1. using System.Reflection;  
2. using System.Web.Mvc;  
4. namespace MvcApplication1.Selectors  
5. {  
6.    public class AjaxMethod : ActionMethodSelectorAttribute  
7.    {  
8.        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)  
9.        {  
10.            return controllerContext.HttpContext.Request.IsAjaxRequest();  
11.        }  
12.    }  
13. }  

The selector in Listing 13 simply returns the value of the IsAjaxRequest() method as its selection criterion.
The controller in Listing 14 illustrates how you can use the AjaxMethod attribute.
Listing 13的选择器仅仅简单地将IsAjaxRequest()方法的值作为判断标准加以返回。
Listing 14中的控制器向您展示这个AjaxMethod属性如何运用。
Listing 14 – Controllers\NewsController.cs [C#]
1. using System;  
2. using System.Collections.Generic;  
3. using System.Web.Mvc;  
4. using MvcApplication1.Selectors;  
6. namespace MvcApplication1.Controllers  
7. {  
8.    public class NewsController : Controller  
9.    {  
10.        private readonly List _news = new List();  
11.        private Random _rnd = new Random();  
13.        public NewsController()  
14.        {  
15.            _news.Add("Moon explodes!");  
16.            _news.Add("Stock market up 200 percent!");  
17.            _news.Add("Talking robot created!");  
18.        }  
20.        public ActionResult Index()  
21.        {  
22.            var selectedIndex = _rnd.Next(_news.Count);  
23.            ViewData.Model = _news[selectedIndex];  
24.            return View();  
25.        }  
28.        [AjaxMethod]  
29.        [ActionName("Index")]  
30.        public string Index_AJAX()  
31.        {  
32.            var selectedIndex = _rnd.Next(_news.Count);  
33.            return _news[selectedIndex];  
34.        }  
37.    }  
38. }  
The controller in Listing 14 exposes two actions named Index(). The first Index() action is intended to be invoked by a normal browser request. The second action is intended to be invoked by an Ajax request.
The AjaxMethod attribute is applied to the second Index() action. If this action were not decorated with the AjaxMethod attribute then you would get an Ambiguous Match Exception because the ASP.NET MVC framework would not be able to decide which of the two actions to execute (see Figure 6).
Listing 14中的控制器包含两个Index()行为,第一个Index()行为是由通常的浏览器请求来调用,第二个由Ajax请求调用。
AjaxMethod属性应用于第二个Index()行为,如果该行为没有用AjaxMethod属性修饰,那么您将得到“模糊匹配的异常”,因为ASP.NET MVC框架无法确定去执行哪一个行为(见图6)。

Figure 6 – An Ambiguous Match Exception


The view in Listing 15 uses the Ajax.ActionLink() helper method to render a Get News link for displaying the news. If you are using an uplevel browser – a browser that supports basic JavaScript. – then clicking the link performs an Ajax request against the server. The Index() method decorated with the AjaxMethod attribute is invoked and the page is updated without performing a postback.
If, on the other hand, you are using a downlevel browser – a browser that does not support basic JavaScript. – then clicking the Get News link performs a normal postback. The page still gets updated with a news item, but the user must undergo the awful experience of a postback (see Figure 7).
Listing 15中的视图使用Ajax.ActionLink()方法在浏览器上绘制一个“Get News”链接,点击链接将显示新闻。如果您使用uplevel浏览器(支持基本的JavaScript的浏览器),则点击该链接将向服务器发送一个Ajax请求,由AjaxMethod 属性修饰的Index()方法被调用,该页面将会更新(但没有执行postback)。
另一方面,如果您使用的是downlevel浏览器(不支持基本的JavaScript的浏览器),那么点击“Get News”链接将执行标准的postback。页面仍然会更新显示新闻,但是用户将会体验到postback的糟糕感受(见图7)。

Figure 7 – Displaying the news


Listing 15 – Views\News\Index.aspx [C#]
1. <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
8.    <%=Ajax.ActionLink("Get News", "Index", new AjaxOptions {UpdateTargetId = "news"})%>  

Handling Unknown Actions
A controller has a special method named HandleUnknownAction(). This method is called automatically when a controller cannot find an action that matches a browser request. For example, if you request the URL /Product/DoSomethingCrazy and the Product controller does not have an action named DoSomethingCrazy() then the Product controller HandleUnknownAction() method is invoked.
By default, this method throws a 404 Resource Not Found HTTP exception. However, you can override this method and do anything you want. For example, the controller in Listing 16 displays a custom error message.
控制器有一个特殊的方法:HandleUnknownAction(),当控制器无法找到与浏览器请求相匹配的行为时,该方法将被自动调用。例如,如果您请求的URL 是/Product/DoSomethingCrazy而Product控制器没有名为DoSomethingCrazy()的行为,那么Product控制器的HandleUnknownAction()方法将被调用。
默认情况下,该方法抛出一个404 Resource Not Found HTTP异常。然而,您可以重载此方法,做您希望的任何事。例如,Listing 16中的控制器显示一个自定义错误消息。
Listing 16 – Controllers\CatalogController.cs [C#]
1. using System.Web.Mvc;  
3. namespace MvcApplication1.Controllers  
4. {  
5.    public class CatalogController : Controller  
6.    {  
8.        public ActionResult Create()  
9.        {  
10.            return View();  
11.        }  
13.        public ActionResult Delete(int id)  
14.        {  
15.            return View();  
16.        }  
19.        protected override void HandleUnknownAction(string actionName)  
20.        {  
21.            ViewData["actionName"] = actionName;  
22.            View("Unknown").ExecuteResult(this.ControllerContext);  
23.        }  
26.    }  
27. }  

If you request the URL /Catalog/Create or /Catalog/Delete then the Catalog controller will return the Create or Delete view. If you request a URL that contains an unknown action such as /Catalog/Wow or /Catalog/Eeeks then the HandleUnknownAction() method executes.
In Listing 16, the HandleUnknownAction() method adds the name of the action to view data and then renders a view named Unknown (see Figure 8).

Figure 8 – Displaying the Unknown view


Testing Controllers and Actions
The ASP.NET MVC team worked hard to make sure that controller actions were extremely easy to test. If you want to test a controller action then you simply need to instantiate the controller and call the action method.
For example, the controller in Listing 17 exposes two actions named Index() and Details(). If you invoke the Details() action without passing a value for the id parameter then you should be redirected to the Index() action.
ASP.NET MVC开发团队付为使得控制器行为的测试极为方便,付出了艰苦努力。如果您希望测试一个控制器行为,只需简单地构造这个控制器的实例并调用该行为。
例如,Listing 17中的控制器包括两个行为:Index()和Details()。如果您调用Details()行为时不传递id参数值,那么将会被重新定向到Index()行为。
Listing 17 – Controllers\PersonController.cs [C#]
1. using System.Web.Mvc;  
3. namespace MvcApplication1.Controllers  
4. {  
5.    public class PersonController : Controller  
6.    {  
7.        public ActionResult Index()  
8.        {  
9.            return View("Index");  
10.        }  
12.        public ActionResult Details(int? id)  
13.        {  
14.            if (!id.HasValue)  
15.                return RedirectToAction("Index");  
16.            return View("Details");  
17.        }  
19.    }  
20. }  

*** Begin Warning ***
When returning a view, you must be explicit about the view name or you won’t be able to verify the name of the view in a unit test. For example, in Listing 17, the Index() method returns View(“Index”) and not View().
*** End Warning ***
The unit tests in Listing 18 illustrate how you can test the actions exposed by the Person controller. The first unit test, named DetailsWithId(), verifies that calling the Details() method with a value for the id parameter returns the Details view.
The second unit test, named DetailsWithoutId(), verifies that calling the Details() method with no value for the idparameter causes a RedirectToRouteResult to be returned.
【注意】当返回一个视图时,您必须显式指定返回的视图名称,否则在单元测试中将无法验证视图的名称。例如,在Listing 17中,Index()方法返回的是View(“Index”)而不是View()。
Listing 18中的单元测试向您展示如何测试Person控制器中的行为。第一个DetailsWithId()单元测试,验证调用Details()方法时传递id参数值将返回Details视图。
Listing 18 – Controllers\PersonControllerTest.cs [C#]
1. using System.Web.Mvc;  
2. using Microsoft.VisualStudio.TestTools.UnitTesting;  
3. using MvcApplication1.Controllers;  
5. namespace MvcApplication1.Tests.Controllers  
6. {  
7.    [TestClass]  
8.    public class PersonControllerTest  
9.    {  
10.        [TestMethod]  
11.        public void DetailsWithId()  
12.        {  
13.            // Arrange  
14.            var controller = new PersonController();  
16.            // Act  
17.            var result = (ViewResult)controller.Details(33);  
19.            // Assert  
20.            Assert.AreEqual("Details", result.ViewName);  
21.        }  
24.        [TestMethod]  
25.        public void DetailsWithoutId()  
26.        {  
27.            // Arrange  
28.            var controller = new PersonController();  
30.            // Act  
31.            var result = (RedirectToRouteResult)controller.Details(null);  
33.            // Assert  
34.            Assert.AreEqual("Index", result.RouteValues["action"]);  
35.        }  
36.    }  
37. }  

*** Begin Note ***
To learn more about creating and running unit tests, see Appendix B of this book.
*** End Note ***

This chapter was devoted to the topic of ASP.NET MVC controllers. The goal of this chapter was to provide an in-depth explanation of how you can create controllers and controller actions.
In the first part of this chapter, you were provided with an overview of the different types of ActionResults that can be returned from a controller action. You learned how to returns views, redirect users to other actions, return JSON, and return downloadable files.
Next, we examined the different attributes that you can apply to a controller action to control when the controller action is invoked. You learned how to use the AcceptVerbs and ActionName attributes. You also learned how to create a custom ActionSelect attribute that enables you to execute an action only within the context of an Ajax request.
Finally, you learned how to build unit tests for your controllers. You learned how to test whether a controller returns different ActionResults such as a ViewResult or a RedirectToRouteResult.
本章完整地讨论了ASP.NET MVC控制器方面的主题,本章目标是向您提供详尽的关于如何创建控制器和控制器行为的知识。
最后,您学会了如何为控制器创建单元测试。您学会了如何测试控制器返回不同的ActionResult,如ViewResult 或RedirectToRouteResult。

来自 “ ITPUB博客 ” ,链接:,如需转载,请注明出处,否则将追究法律责任。



  • 博文量
  • 访问量