[ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)


我們知道,在 MVC 中每個請求都會提交到 Controller 進行處理。Controller 是和請求密切相關的,它包含了對請求的邏輯處理,能對 Model 進行操作並選擇 View 呈現給用戶,對於業務和數據的邏輯代碼以及接口和輔助類庫等一般都不放到 Controller 中。

Controller 和 Action 的內容較多,我把它分成了兩篇,也可能會分成三篇。本篇介紹 Controller 的實現、Controller 對狀態數據的獲取、ActionResult 和 Action 的數據傳遞,后續將介紹 Controller 工廠、Action Invoker 和暫時還沒想好或正在學習的一些較高級的特性。

文目錄

繼承 IController 接口

在本系列前面的文章中,我們添加的 Controller 都是一個繼承自抽象類 System.Web.Mvc.Controller 的普通類(請注意:controller(或Controller) 和 Controller 類在本文是兩個意思,請在閱讀本文時根據上下文理解)。Controller 抽象類封裝了很多很實用的功能,讓開發人員不用自己去寫那些重復煩瑣的處理代碼。

如果不使用封裝的 Controller 抽象類,我們也可以通過實現 IController 接口來創建自己的 controller。IController 接口中只有一個 Exctute 方法:

public interface IController { 
    void Execute(RequestContext requestContext); 
}

IController 接口在 System.Web.Mvc 命名空間下,一個結構非常簡單的接口。

當請求送到一個實現了 IController 接口的 controller 類時(路由系統通過請求的URL能夠找到controller),Execute 方法被調用。

下面我們創建一個空的 MVC 應用程序,在 Controllers 文件夾下添加一個實現了 IController 的類,並做一些簡單的操作,如下:

using System.Web.Mvc;
using System.Web.Routing;

namespace MvcApplication1.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));
        }
    }
}

運行應用程序,URL 定位到 /Basic/Index(你可以把 Index 改成其他任意片段名稱),結果如下:

實現了 IController 的類,MVC就會辨識它為一個 controller 類,根據 controller 名把對應的請求交給這個類處理。

但對於一個稍稍復雜的應用程序,自己實現 IController 接口是要做很多工作的,我們很少會這么做。通過這我們更好地理解了 controller 的運行,controller 中一切對請求的處理都是從 Execute 方法開始的。

繼承 Controller 抽象類

MVC 允許我們自由地進行自定義和擴展,比如像上面講的你可以實現 IController 接口來創建對各類請求的各種處理並生成結果。不喜歡 Action 方法或不關心 View,那么你可以自己動手寫一個更好更快更優雅的 controller 來處理請求。但像前面說的,自己實現 IController 接口要做很多工作,最重要的是沒有經過長期實踐測試,代碼的健壯性得不到保證, 一般不建議你這么做。MVC 框架的 System.Web.Mvc.Controller 類,提供了足夠實用的特性來方便我們對請求的處理和返回結果。

繼承 Controller 類的 controller 我們已經使用過很多次了,對它已經有一定的了解,它提供了如下幾個關鍵的特性:

  • Action方法:一個 Controller,它的行為被分為多個方法,通常一個方法對應着一個請求,並且可以通過方法參數來取得請求傳遞過來的數據。
  • ActionResult:可以返回一個描述了 Action 方法執行結果的對象,這樣的好處是想返回什么結果就指定對應的返回對象就行,不用關心怎么去執行並生成結果。
  • Filters:通過C#特性,對某一種行為的處理(比如授權和驗證)進行封裝,方便了在多個 Controller 和 Action 方法之間進行重用。

所以,如果你不是因為特殊的需求或閑得蛋疼,創建一個滿足要求的 Controller 最好的途徑是繼承 Controller 抽象類。由於之前我們已經使用過多次,這里就不再進行具體的演示了,但我們還是來看一下它的代碼結構。

在 Controllers 文件夾下添加一個 Controller,VS 已經把類的結構幫我們生成好了,如果你喜歡整潔,會習慣性地把不需要用到的引用刪掉,代碼如下:

using System.Web.Mvc;

namespace MvcApplication1.Controllers {
    
    public class DerivedController : Controller {
        
        public ActionResult Index() {
            ViewBag.Message = "Hello from the DerivedController Index method";
            return View("MyView");
        }
    }
}

我們可以查看 Controller 抽象類的定義,發現它是繼承 ControllerBase 類的,在 ControllerBase 類中實現了 IController 接口的 Execute 方法,這個方法是MVC對請求進行處理的各個組件的入口,其中包括通過路由系統找到 Action 方法並調用。

