MVC框架搜索的目录序列是另外一个由命名约定决定配置的例子,无需向框架注册视图文件,只要将其放在一些已知的位置,框架就会自动去寻找这些文件。
提示:定位视图的命名约定是可自定义的,请参见第十五章。
如果要进一步的使用规约,可以通过使用View方法来省略要呈现的视图的名称。
清单12-13:在不指定名称的情况下创建一个ViewResult
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController:Controller
{
public ViewResult Index()
{
return View();
}
}
}
一旦这样返回,MVC框架就会假设要呈现的视图与包含这个返回方法的Action方法具有相同的名称,也就是说,上图中调用的View方法之后,框架会搜索名称为Index的视图。
注意:按照上述方式,视图会与被搜索的Action方式具有相同的名称,但是实际上,视图的名称却是从RouteData.Values["action"]中的值,详见清单12-7,这些在第十一章中作为路由系统的一部分已作出了说明。
View方法被大量重写,但各种版本都会向最终创建并返回的ViewResult对象设置各种不同的属性。例如,可以重写一个视图使用的layout值来显式指定所使用的layout文件,就像这样:
public ViewResult Index()
{
return View("Index","AlternativeLayoutPage");
}
=================通过路径来指定一个视图==============
命名规约这种方式简单又方便,但是却限制了能够被呈现的视图。如果想要呈现特定的视图的话,可以通过显式指定一个路径来绕过搜索阶段。如下例:
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController:Controller
{
public ViewResult Index()
{
return View("~/Views/Other/Index.cshtml");
}
}
}
如果要这样指定一个视图,那么这个路径必须以/或者~/开始并且以扩展名结尾。
要使用这样一个功能,首先要明白要到达怎样的目的。
如果是要呈现一个属于其他控制器的视图,那么最佳的方式是将用户请求重定向到那个控制器的Action方法。
如果要解决的是由于视图的命名不符合项目的组织方式,进而产生的命名方案的问题,请参见第十五章,详细描述了如何实现一个自定义的搜索序列。
=================================================
从Action方法向视图传递数据
我们常常会从Action方法向视图传递数据,MVC框架提供了一组不同的方式来满足这个需要,将会在下面几节中陈述。在这几节里中涉及的关于视图的主题将会在第十五章中深入的讲解。
提供一个视图模型对象
可以通过向View方法传递参数来向视图发送一个对象,如清单12-14所示:
清单12-14 指定视图模型对象
public ViewResult Index()
{
DateTime date=DateTime.Now;
return View(date);
}
这样上述的date对象就会作为视图模型传递到视图中去,可以使用Model关键字来访问,如清单12-15所示:
清单 12-15:访问视图模型
==================================
@{
ViewBag.Title="Index";
}
<h2>Index</h2>
The day is : @(((DateTime)Model).DayOfWeek)
==================================
上述视图是典型的非类型化或者说弱类型化的视图,这种视图不包含任何关于视图模型对象的信息,只将其作为基本的object实例来处理。要获取DayOfWeek属性的值,就必须将这个对象转换成DateTime类型的实例。但是这样虽然能成功,却也产生了凌乱的视图。通过对视图进行将类型化,可以使得视图变得干净许多,如清单12-16所示:
清单12-16 强类型化的视图
========================
@model DateTime
@{
ViewBag.Title="Index";
}
<h2>Index</h2>
The day is : @Model.DayOfWeek
========================
可以使用model关键字来指定视图使用的模型,注意是小写的m,而在读取值的时候使用的却是大写的M。这样一来不止是使得视图整洁,VS也对强类型化的视图具有很好的支持。
使用ViewBag特性传递数据
ViewBag可以保存任意的动态对象并在视图中访问,在控制器中,通过Controller.ViewBag属性访问,如清单12-17显示:
清单 12-17 使用ViewBag特性
public ViewResult Index()
{
ViewBag.Message="Hello";
ViewBag.Date=DateTime.Now';
return View();
}
上述清单中定义了Message和Date属性,在这之前,这些属性并不存在,也没有预先创建。
===================
此部分由于自动存草稿丢失
===================
返回文本数据
除了HTML之外,还有许多其他的基于文本的数据格式需要生成并相应给客户端。包括:
1.XML,RSS,Atom
2.JSON
3.CSV
4.普通文本
MVC框架已经特别为JSON数据提供支持,对于其他的格式,可以使用用于通用目的的ContentResult作为action的返回值。如下:
清单 12-25 从action方法返回文本数据
public ContentResult Index()
{
string message = "This is plain text";
return Content(message, "text/plain", Encoding.Default);
}
以上使用了Controller.Content这个帮助器方法创建并返回一个ContentResult对象,该方法使用3个参数:
1. 首先是要发送的文本数据
2. 第二个是响应的HTTP content-type响应头的值,也可以使用System.Net.Mime.MediaTypeName类来获取这个值,对于普通文本来说,这个值就是text/plain
3. 最后一个参数指定了将文本转换问字节的编码形式
后两个参数可以忽略,但是这样一来框架会默认使用text/html作为内容类型,也就是说按照HTML进行返回。默认情况下还会使用浏览器声明支持的编码形式对文本进行处理,这样就可以如下返回文本:
return Content("This is plain text");
事实上,还可以更进一步,如果action方法返回任何非ActionResult类型的对象,MVC框架都会视图将这个对象序列化为一个字符串值并作为HTML进行发送,如下:
清单 12-26 从action方法返回非ActionResult类型的对象
public object Index()
{
return "This is plain text";
}
返回XML数据
从action方法返回XML数据非常简单,尤其是使用LINQ to SQL或使用XDocument API从对象生成XML时,如下:
清单 12-27 在action方法中生成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类型用于生成XML,如下定义:
public class StoryLink
{
public string Title { get; set; }
public string Description { get; set; }
public string Url { get; set; }
}
这个action方法的结果是一个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>
提示:如果对LINQ to XML和XDocument API不熟悉的话,那么学一学还是有好处的,这些技术提供了最简单和最优雅的方式来处理XML数据。
返回JSON数据
近年来,XML形式数据在网络应用中的地位逐渐下降,取而代之的是Javascript Object Notation格式,即JSON,它是轻量级的,基于文本并能够描述层次数据结构的格式。
JSON数据是经过验证的Javascript代码,这就意味着其可以被所有的主流浏览器支持,这使其较之XML数据更加的简炼和易于使用。JSON最常见的用途是从服务器向客户端的AJAX查询发送包含数据的响应。
MVC框架内置了JsonResult类,用于将.NET的类型序列化为JSON格式,通过Controller.Json方法可以方便的实现该功能,如下:
清单 12-28 创建JSON数据和JsonResult类
[HttpPost]
public JsonResult JsonData()
{
StoryLink[] stories = GetAllStories();
return Json(stories);
}
这个例子使用了和之前一样的StoryLink类,但是序列化行为却是由JsonResult类来保存,这个例子产生的响应结果如下:
[{"Title":"First example story","Description":"This is the first example story","Url":"/Story/1"},
{"Title":"Second example story","Description":"This is the second story","Url":"/Story/2"},
{"Title":"Third example story","Description":"This is the third example story","Url":"/Story/3"}]
注意:出于安全考虑,JsonResult对象只会为HTTP POST请求产生响应,这是为了防止数据暴露在第三方的XSS攻击面前。
返回文件和二进制数据
FileResult是所有会向浏览器发送二进制数据的action结果的抽象基类,MVC框架内置了三个该类的子类:
1. FilePathResult 直接发送服务器文件系统中的文件
2. FileContentResult 发送一个内存中的字节数组的内容
3. FileStreamResult 发送一个已经打开的 System.IO.Stream 对象所包含的内容
不必为使用哪个类型而担心,因为这些类型都会自动的为Controller.File 这个帮助器的不同重载方法所使用。
发送一个文件
以下清单演示了如何从磁盘发送一个文件:
清单12-29 发送一个文件
public FileResult AnnualReport()
{
string filename=@"c:\AnnualReport.pdf";
string contentType="application/pdf";
string downloadName="AnnualReport2011.pdf";
returnt File(filename, contentType, downloadName);
}
这个动作会让浏览器弹出一个文件保存或修改文件的对话框,不同的浏览器中可能以不同的方式处理。
清单12-29中的重载参数说明如下:
参数 | 必须 | 类型 | 描述 |
filename | 是 | 字符串 | 表示文件在服务器文件系统中的路径 |
contentType | 是 | 字符串 | 响应头的MIME类型,浏览器将根据内容类型来决定如何处理文件 |
fileDownloadName | 否 | 字符串 | 响应头的content-disposition的值,如果指定了这个参数,浏览器将会始终以文件保存或打开的方式来弹出对话框,并将该值作为下载文件的名称,并无视该文件在服务器文件系统中的名称。 |
如果指定了fileDownloadName这个参数,并且浏览器知道如何解析这个文件类型,比如说image/gif,那么浏览器将直接显示这个文件。
否则,浏览器将会呈现文件打开保存对话框。
注意:如果为文件指定了不相符合的内容类型,那么将会产生不可预知的结果,如果不知道要发送的文件的MIME类型是什么,那就使用application/octet-stream来代替。
这意味着这是某种未知的二进制文件,浏览器会根据其扩展名自行处理这个文件。
发送一个字节数组
如果二进制数据已经贮存在内存中,那么可以直接使用File方法的另一种重载形式向浏览器发送数据,如下:
清单12-30 发送二进制数组
public FileContentResult DownloadReport()
{
byte[] data = ...// 以某种形式生成或获取文件内容
return File(data, "application/pdf", "AnnualReport.pdf");
}
我们曾在第九章的末尾使用这种方式发送过从数据库获取的图片数据,而这次指定了另一种内容类型以及在客户端浏览器显示的文件名称。
对于以这种方式发送的文件,浏览器将以和磁盘文件相同的方式处理。
发送流中的内容
如果已经将数据存放在一个已打开的System.IO.Stream 的实例对象中,就可以使用File方法的另一种重载形式直接发送这个数据流,如下:
清单 12-31 发送流中的内容
public FileStreamResult DownloadReport()
{
Stream stream = ...//some kind of stream
return File(stream, "text/html");
}
返回错误及HTTP码
下面将介绍最后一种 ActionResult 类,该类用于向客户端发送特定的错误信息以及HTTP响应码。大多数应用不会用到这个功能,因为MVC框架会自动生成这些结果。
然而,如果需要更加直接的控制响应的话,那么这种方式就十分有用。
发送特定的HTTP响应码
使用 HttpStatusCodeResult 类可以实现这个功能,但对于这个功能,不存在一个控制器帮助器方法来处理它,所以必须直接实例化这个类,如下:
清单 12-32 发送特定HTTP响应码
public HttpStatusCodeResult StatusCode()
{
return new HttpStatusCodeResult(404, "URL cannot be serviced");
}
HttpStatusCodeResult的构造器参数是用数字表示的状态参数,以及一个可选的描述性参数。上述清单中,返回的代码是404,表示请求的资源不存在。
发送404结果
可使用更加方便的HttpNotFoundResult类来达到和清单12-32中相同的效果,该类是从HttpStatusCodeResult类派生出来的,控制器为这个类提供了帮助器方法,HttpNotFound方法,如下:
清单 12-33 生成404结果
public HttpStatusCodeResult StatusResult()
{
return HttpNotFound();
}
发送401结果
另特定的HTTP状态码的封装类是HttpUnauthorizedResult,其返回401码,表示某个请求是未经授权的,如下:
清单 12-34 生成401结果
public HttpStatusCodeResult StatusResult()
{
return new HttpUnauthorizedResult();
}
对于该类,控制器并未提供帮助器方法,所以必须直接返回该类的实例,返回该类实例的结果通常是将用户重定向到身份验证的页面。
创建自定义Action Result
内置的各种ActionResult类型满足了大多数的情景和用途,但是,同样可以创建自定义的ActionResult来满足某些特殊用途。
接下来将演示如何创建一个自定的ActionResult,可以从对象中生成RSS文档。RSS是一种文档格式,用于向订阅者发布一些频繁更新的项目,如新闻的头条等。
清单 12-35 创建一个自定义ActionResult
using System;
using System.Collection.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 string Title { get; set; }
public Func<T, XElement> Formatter { get; set; }
public override void ExecuteResult (ControllerContext context)
{
HttpResponseBase response = context.HttpContext.Response;
//set the content type of response
response.ContentType = "application/rss+xml";
//get the RSS content
string rss = GenerateXML(response.ContentEncoding.WebName);
//write the content to result
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();
}
}
}
实际上述操作定义了两个类,第一个是名为RssActionResult的抽象类,从ActionResult继承而来。而第二个是名为RssActionResult<T>的强类型类,从抽象类RssActionResult继承而来。通过定义这两个类可以创建返回RssActionResult的action方法,但却必须创建强类型子类的实例。
RssActionResult<T>子类的构造器方法接受三个参数,要生成的RSS文档的标题,所有数据项的集合,以及一个用于将每一个数据项都转换成XML片段的代理。
要从抽象类ActionResult中继承,就必须得提供对ExecuteResult方法的实现,上述示例使用LINQ和XDocument API生成一个RSS文档,这个文档被写入到响应对象中,然后可以使用ControllerContext参数进行访问。
清单 12-36 使用自定义ActionResult
public RssActionResult RSS()
{
StoryLink[] stories = GetAllStories();
return new RssActionResult<StoryLink>("My Stories", stories, e => { return new XElement("item",
new XAttribute("title", e.Title),
new XAttribute("description", e.Description),
new XAttribute("", e.Url)
);
};
);
}
要使用自定义的ActionResult,只需要简单地创建一个强类型类的实例,并传递必须的参数就可以了。
总结
控制器是MVC模式中举足轻重的组成部分,本章展示了如何通过实现IController接口来创建原始的控制器,或者通过派生Controller类来创建更加方便的方法。
在下一章中,我们将继续更加深入的研究控制器的构成,以便定制控制器的创建和行为方式,这样就可以为应用剪裁控制器。