《Pro ASP.NET MVC 3 Framework》學習筆記之二十三【Controllers和Actions】


生成輸出(Producing Output)

在controller完成處理請求之后,通常需要生成一個響應。當我們通過直接實現IController接口創建一個簡單的controller時,我們需要對處理請求的每一個方面負責,包括創建對客戶端的響應。如果我們想發送一個HTML響應,那我們必須創建並且集合HTML數據,然后使用Response.Write方法將數據發送到客戶端。類似地,如果我們想重定向用戶到其他的URL,可以使用Response.Redirect方法。

下面是具體的實例:

View Code
using System.Web.Mvc;
using System.Web.Routing;

namespace ControllersAndActions.Controllers
{
public class BasicController : IController
{
public void Execute(RequestContext requestContext)
{
string controller = (string)requestContext.RouteData.Values["controller"];
string action = (string)requestContext.RouteData.Values["action"];
requestContext.HttpContext.Response.Write(string.Format("Controller:{0},Action:{1}", controller, action));
//..or..
requestContext.HttpContext.Response.Redirect("/Some/Other/Url");
}
}
}

 當然在DerivedController里面也可以使用Response屬性,但是這樣使用會帶來一些問題:

①controller類必須包含HTML或URL架構的詳細信息,這會讓維護和可讀性非常差
②對於這種直接響應輸出的controller進行單元測試也很困難
③處理好每一個這種方法響應的細節是非常乏味和易出錯的

幸運的是,MVC框架有一個解決這些問題非常好的功能——Action Results。下面會介紹Action Result的概念並展示它用來生成controller的響應的不同方式:

理解Action Results

MVC框架使用Action Results將"說明我們意圖(stating our intentions)"和"執行我們的意圖(executing our intentions)"分開(我也不是很明白這兩個概念,如果路過的朋友清楚,請留言指正,謝謝)

不直接使用Response對象,而是返回一個從ActionResult類派生的對象,用這個對象來描述我們想要controller響應什么,比如呈現一個視圖或重定向到另外的URL,再或者action方法等等。ps:MVC里面的Action Result系統采用了設計模式里面的命令模式

MVC框架接收從action方法返回的ActionResult對象,並調用定義在ActionResult類里面ExecuteResult方法,接着,對ActionResult(這是一個抽象類)的實現為我們處理Response對象並生成符合我們意圖的輸出。下面是一段關於RedirectResult類的源碼:

View Code
    public class RedirectResult : ActionResult {

[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public RedirectResult(string url)
: this(url, permanent: false) {
}

[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public RedirectResult(string url, bool permanent) {
if (String.IsNullOrEmpty(url)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url");
}

Permanent = permanent;
Url = url;
}

public bool Permanent {
get;
private set;
}

[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public string Url {
get;
private set;
}

public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
if (context.IsChildAction) {
throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
}

string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
context.Controller.TempData.Keep();

if (Permanent) {
context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);
}
else {
context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
}
}

}

當我們創建一個RedirectResult類的實例,我們傳入了一個想要跳轉的URL參數,還可以傳入一個可選的參數:是否永久重定向(默認值是false)。ExecuteResult將會在Action方法執行完時被MVC調用,通過ControllerContext對象獲取用於查詢的Response對象,然后調用RedirectPermanent或Redirect方法。

我們在DerivedController類里面添加一個方法如下:

View Code
    public class DerivedController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello from DerivedController Index Method";
return View("Index");
}

public ActionResult Redirect()
{
return new RedirectResult("/Derived/Index");
}
}

這時運行程序輸入/Derived/Redirect會跳轉到Index頁面。當然可以簡便的寫成return Redirect("/Derived/Redirect")。

此外,MVC框架內置了許多action result類型,都是從ActionResult類派生的,這樣能夠方便我們在action方法里面選擇具體的返回類型,比如我們要呈現到View,可以選擇ViewResult作為action方法的返回值。更多關於從ActionResult派生的類型請參考這里。下面會展示怎樣使用這些返回值類型以及怎樣創建和使用自定義的ActionResult。