Controller 類內部使用 Razor 視圖系統來呈現 View,這里通過 View 方法,指定 View 的名稱參數來告訴 MVC 選擇 MyView 視圖來返回給用戶結果。

在 Controller 中獲取狀態數據

我們經常需要訪問客戶端提交過來的數據,比如 QueryString 值、表單值和通過路由系統來自 URL 的參數值,這些值都可稱為狀態數據。下面是 Controller 中獲取狀態數據的三個主要來源:

  • 一系列的上下文對象。
  • 傳遞給 Action 方法的參數。
  • 顯式的調用框架的模型綁定(Model Binding)特性。

從上下文對象中獲取狀態數據

獲取狀態數據最直接的方法就是從上下文對象中提取。當你創建了一個繼承自 Controller 類的 Controller 時,可以通過一系列的屬性可以方便的訪問到和請求相關的數據,這些屬性包括 Request、Response、RouteData、HttpContext 和 Server,每一個都提供了請求相關的不同類型的信息。下面列出了最常的上下文對象:

在 Action 方法中可以使用任意上下文對象來獲取請求相關的信息,如下面在 Action 方法中所演示的:

...
public ActionResult RenameProduct() {
    //訪問不同的上下文對象
    string userName = User.Identity.Name;
    string serverName = Server.MachineName;
    string clientIP = Request.UserHostAddress;
    DateTime dateStamp = HttpContext.Timestamp;
    AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product");
            
    //從POST請求提交的表單中獲取數據
    string oldProductName = Request.Form["OldName"];
    string newProductName = Request.Form["NewName"];
    bool result = AttemptProductRename(oldProductName, newProductName);

    ViewData["RenameResult"] = result;
    return View("ProductRenamed");
}
...

這些上下對象不用特意去記,用的時候,你可以通過VS的智能提示來了解這些上下文對象。 

使用 Action 方法參數獲取狀態數據

在本系列的前面的文章中,我們已經知識如何通過 Action 參數來接收數據,這種方法和上面的從上下文對象中獲取相比,它更為簡潔明了。比如,我們有下面這樣一個使用上下文對象的 Action 方法:

public ActionResult ShowWeatherForecast() {
    string city = (string)RouteData.Values["city"];
    DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
    // do something ... 
    return View();
}

我們可以像下面這樣使用 Action 方法參數來重寫它:

public ActionResult ShowWeatherForecast(string city, DateTime forDate) { 
    // do something ... 
    return View(); 
}

它不僅易讀性強,也方便進行單元測試。

Action 方法的參數不允許使用 ref 和 out 參數,這是沒有意義的。

MVC 框架通過檢查上下文對象來為 Action 方法的參數提供值,它的名稱是不區分大小寫的,比如 Action 方法的 city 參數的值可以是通過 Request.Form["City"] 來獲取的。

理解 Action 方法的參數是如何被賦值的

Controller 類通過 MVC 框架的 value provider 和 model binder 組件來為 Action 方法獲取參數的值。

value provider 提供了一系列Controller中可以訪問到的值,在內部它通過從 Request.Form、Request.QueryString、Request.Files 和 RouteData.Values 等上下文對象中提取數據(鍵值集合),然后把數據傳遞給 model binder,model binder 試圖將這些數據與Action方法的參數進行匹配。默認的 model binder 可以創建和賦值給任何.NET類型對象參數(即 Action 方法的參數),包括集合和自定義的類型。

在這不對 model binder 進行介紹,我將在本系列的后續博文中對其進行專門的介紹。

理解 ActionResult

ActionResult 是描述 Action 方法執行結果的對象,它的好處是想返回什么結果就指定對應的返回對象就行,不用關心如何使用Response對象來組織和生成結果。ActionResult 是一個命令模式的例子,這種模式通過存儲和傳遞對象來描述操作。

當 MVC 框架從 Action 方法中接收到一個 ActionResult 對象,它調用這個對象的 ExecuteResult 方法,其內部是通過 Response 對象來返回我們想要的輸出結果。

為了更好的理解,我們通過繼承 ActionResult 類來自定義一個 ActionResult。在MVC工程中添加一個Infrastructure文件夾,在里面創建一個名為 CustomRedirectResult 的類文件,代碼如下:

using System.Web.Mvc;

namespace MvcApplication1.Infrastructure {
    
    public class CustomRedirectResult : ActionResult {
        
        public string Url { get; set; }

        public override void ExecuteResult(ControllerContext context) {
            string fullUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
            context.HttpContext.Response.Redirect(fullUrl);
        }
    }
}

當我們創建一個 CustomRedirectResult 類的實例時,我們可以傳遞想要跳轉的 URL。當 Action 方法執行結束時,MVC 框架調用 ExecuteResult 方法,ExecuteResult 方法通過 ControllerContext 對象獲得 Response 對象,然后調用 Redirect 方法。

