天天看點

精通MVC3摘譯(8)-處理輸出(2)

使用ViewBag傳遞資料

View Bag允許你在一個dynamic對象上定義任何屬性,并且在view中通路它。這個dynamic對象可以通過Controller.ViewBag屬性通路它。如下示範:

public ViewResult Index() {

ViewBag.Message = "Hello"; ViewBag.Date = DateTime.Now;

return View();

}

上例中我們定義了名為Message和Date的屬性,并且指派。在這之前,這個屬性并不存在,我們并沒有建立它們。要在View中讀取它們,我們隻需讀取相同的屬性就可以了。如下代碼:

@{

ViewBag.Title = "Index";

<h2>Index</h2>

The day is:

@ViewBag.Date.DayOfWeek

<p />

The message is:

@ViewBag.Message

使用view model對象時,ViewBag有一個優勢,他可以友善的傳送多個對象個view。如果我們隻限制使用view model,那麼我們需要建立一個新的類型,包含string和DateTime成員,以此實作上例的功能。當處理dynamic對象的時候,我們可以任意次序的輸入在view中的方法和屬性,就像這樣:

The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah

Visual Studio對dynamic對象不支援智能提示,包括ViewBag,在view被呈現之前,Error也不會顯示。

我們喜歡ViewBag的靈活性,但是我們趨向于堅持使用強類型view。在同一個view中,沒有什麼理由不能同時使用view model和ViewBag,它們互相之間沒有沖突。

使用View Data傳遞資料

View Bag功能在MVC3中才加入,在此之前,使用view model對主要的方法是使用view data.View Data和View Bag類似,但是它是通過ViewDataDictionary實作的,而不是dynamic對象。ViewDataDictionary類就是一個鍵值對集合,可以通過 Controller類的ViewData 屬性通路。如下:

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

在view中讀出值,代碼如下:

Listing 12-20. Reading View Data in a View

@(((DateTime)ViewData["Date"]).DayOfWeek) The message is: @ViewData["Message"]

可以看到,我們必須從view data中轉型。有了View Bag之後,很少會使用View Data,但是我們還是喜歡強類型的view和view model。

執行跳轉

action方法的傳回值不一定都是直接生成輸出,可能是重定向到另一個URL。多數情況下,跳轉的URL是另一個輸出結果的action方法。

POST/REDIRECT/GET 模式

action方法中最常用的跳轉是處理HTTP POST請求。之前提起過,POST請求用來改變應用程式狀态的,如果使用這種方式隻是要傳回HTML,那麼你就得冒險,如果使用者點選浏覽器的重新整理按鈕或者重新送出第二次,可能會有異常的結果出現

要避免這種問題,你必須遵循Post/Redirect/Get模式。這種模式下,接收一個POST請求,處理後,重定向浏覽器,這樣的話由浏覽器提出的GET請求另一個URL。GET請求不應該修改應用程式狀态,是以,任何不小心的重複送出請求不會導緻問題發生。

當你執行一個重定向,你會在兩個HTTP Code中選擇一個發送給浏覽器:

發送HTTP code302,這是一個臨時重定向。這是最常見的重定向類型。在MVC 3之前,這是MVC Framework内建支援的唯一的方法。當使用 Post/Redirect/Get模式,你發送的就是這個code。

發送HTTP code 301,這個code訓示了永久重定向。這個應該小心使用,因為它訓示HTTP不在處理原始的URL,而使用根據定向代碼使用新的URL。如果你不确定的話,使用臨時重定向,發送code302.

重定向到文本URL

重定向到浏覽器,最常用的方法是調用Redirect 方法,該方法傳回RedirectResult 的執行個體。如下:

public RedirectResult Redirect() {

return Redirect("/Example/Index");

你想要重定向的URL作為一個string,傳遞給Redrect方法的參數。Redirect 方法發送臨時重定向指令。如果要發送永久重定向,使用RedirectPermanent 方法,如下代碼:

return RedirectPermanent("/Example/Index");

如果你喜歡,你可以使用Redirect 方法的重載版本,此版本可以傳遞一個布爾參數,訓示重定向是否是永久的。

重定向到路由系統URL

如果你将使用者重定向到應用程式的不同部分,你需要确定發送的URL是合法的。使用文本URL意味着對路由結構的任何改變都需要重新修改URL。

作為替代,你可以使用路由系統,通過RedirectToRoute方法生成一個有效的URL,該方法生成一個RedirectToRouteResult執行個體

public RedirectToRouteResult Redirect() {

return RedirectToRoute(new { controller = "Example", action = "Index", ID = "MyID" });

RedirectToRouteResult方法是一個臨時重定向,使用RedirectToRoutePermanent方法可以得到永久重定向,這個兩個方法都采用匿名類型作為參數。

重定向到Action方法

你可以使用RedirectToAction方法,更優雅的方式重定向到一個action方法。這個方法僅僅是對RedirectToRoute方法的一個包裝,讓你指定action方法和controller的值,不需要建立匿名類型。如下代碼:

return RedirectToAction("Index");

如果你指定一個action方法,然後假設你要調用的是目前controller的action方法。如果你要重定向到另一個controller,你需要提供這個controller的名字作為參數,如下:

return RedirectToAction("Index", "MyController");

還有其他的重載版本,你可以使用他們提供額外的值來生成URL,他們都采用匿名類型,雖然使用起來不那麽友善,但是仍然可以使你的代碼簡潔,

注意,在action方法和controller的值傳遞到路由系統之前,都是還沒驗證的。你必須确認你指定的目标是存在的。RedirectToAction執行臨時重定向,RedirectToActionPermanent是永久重定向。

重定向中保護資料

重定向導緻浏覽器送出一個全新的HTTP請求,這意味着你不能通路到原始請求的細節。如果你想要從一個請求傳遞資料到另一個,你需要使用Temp Data功能。TempData 類似于 Session data,除了TempData值可以在讀取時标記為删除,當請求處理完畢,他們可以被删除。對于僅在重定向期間暫存資料,這個功能非常理想。下面的是一個簡單的例子:

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

當這個方法處理一個請求的時候,會設定TempData集合中的值,然後重定向使用者的浏覽器到Index方法,然後将值傳送給view,如下:

ViewBag.Message = TempData["Message"]; ViewBag.Date = TempData["Date"];

在視圖中可以更直接的讀取這些值,比如:

@(((DateTime)TempData["Date"]).DayOfWeek) The message is: @TempData["Message"]

直接在view中讀取這些值,意味着你不需要在action方法中使用View Bag和View Data。但是,你必須轉型。你可以使用Peek方法,來從TempData中讀取值而不把它删除,如下:

DateTime time = (DateTime)TempData.Peek("Date");

你也可以通過使用Keep方法保護一個值不被删除,比如:TempData.Keep("Date");

Keep方法不會永久的保護一個值。如果這個值再次讀取,他會再次被删除。如果要儲存一個不自動删除的值,那麼就使用session吧。

傳回文本資料

除了HTML,還有好幾種基于文本的資料格式:

? XML, RSS 和Atom (XML子集)

? JSON (通常應用在AJAX上)

? CSV (導出表格資料)

? plain text

MVC Framework對JSON有專門的支援。對所有的這些資料類型,我們可以使用通用ContentResult action結果。下面提供了一個示範:

public ContentResult Index() {

string message = "This is plain text";

return Content(message, "text/plain", Encoding.Default);

通過Controller.Content方法,建立ContentResult,Controller.Content方法帶有3個參數:

第一個是你想發送的文本資料。

第二個是HTTP content-type header值。你可以線上查詢或者使用 System.Net.Mime.MediaTypeNames類獲得一個值,對于純文字,這個值就是text/plain。

最後一個參數指定編碼結構,用來轉換text為特定的位元組序列。

你可以忽略最後2個參數,framework會假設資料是HTML(content type 是text/html)。它會試着選擇一個浏覽器發起請求是時候的編碼格式。你可以隻是傳回文本,就像下面代碼:

return Content("This is plain text");

事實上,可以更進一步,如果你從action方法處傳回一個不是ActionResult的對象,MVC Framework會試着序列化這個資料為字元串,然後作為HTML發送給浏覽器。如下例子,從Action方法傳回一個非ActionResult對象。

public object Index() {

return "This is plain text";

結果如下圖:

精通MVC3摘譯(8)-處理輸出(2)
傳回XML資料

從action方法傳回XML資料很簡單,尤其是你使用LINQ to XML 和XDocument API 從對象中生成XML,如下提供了一個示範:

public ContentResult XMLData() {

StoryLink[] stories = GetAllStories();

XElement data = new XElement("StoryList", stories.Select(e =&gt; {

return new XElement("Story",

new XAttribute("title", e.Title),

new XAttribute("description", e.Description),

new XAttribute("link", e.Url));

}));

return Content(data.ToString(), "text/xml");

StoryLink類的定義如下:

public class StoryLink{

public string Title {get; set;}

public string Description { get; set; }

public string Url { get; set; }

傳回的 XML片段如下:

<StoryList>

<Story title="First example story" description="This is the first example story"

link="/Story/1" />

<Story title="Second example story" description="This is the second example story"

link="/Story/2" />

<Story title="Third example story" description="This is the third example story"

link="/Story/3" />

</StoryList>

傳回JSON資料

近來,在WEB應用程式中使用XML文檔和XML片段正在減少,而都傾向于使用Javascript Object Notation(JSON)。JSON是一個輕量級的,基于文本的格式,它描述層次型的資料結構。JSON資料是合法的Javascript代碼,這意味着它天生就能由主流的浏覽器支援,相比XML來說,它更緊湊和易用。JSON最常用于發送資料到用戶端,響應AJAX查詢。

MVC Framework内建了JsonResult類,它将.NET對象序列号為JSON格式,你可以使用Controller.Json方法建立JsonResult ,如下代碼:

[HttpPost]

public JsonResult JsonData() {

return Json(stories);

這個示例使用了和之前相同的StoryLink類,但是不需要操縱資料,因為序列化由JsonResult類負責。action 方法得到的響應如下:

[{"Title":"First example story",

"Description":"This is the first example story","Url":"/Story/1"},

{"Title":"Second example story",

"Description":"This is the second example story","Url":"/Story/2"},

{"Title":"Third example story",

"Description":"This is the third example story","Url":"/Story/3"}]

我們格式化了JSON資料,使之更易讀,如果不熟悉JSON也不必擔心,之後還會再說到。想了解JSON,可以通路http://www.json.org

出于安全原因,JsonResult對象僅對 HTTP POST請求生成響應。這防止資料通過跨站點請求暴露給第三方。我們喜歡用HttpPost标記生成JSON的action方法,作為對這種行為的提醒,盡管這不是必須的。

傳回檔案和二進制資料

FileResult是一個抽象基類。MVC Framework提供3個内建的具體的子類。

? FilePathResult 直接從伺服器檔案系統發送檔案。

? FileContentResult 發送記憶體中的位元組數組内容。

? FileStreamResult 發送打開的System.IO.Streamsends對象的内容。

不需要擔心選擇哪個類型使用,因為他們都是通過Controller.File方法的不同重載版本自動建立的。看下面的示範代碼:

示範如何從硬碟上發送一個檔案。

public FileResult AnnualReport() {

string filename = @"c:\AnnualReport.pdf";

string contentType = "application/pdf";

string downloadName = "AnnualReport2011.pdf";

return File(filename, contentType, downloadName);

這個action方法導緻浏覽器提示使用者儲存檔案,如下圖,不同的浏覽器處理檔案下載下傳有各自的方式,此圖顯示的是IE8的方式。

精通MVC3摘譯(8)-處理輸出(2)

我們使用的File方法的重載方法有3個參數:

File方法的參數說明,如下表:

精通MVC3摘譯(8)-處理輸出(2)

如果你忽略fileDownloadName同時浏覽器知道怎樣顯示MIME類型(比如,所有的浏覽器都知道如何顯示p_w_picpath/gif檔案),那麼浏覽器會顯示這個檔案。

如果你忽略fileDownloadName同時浏覽器不知道怎麼顯示MIME類型 (比如,你可能指定的是application/vnd.ms-excel)那麼浏覽器會彈出一個對話框提示我們儲存還是打開。基于目前的URL,猜測一個合适的檔案名(在IE中基于你指定的MIME類型)。然而,猜測的檔案名對使用者來說沒意義,可能它有個未關聯的檔案字尾名,比如.mvc或者根本就沒有擴充名。是以,如果你期望得到一個儲存或打開的對話框,你最好明确的指定fileDownloadName。

注意,如果你指定的fileDownloadName 不符合contentType參數(比如,你使用了MIME類型application/vnd.ms-excel指定了AnnualReport.pdf的檔案名),那麼結果是不可預測的。如果你不知道哪個MIME類型符合你要發送的檔案,你可以設定為 application/octet-stream。意思是“一些未指定的二進制檔案”。它告訴浏覽器自行決定如何處理檔案,通常基于檔案擴充名處理。

傳遞二進制數組

如果記憶體中已經存在二進制資料,你可以使用File方法的重載方法的發送給浏覽器,如下面的例子,發送二進制數組:

public FileContentResult DownloadReport() {

byte[] data = ... // Generate or fetch the file contents somehow

return File(data, "application/pdf", "AnnualReport.pdf");

我們在之前使用這種技術從資料庫中發送圖檔資料。注意,你必須指定contentType,并且可以指定一個fileDownloadName。和從硬碟發送檔案一樣,浏覽器也會以同樣的方式對待這個資料。

傳送流内容

如果能通過打開一個System.IO.Stream擷取資料,你也可以将這個流資料傳遞給File方法的一個重載方法。流的内容會被讀取,發送到浏覽器。如下面的示範代碼,傳送流内容:

public FileStreamResult DownloadReport(){

Stream stream = ...open some kind of stream...

return File(stream, "text/html");

傳回錯誤和HTTP代碼

我們最後要看的内建的ActionResult類是能用來發送特殊錯誤資訊和HTTP結果代碼給浏覽器的類。大多數應用程式不需要這些功能呢,因為MVC Framework會自動生成此類錯誤,但是,如果你要更直接的控制輸出到浏覽器的響應,這是可能就很有用了。

發送指定的HTTP代碼

你可以通過HttpStatusCodeResult 類發送指定的HTTP狀态代碼到浏覽器。這個沒有controller便捷方法,你必須直接執行個體化這個類。如下例子,發送指定的狀态碼:

public HttpStatusCodeResult StatusCode() {

return new HttpStatusCodeResult(404, "URL cannot be serviced");

HttpStatusCodeResult的構造參數是數字代碼和一個可選的描述資訊。在上面例子中,我們傳回了404錯誤碼,訓示請求的資源不存在。

發送404 Result

我們可以通過更簡便的HttpNotFoundResult類實作之前例子中同樣的效果,HttpNotFoundResult 繼承自HttpStatusCodeResult ,可以通過controller 的HttpNotFound方法建立。如下面的例子:

    return HttpNotFound();

發送401 Result

另一個包裝了專用的HTTP狀态碼的類是HttpUnauthorizedResult,該類傳回401代碼,訓示請求未被授權。如下代碼:

return new HttpUnauthorizedResult();

Controller類中沒有建立HttpUnauthorizedResult執行個體的便捷方法,是以必須直接使用。傳回此執行個體的效果通常是把使用者定向到認證頁面。

自定義 Action Result

内建的action result類對大多數情況下都已經足夠了,但是你也能建立自定義的action result。這裡,我們示範自定義action result,從對象中生成一個RSS document。如下代碼:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Xml.Linq;

namespace ControllersAndActions.Infrastructure {

public abstract class RssActionResult : ActionResult {

public class RssActionResult<T> : RssActionResult {

public RssActionResult(string title, IEnumerable<T> data,

Func<T, XElement> formatter) {

Title = title;

DataItems = data;

Formatter = formatter;

public IEnumerable<T> DataItems { get; set; }

public Func<T, XElement> Formatter { get; set; }

public string Title { get; set; }

public override void ExecuteResult(ControllerContext context) {

HttpResponseBase response = context.HttpContext.Response;

// set the content type of the response

response.ContentType = "application/rss+xml";

// get the RSS content

string rss = GenerateXML(response.ContentEncoding.WebName);

// write the content to the client

response.Write(rss);

private string GenerateXML(string encoding) {

XDocument rss = new XDocument(new XDeclaration("1.0", encoding, "yes"),

new XElement("rss", new XAttribute("version", "2.0"),

new XElement("channel", new XElement("title", Title),

DataItems.Select(e =&gt; Formatter(e)))));

return rss.ToString();

事實上,我們定義了2個類,第一個是一個抽象類RssActionResult,是ActionResult的子類。第二個是強類型的類RssActionResult<T>,從RssActionResult類繼承。我們定義2個類,這樣我們可以建立傳回抽象類RssActionResult的action方法,而不是傳回建立強類型子類的執行個體。自定義的action result的構造方法,RssActionResult<T>有3個參數,生成的RSS文檔的标題,文檔包含的資料項集合,一個生成轉換每個資料到XML片段的委托。

注意,我們暴露了title,資料項,和委托作為公共屬性。這讓單元測試簡單化。是以我們可以不需要調用ExecuteResult方法而決定結果的狀态。

要從抽象ActionResult類中繼承,必須提供一個ExecuteResult方法的實作。我們的例子使用了LINQ和XDocument API生成RSS文檔。文檔寫到Response對象,通過ControllerContext參數可以通路。下面顯示了使用了我們自定義action result的action 方法。

public RssActionResult RSS() {

return new RssActionResult<StoryLink>("My Stories", stories, e =&gt; {

return new XElement("item",

繼續閱讀