使用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";

}

Index

The day is: @ViewBag.Date.DayOfWeek

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 属性访问。如下:

public ViewResult Index() {

ViewData["Message"] = "Hello";

ViewData["Date"] = DateTime.Now;

return View();

}

在view中读出值,代码如下:

Listing 12-20. Reading View Data in a View

@{

ViewBag.Title = "Index";

}

Index

The day is: @(((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 方法,如下代码:

public RedirectResult Redirect() {

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的值,不需要创建匿名类型。如下代码:

public RedirectToRouteResult Redirect() {

return RedirectToAction("Index");

}

如果你指定一个action方法,然后假设你要调用的是当前controller的action方法。如果你要重定向到另一个controller,你需要提供这个controller的名字作为参数,如下:

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

还有其他的重载版本,你可以使用他们提供额外的值来生成URL,他们都采用匿名类型,虽然使用起来不那麽方便,但是仍然可以使你的代码简洁,

注意,在action方法和controller的值传递到路由系统之前,都是还没验证的。你必须确认你指定的目标是存在的。RedirectToAction执行临时重定向,RedirectToActionPermanent是永久重定向。

重定向中保护数据

重定向导致浏览器提交一个全新的HTTP请求,这意味着你不能访问到原始请求的细节。如果你想要从一个请求传递数据到另一个,你需要使用Temp Data功能。TempData 类似于 Session data,除了TempData值可以在读取时标记为删除,当请求处理完毕,他们可以被删除。对于仅在重定向期间暂存数据,这个功能非常理想。下面的是一个简单的例子:

public RedirectToRouteResult Redirect() {

TempData["Message"] = "Hello";

TempData["Date"] = DateTime.Now;

return RedirectToAction("Index");

}

当这个方法处理一个请求的时候,会设置TempData集合中的值,然后重定向用户的浏览器到Index方法,然后将值传送给view,如下:

public ViewResult Index() {

ViewBag.Message = TempData["Message"];

ViewBag.Date = TempData["Date"];

return View();

}

在视图中可以更直接的读取这些值,比如:

@{

ViewBag.Title = "Index";

}

Index

The day is: @(((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";

}

结果如下图:

clipboard

返回XML数据

从action方法返回XML数据很简单,尤其是你使用LINQ to XML 和XDocument API 从对象中生成XML,如下提供了一个演示:

public ContentResult XMLData() {

StoryLink[] stories = GetAllStories();

XElement data = new XElement("StoryList", stories.Select(e => {

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片段如下:

link="/Story/1" />

link="/Story/2" />

link="/Story/3" />

返回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() {

StoryLink[] stories = GetAllStories();

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)_第1张图片

我们使用的File方法的重载方法有3个参数:

File方法的参数说明,如下表:精通MVC3摘译(8)-处理输出(2)_第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方法创建。如下面的例子:

public HttpStatusCodeResult StatusCode() {

    return HttpNotFound();

}

发送401 Result

另一个包装了专用的HTTP状态码的类是HttpUnauthorizedResult,该类返回401代码,指示请求未被授权。如下代码:

public HttpStatusCodeResult StatusCode() {

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 : RssActionResult {

public RssActionResult(string title, IEnumerable data,

Func formatter) {

Title = title;

DataItems = data;

Formatter = formatter;

}

public IEnumerable DataItems { get; set; }

public Func 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 => Formatter(e)))));

return rss.ToString();

}

}

}

事实上,我们定义了2个类,第一个是一个抽象类RssActionResult,是ActionResult的子类。第二个是强类型的类RssActionResult,从RssActionResult类继承。我们定义2个类,这样我们可以创建返回抽象类RssActionResult的action方法,而不是返回创建强类型子类的实例。自定义的action result的构造方法,RssActionResult有3个参数,生成的RSS文档的标题,文档包含的数据项集合,一个生成转换每个数据到XML片段的委托。

注意,我们暴露了title,数据项,和委托作为公共属性。这让单元测试简单化。所以我们可以不需要调用ExecuteResult方法而决定结果的状态。

要从抽象ActionResult类中继承,必须提供一个ExecuteResult方法的实现。我们的例子使用了LINQ和XDocument API生成RSS文档。文档写到Response对象,通过ControllerContext参数可以访问。下面显示了使用了我们自定义action result的action 方法。

public RssActionResult RSS() {

StoryLink[] stories = GetAllStories();

return new RssActionResult("My Stories", stories, e => {

return new XElement("item",

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

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

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

});

}

要使用我们自定义的action result,我们穿件了强类型类的一个实例,传递必要的参数,此例中,我们使用之前例子中的StoryLink类。委托(我们使用的是Func)生成XML。如果你导向到这个action方法,你会生成一个浏览器能识别的RSS文档。

精通MVC3摘译(8)-处理输出(2)_第3张图片