下面我們在 Controller 中使用自定義的 CustomRedirectResult:

public class DerivedController : Controller {
    ...
    public ActionResult ProduceOutput() {
        if (Server.MachineName == "WL-PC") {
            return new CustomRedirectResult { Url = "/Basic/Index" };
        }
        else {
            Response.Write("Controller: Derived, Action: ProduceOutput");
            return null;
        }
    }
}

運行后我們看到如下結果:

當運行在本機(WL-PC)時直接重定向到了指定的/Basic/Index。

上面我們通過自定義 CustomRedirectResult 來實現重定向,我們可以用 MVC 框架提供的方法,如下:

... 
public ActionResult ProduceOutput() { 
    return new RedirectResult("/Basic/Index"); 
} 

為了使用方便,Controller 類中為大部分類型的 ActionResult 提供簡便的方法,如上面的可像下面這樣簡寫:

... 
public ActionResult ProduceOutput() { 
    return Redirect("/Basic/Index"); 
}

 MVC框架包含了許多 ActionResult 類型,這些類型都繼承自 ActionResult 類,大部分在 Controller 類中都有簡便的方法,下面列舉了一些:

除了該表列出來的,還有ContentResultFileResultJsonResult JavaScriptResult。具體每種ActionResult類型的用法這里就不講了,大家可以看看蔣老師的了解ASP.NET MVC幾種ActionResult的本質系列的文章。

幾種從 Action 傳遞數據到 View 的方式

我們經常需要在 Action 方法中傳遞數據到一個 View 中,MVC 框架為此提供了一些很方便的操作。下面簡單簡介幾種常用的方式。

View Model 對象

通過 View Model 對象傳遞數據給View,這是最常用的一種,在 Acton 方法執行結束時通過 View 方法傳遞 View Model 對象給 View,如下代碼所示:

... 
public ViewResult Index() { 
    DateTime date = DateTime.Now; 
    return View(date); 
} 

在 View 中我們通過 Model 屬性來使用傳遞過來的 View Model 對象,如下:

@model DateTime 

@{ 
    ViewBag.Title = "Index"; 
}

<h2>Index</h2> 
The day is: @Model.DayOfWeek

在 Razor 視圖引擎中,@model 的作用是聲明 odel 屬性的類型,省去了類型轉換的麻煩,而 @Model 是V iew Model 對象的引用。

ViewBag、ViewData 和 TempData 屬性

ViewBag、ViewData 和 TempData 都是 Controller 和 View 中能訪問到的屬性,都是用來存儲小量的數據,他們的區別如下:

  • ViewBag,是一個動態(dynamic)的弱類型,在程序運行的時候解析,是 MVC3 中新增的特性,只在當前View有效。
  • ViewData,是一個字典集合,也是只在當前View有效,性能比 ViewBag 高,但是使用的時候需要類型轉換。
  • TempData,也是字典集合,一般用於兩個請求之間臨時緩存內容或頁面間傳遞消息,保存在 Session 中,使用完以后則從 Session 中被清除。

下面是三者使用的例子,先在 Controller 中分別用三者存儲小數據:

public class DerivedController : Controller {

    public ActionResult Index() {
        ViewBag.DayOfWeek = DateTime.Now.DayOfWeek;
        ViewData["DayOfMonth"] = DateTime.Now.Day;
        return View();
    }

    public ActionResult ProduceOutput() {
        TempData["Message"] = "Warning message from Derived Controller.";
        return Redirect("/Home/Index");
    }
}

在 Views/Derived 目錄下的 Index.cshtml 中,取出 ViewBag 和 ViewData 中的存儲的數據:

...
Day of week from ViewBag: @ViewBag.DayOfWeek
<p /> 
Day of month from ViewData: @ViewData["DayOfMonth"] 

在 Views/Home 目錄下的 Index.cshtml 中,取 TempData 中的數據如下:

...
@TempData["Message"]

當請求 /Derived/ProduceOutput 時,ProduceOutput 方法將一條消息存到 TempData 中,並跳轉到 /Home/Index。

下面是分別是將URL定位到 /Derived/Index 和 /Derived/ProduceOutput 時的結果:

  

一般在當前 View 中使用 ViewBag 或 ViewData,在兩個請求之間傳遞臨時數據用 TempData。由於 TempData 被使用后即被釋放,所以如果要二次使用 TempData 中的數據就需要將其存到其他變量中。

 


參考:Pro ASP.NET MVC 4 4th Edition

 


免責聲明!

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



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