通過呈現View返回HTML

最常見的來自action方法的響應就是生成HTML並發送給瀏覽器。當使用了ActionResult這套體系,為了生成HTML,我們要創建一個指定了要呈現視圖的ViewResult類的實例。如下所示:例子指定了Homepage的視圖

View Code
    public class ExampleController : Controller
{
public ViewResult Index()
{
return View("Homepage");
}
}

ps:我們可以顯示的創建ViewResult對象(return new ViewResult { ViewName = "Homepage" };)這是一種完美的,可接受的方式。但是我們偏向於使用Controller類提供的的幫助方法來簡化代碼。

當MVC框架調用ViewResult對象的ExecuteResult時,就會開始對我們指定的View進行搜索,如果我們使用了Area,則搜索的順序如下:

•  /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.aspx
•  /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.ascx
•  /Areas/<AreaName>/Views/Shared/<ViewName>.aspx
•  /Areas/<AreaName>/Views/Shared/<ViewName>.ascx
•  /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml
•  /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml
•  /Areas/<AreaName>/Views/Shared/<ViewName>.cshtml
•  /Areas/<AreaName>/Views/Shared/<ViewName>.vbhtml

只要有一個View找到,整個搜索就會停止,並將找的View呈現給客戶端。如果沒有用Area,則沒有前面的/Areas/<AreaName>.

ps:MVC按照這種順序搜索Viiew也體現了"約定勝於配置(convention over configuration)"的思想

下面可以進行如下單元測試:

View Code
[TestMethod] 
public void ViewSelectionTest()
{
// Arrange -創建一個Controller
ExampleController target = new ExampleController();

// Act - 調用Action方法
ActionResult result = target.Index();

// Assert -檢查結果
Assert.AreEqual("Homepage", result.ViewName);
}
//如果測試一個Action方法選擇的是默認的View,如下所示:
public ViewResult Index()
{
return View();
}
//這時可以做這樣的判斷
Assert.AreEqual("", result.ViewName);

當我們調用View方法不指定具體的視圖的名字時,會呈現默認的View,這個默認的View名就是該Action方法名,如public ViewResult Index(){return View();},默認的視圖就是Index。MVC就會按照約定的順序搜尋Index視圖,搜尋哪一個視圖是由RouteData.Values["action"]的值決定的。View方法很多重載,大家可以自己看看智能提示。

通過路徑來指定呈現的View

這種命名約定的方法非常方便和簡捷的,但是它限制了我們所能呈現的一些View。如果我們要呈現一個具體的view,可以提供一個顯示的路徑來繞開搜索的階段。下面是一個這樣的例子:

View Code
using System.Web.Mvc; 

namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index() {
return View("~/Views/Other/Index.cshtml");
}
}
}

當我們像這樣指定一個view時,指定的路徑必須以"/"或"~/"並且包含擴展名(如.cshtml)。當然我推薦這樣來使用,因為我們可以有其他的方法來達到同樣的效果。

從Action方法向View傳遞數據

我們常常需要從action方法向view傳遞數據,MVC框架提供了多種方式來實現。

1.提供一個View Model對象
我們能夠將一個對象作為View方法的參數傳遞給view,例如:

View Code
//Controller部分
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
DateTime date = DateTime.Now;
return View(date);
}
}
}
//View部分
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

The day is: @(((DateTime)Model).DayOfWeek)

上面的View是一個沒有類型或者說若類型的view。這個view不知道關於view model對象的任何信息,並且將它作為了object對象的實例進行處理。為了得到DayofWeek屬性的值,我們需要將object對象的實例強轉為DateTime.這樣做能夠實現我們效果,但是卻讓view變得凌亂。

我們可以通過創建強類型的view來改進,即在view里面指定view model對象的類型,只需要添加一句代碼:@model Datetime,如下所示:

@model DateTime 
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @Model.DayOfWeek

可以發現,指定view model的類型通過@model關鍵字(注意這里的"m"是小寫),當我們讀取view model對象屬性值時,使用@Model關鍵字(這里的"M"是大寫哦),使用強類型的視圖不僅讓我們的view變得整潔,而且方便我們編碼,因為會對屬性的智能提示。
下面進行單元測試:

View Code
[TestMethod] 
public void ViewSelectionTest()
{
// Arrange - create the controller
ExampleController target = new ExampleController();

// Act - call the action method
ActionResult result = target.Index();

// Assert - check the result
Assert.AreEqual("Hello, World", result.ViewData.Model);
}

使用ViewBag傳遞數據

在前面就已經使用過ViewBag,這個功能讓我們能夠隨意地定義一個動態對象的屬性,並能夠在view里面訪問,如下所示:

View Code
//Controller部分
public ViewResult Index() {
ViewBag.Message = "Hello";
ViewBag.Date = DateTime.Now;
return View();
}

//View部分
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

The day is: @ViewBag.Date.DayOfWeek
<p />
The message is: @ViewBag.Message

相對於使用view model對象,ViewBag能夠很容易地發送多個對象到view。如果我們被限制只能使用view models,那么為實現相同的效果,我們需要創建一個新的類型具有string和DateTime兩個類型的成員。使用動態對象,我們可以輸入視圖中調用的方法屬性序列

像這樣的:The day is: @ViewBag.Date.DayOfWeek.Blah.Blah。VS不會為動態對象提供任何智能提示包括ViewBag,所以如果有什么錯誤,只有等到view呈現才知道。當然我們完全可以同時使用view model對象和ViewBag,各盡其用。

使用ViewData傳遞數據

ViewData是在MVC3之前的版本中出現的,主要的功能類似於ViewBag,但ViewData是使用ViewDataDictionary類實現的而不是一個動態類型,ViewDataDictionary類像一個常規的鍵/值對的集合,並通過Controller類的ViewData屬性訪問。如下所示:

View Code
//Controller部分
public ViewResult Index()
{

ViewData["Message"] = "Hello";
ViewData["Date"] = DateTime.Now;

return View();
}

//View部分
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

The day is: @(((DateTime)ViewData["Date"]).DayOfWeek)
<p />
The message is: @ViewData["Message"]

ps:我們能看到需要對object對象進行類型轉換,現在有了ViewBag以后,推薦使用ViewBag,但是盡量使用強類型view和view models是非常明智的。

執行重定向(Performing Redirections)

一個來自action方法常見的結果不是直接生成任何輸出,而是重定向用戶到其他的URL。大多數時候,這個重定向的URL是應用程序里面另外一個action方法,用來生成我們想給用戶看到的輸出。

POST/REDIRECT/GET模式:最頻繁使用的重定向是在處理POST請求的action方法里面,正如我們前面提到的,POST請求是要改變應用程序狀態的,如果我們處理一個請求后接着僅僅是返回HTML,我們就冒了用戶第二次點擊重載按鈕和重提交按鈕所導致的不可預期的異常的風險。為了避免這個問題,我們可以遵循POST/Redirect/GET模式。在這個模式中,接收一個請求,處理它並重定向到瀏覽器以至於瀏覽器為其他的URL制造一個GET請求。GET請求不應該修改我們應用程序的狀態,所以任何無意的關於請求的重提交不會引起任何問題。

當我們執行一個重定向,我們就發送了兩個HTTP編碼中的一個到瀏覽器:

①發送HTTP 302狀態編碼,代表暫時重定向。這是最頻繁使用的重定向類型,MVC3里面也是這樣,當我們遵循Post/Redirect/Get模式時,這時發送的代碼就是我們想要的。
②發生HTTP 301狀態編碼,表示永久重定向。這要慎重使用,因為它指示HTTP編碼的接收者不會再次請求原始的URL並使用包含了重定向編碼的新URL。如果你有疑慮,那么最好使用暫時重定向,發送302編碼。

不早了,今天的筆記就到這里!筆記里面不對的地方還請路過的朋友指正,謝謝!
晚安


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM