一、Controller的责任
MVC的核心就是Controller(控制器),它负责处理浏览器传送过来的所有请求,并决定要将什么内容响应给浏览器。但Controller并不负责决定内容应该如何显示,而是将特定形态的内容响应给MVC架构,最后才由MVC架构依据响应的形态来决定如何将内容响应给浏览器。如何决定响应内容是View的责任。
二、Controller的类与方法
Controller本身就是一个类(Class),该类有许多方法(Method)。在这些方法中,只要是公开方法,该方法就会被视为是一种动作(Action);只要有动作存在,就可以通过该动作方法接收网页请求并决定响应视图。
由上可知编写Controller的基本要求:
- Controller必须为公开类。
- Controller的名称必须以"Controller"结尾。
- 必须继承自MVC内置的Controller类,或继承自实现IController接口的自定义类,或自行实现IController接口。
- 所有方法必须为公开方法。改方法可以没有参数,也可以有多个参数。
三、Controller的执行过程
Controller被MvcHandler选中之后,下一步就是通过ActionInvoker选取适当的Action来执行。在Controller中,每一个Action可以定义0到多个参数。ActionInvoke会依据当前的RouteValue及客户端传过来的信息准备好可输入Action参数的依据,最后正式调用被Controller选中的那个Action方法。
Action执行完后的返回值通常是ActionResult类的。事实上,ActionResult类是一个抽象类,因此,MVC本身就实现了许多不同ActionResult类的子类。Controller得到ActionResult类之后,就会开始执行ActionResult类的ExecuteResult()方法,并将执行的结果返回客户端。这时,Controller的任务就算完成了。
Controller在执行时还有一个动作过滤器(Action Filter)机制,可以分成以下4中类型。
- 授权过滤器(Authorization Filter);
- 动作过滤器(Action Filter);
- 结果过滤器(Result Filter);
- 例外过滤器(Exception Filter)。
此外,在Controller的执行过程中还必须考虑动作过滤器的执行顺序。除上述说明之外,在执行Action与ActionResult类时嗨会有一些事件被执行,这部分将在九中说明。
四、动作名称选取器
通过ActionInvoker选取Controller中的公开方法时,默认会用Reflection(映像)的方式取得Controller中具有相同名字的方法(不区分大小写)。如下程序范例表示得很清楚:当RouteValue表达式中的Action是"Index",默认会执行Index()方法。
1 public class HomeController : Controller 2 { 3 /// <summary> 4 /// 要求网址 http://localhost/Home/Index 5 /// </summary> 6 public ActionResult Index() 7 { 8 return View(); 9 } 10 11 }
如果在以上的Action中加入ActionName属性,并将其指名为"Default",此时,若RouteValue表达式中的Action是"Index",就不会执行Index()方法,而必须使RouteValue表达式中的Action为"Default",Index()方法才能被正确执行,这就是动作名称选取器(Action Name Selector)的作用,示例如下。
1 public class HomeController : Controller 2 { 3 /// <summary> 4 /// 要求网址 http://localhost/Home/Index 5 /// </summary> 6 [ActionName("Default")] 7 public ActionResult Index() 8 { 9 return View(); 10 } 11 12 }
唯一需要特别注意的是,如果你使用默认的"return View()"方法返回ActionResult类,由于应用了[ActionName("Default")]属性,所以MVC会去寻找"/Views/Home/Default.aspx"页面而不是"/Views/Home/Index.aspx"页面来执行。
五、动作方法选取器
5.1 NonAction属性
若将NonAction属性应用在Controller中的Action方法上,即便该Action方法是公开方法,也会告知ActionInvoke不要选取这个Action来执行。这个属性主要用来保护Controller中的特定公开方法不会被发布到Web上。或是当功能尚未开发完成就要进行部署时,若暂时不想将此方法删除,也可应用这个属性,表示"不要对外公开"。
1 [NonAction] 2 public ActionResult Index() 3 { 4 return View(); 5 }
将Action方法中的"public"修改成"privare",也可以达到同样的目的,示例如下:
1 private ActionResult Index() 2 { 3 return View(); 4 }
5.2 HttpGet属性、HttpPost属性、HttpDelete属性和HttpPut属性
HttpGet、HttpPost、HttpDelete和HttpPut属性是动作方法选取器的一部分,我们以下列程序为例进行介绍。若应用了[httpPost]属性,表示只有当客户端浏览器发送HTTP POST请求时才可以选取这个Action。
1 [HttpPost] 2 private ActionResult Index() 3 { 4 return View(); 5 }
相反的,若果没有应用这些属性,客户端浏览器发送任何HTTP动词,都会自动选取对应的Action。
这些属性常用在需要接受窗口数据的时候。你可以创建两个同名的Action,一个应用[HttpGet]属性来显示窗口HTML,另一个应用[HttpPost]属性来接收窗口送出的值,范例程序如下。
1 [HttpGet] 2 public ActionResult Create() 3 { 4 return View(); 5 } 6 [HttpPost] 7 private ActionResult Create(FormCollection c) 8 { 9 UpdateToDB(c); 10 return RedirectToAction("Index"); 11 }
NOTE 由于HTML窗口无法送出"Delete"这个Http动词,所以如果希望Action能提供像RESET协议那样的方式来处理删除动作,又能通过同一个窗口使用这个只允许"Delete"的动作的话,可以用Html.HttpMethodOverride()方法的HTML辅助方法来模拟Http Delete方法的行为,但实际上窗口还是以Http Post的方式送出去的。
六、 ActionResult类
ActionResult类是Action执行的结果,但ActionResult中并不包含执行结果,而是包含执行响应时所需的信息。当Action返回ActionResult类之后,会由MVC执行。先看看ActionResult抽象类的程序代码。在ActionResult抽象类中仅定义了一个ExecuteResult()方法来执行结果。
1 namespace System.Web.Mvc 2 { 3 // 摘要: 4 // 封装一个操作方法的结果并用于代表该操作方法执行框架级操作。 5 public abstract class ActionResult 6 { 7 // 摘要: 8 // 初始化 System.Web.Mvc.ActionResult 类的新实例。 9 protected ActionResult(); 10 11 // 摘要: 12 // 通过从 System.Web.Mvc.ActionResult 类继承的自定义类型,启用对操作方法结果的处理。 13 // 14 // 参数: 15 // context: 16 // 用于执行结果的上下文。上下文信息包括控制器、HTTP 内容、请求上下文和路由数据。 17 public abstract void ExecuteResult(ControllerContext context); 18 } 19 }
MVC定义的ActionResult如表所示:
类 |
Contro辅助方法 |
用 途 |
ContentResult |
Content |
返回一段用户自定义的文字内容 |
EmptyResult |
不返回任何数据,即不响应任何数据 |
|
JsonResult |
Json |
将数据序列化成JSON格式返回 |
RedirectResult |
Redirect |
重定向到指向的URL |
RedirectToRouteResult |
RedirectToAction、RedirectToRoute |
与RedirectResult类似,但它将新定向到一个Action或Route |
ViewResult |
View |
使用IViewInstance接口和IViewEngine接口,实际输出数据的是IViewEngine接口和View |
PartialViewResult |
PartialView |
与ViewResult类相似,返回的是”部分显示”,即”UserControls”目录下的View |
FileResult |
File |
以二进制串流的方式返回一个文件数据 |
JavaScriptResult |
JavaScript |
返回的是JavaScript指令码 |
表中的Controller辅助方法在Controller类中为返回ActionResult类提供支持,如下程序可用于跳转到另一个页面。
1 [HttpPost] 2 public ActionResult Post(FormCollection c) 3 { 4 return new RedirectResult("/"); 5 }
如果使用Controller辅助方法,就可以将以上程序改写如下:
1 [HttpPost] 2 public ActionResult Post(FormCollection c) 3 { 4 return Redirect("/"); 5 }
以上两段程序代码其实差不多,但实际操作中则是以使用Controller辅助方法居多。
6.1 ViewResult类
ViewResult类是在MVC中最常用的ActionResult类,用于返回一个标准的视图。通过Controller辅助方法,我们能更方便地定义如何输出视图。指定要输出的View名称、指定该View要应用哪个MasterPage、指定要输入的View的Model。
6.2 PartialViewResult类
PartialViewResult类与ViewResult类非常相似,但它无法为View赋值MasterPage,通常用在前端为Ajax应用程序的情况下,并可以通过Ajax来取得网页中的部分内容。
如下程序会执行"/Views/Home/About.ascx"页面,并将结果输出至客户端。
1 public ActionResult About() 2 { 3 return PartialView(); 4 }
6.3 EmptyResult类
有些Action在执行后其实不需要返回任何数据,例如一个页面执行完后直接转到其他页面的情况。EmptyResult类不会执行任何响应客户端的程序,所以也不会返回任何数据,使用方法如下:
1 public ActionResult Empty() 2 { 3 return new EmptyResult(); 4 }
在MVC中,还有一种表达EmptyResult类的方式,即将上述语法写成如下:
1 public void Empty() 2 { 3 return; 4 }
有一种情况是用EmptyResult类搭配Response.Redirect()方法进行HTTP 302暂时转向,示例如下:
1 public void Redirect() 2 { 3 Response.Redirect("/Home/Index"); 4 }
如果已经开始使用.NET 4.0,也可以考虑使用4.0新增的Respo.RedirectPermanent()方法建立HTTP 301永久转向,示例如下。
1 public void Redirect() 2 { 3 Response.RedirectPermanent("/Home/Index"); 4 }
6.4 ContentResult类
ContentResult类可以响应文字内容的结果。可以让ContentResult类响应任意指定文字内容。Content-Type和文字编码(Encoding)。
如下范例将会响应一段XML文字,并设定响应的Content-Type为text/xml(已指定响应的文字编码方式为Encoding.UTF8)。
1 public ActionResult Content() 2 { 3 return Content("<ROOT><TEXT>123</TEXT></ROOT>","text/xml",Encoding.UTF8); 4 }
如果只响应一串HTML字符串,可以只使用第一个参数,如下:
1 public ActionResult Content() 2 { 3 string strHTML = "........"; //省略 HTML的内容 4 return Content(strHTML); 5 }
还有一种方法可以表达如上一样简单的返回类,直接将返回类设定成"string"即可。MVC会进行判断,只要Action返回的不是ActionResult类,就会将返回的类转换成字符串类输出。
6.5 FileResult类
FileResult类可以响应任意的文件内容,包括二进制格式的数据,例如图像文件。PDF文档或ZIP文件,可以输入byte数组、文件路径、stream数据、Content-Type、下载文件名等参数并将其返回客户端。事实上,FileResult是一个抽象类,在MVC中实现FIleResult类的子类共有3个,分别是:
- FilePathResult:响应一个实体文件;
- FileContentResult:响应一个byte数组的内容;
- FileStreamResult:响应一个Stream数据。
但通过Controller类中所提供的File辅助方法可以让你不用记忆这么多。File()辅助方法能自动选取不同的FileResult类进行响应。
如果通过Action输出一个存储在"App_Data"目录栏下的PNG图像文件,可以参考以下代码:
1 public ActionResult GetFile() 2 { 3 return File(Server.MapPath("~/App_Data/UserA/a.png"),"image/png"); 4 }
若希望直接通过浏览器下载文件,而不是在浏览器打开文件,可以再第3个参数中输入要求下载的文件名。如:PDF文件来自于数据库,并希望让用户下载,可以先取得一个byte数组或Stream数据,并在File()辅助方法的第2个参数中指定正确的Content-Type,最后再指定要下载的文件名即可。
1 public ActionResult GetFile() 2 { 3 byte[] fileContent = GetFileByteArrayFromDB(); 4 5 return File(fileContent,"application/pdf","Report.pdf"); 6 }
6.6 强制下载文件时需注意中文文件名的问题
由于MVC支持中文文件名文件下载的能力有限,若要开发出跨浏览器下载中文文件名文件的解决方案,就不能只靠FileResult类了,可以自定义ActionResult类来克服这个问题,也可直接用较为底层的方法来解决。一下将说明强制下载文件的原理及解决方法。
强制下载文件功能时,通常是通过设定"Content-Disposition"这个HTTP响应标头(Response Header)的方式将强制下载文件的要求告知客户端,示例如下。
1 string fileName = "ExportData.csv"; 2 string strContentDisposition = String.Format("{0};filename=\"{1}\"", "attachment", fileName); 3 Response.AddHeader("Content-Disposition", strContentDisposition);
通过上述程序代码,就可以让客户端强制下载此页面的内容,也就是说,改页面的内容(可能是文件或二进制文件)不会直接在浏览器中打开,下载后也不会打开相关程序。
Content-Disposition标头的第一组参数是"attachment",代表此文件是一个附件文件,也就是"要求下载"的意思。如果将"attachment"改成".inline"的话,就代表这是一个内嵌于其他网页的文件(如图像文件、CSS、JavaScript、Flash等),而这也是默认的设定,等同于不添加Content-Disposition标头的情况。
6.7 JavaScriptResult类
JavaScriptResult类的用途是将javaScript程序代码响应给浏览器。通过Ajax环境,可以利用JavaScriptResult类来响应适当的JavaScript程序代码并将其交给浏览器动态执行,由于Ajax功能属于View.
6.8 JsonResult类
JSON是Web在实现Ajax应用程序时经常使用的一种数据传输格式,JsonResult类可自动将任意对象的数据序列转换成JSON格式返回。JsonResult类默认的Content-Type为application/json,对于某些JavaScript Framework来说,这是必要的需求,如jQuery。 建议尽量避免使用HTTP GET方法获取JSON数据。但若只使用HTTP POST方法获取JSON数据也存在一个问题,那就是数据无法被浏览器缓存。如果数据敏感度不高且想实现缓存的话,还需让JsonResult类能够对HTTP GET请求进行响应,解决方案就是为JSON()辅助方法加上一个JsonResultBehavior列举参数,这样就可以通过GET方法取得JSON数据了。
1 public ActionResult JSON() 2 { 3 return Json(new 4 { 5 id = 1, 6 name = "will", 7 CreatedOn = DateTime.Now 8 }, JsonRequestBehavior.AllowGet); 9 }
6.9 RedirectResult类
RedirectResult类主要用途是执行指向其他页面的重定向。在RedirectResult类的内部,基本上还是用Response.Redirect()方法响应HTTP 302暂时定向的。
1 public ActionResult Redirect() 2 { 3 return Redirect("/Home/Index"); 4 }
6.10 RedirectToRoute类
Controller类中有两个与RedirectToRoute类有关的辅助方法,分别是RedirectToAction()和RedirectToRoute().
1. RedirectToAction()辅助方法
RedirectToAction()方法比较简答,既可通过直接输入Action名称来设定让浏览器转向该Action网址,也可以输入新建的RouteValue值,如下:
- 转到同一个Controller中的另一个Action。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("SamplePage"); 4 }
- 转到指定Controller的特定Action。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("List", "Member"); 4 }
- 转到MemberController的List Action,并且加上"page"这个RouteValue值。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("List", "Member", new { page = 3 }); 4 }
2. RedirectToRoute()辅助方法
RedirectToRoute()辅助方法较为复杂,可利用Global.asax文件中定义的网址路由表来指定不同的转向网址。
- 转到同一个Controller中的另一个Action。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { action = "SamplePage" }); 4 }
- 转到指定Controller的特定Action。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { controller = "Member", action = "list" }); 4 }
- 转到MemberController的List Action,并且加上"page"这个RouteValue。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { controller = "Member", action = "list", page = 3 }); 4 }
七、ViewData与TempData概述
7.1 ViewData
ViewData属性是一个ViewDataDictionary类,可用于存储任意对象的数据,但存储的键值必须为字符串。
ViewData有一个特性,就是它只会存在于当前的HTTP请求中,而不像Session一样可以讲数据带到下一个HTTP请求。
7.2 TempData
TempData的数据结构与ViewData一样,都属于字典类,不过TempData属性的类是TempDataDictionary。TempData嗨有一点不一样的地方,就是它的内部是用Session来存储数据的。存储在TempData中的数据只会暂存:1次网页请求!
1次网页请求的定义:当窗口数据被传送到以下Action中进行存储时,如果发生新建数据失败的情况,我们会希望这次传送的数据可以保留至下一个页面,此时,就会将这个只希望出现1此的信息保存到TempData中,并在下一个页面中执行。
1 [HttpPost] 2 public ActionResult Create(Message msg) 3 { 4 if (!UpdateMessageToDB(msg)) 5 { 6 TempData["PostedMessage"] = msg; 7 return RedirectToAction("Create"); 8 } 9 return RedirectToAction("Index"); 10 }
当新建数据失败的情况发生后,将会返回"Create"这个Action,并将原来的数据从TempData中读出,示例如下:
1 [HttpGet] 2 public ActionResult Create() 3 { 4 string data = TempData["PostedMessage"] as Message; 5 return View(data); 6 }
此时,数据就会回到Create()方法,并在此被传送到Create视图。在这个Controller生命周期结束的前一刻,由于MVC会记录"TempData["PostedMessage"]"语句已经被读取了,因此,在网页请求结束之前会将"TempData["PostedMessage"]"语句删除。
八、模型绑定
8.1 简单模型绑定
8.2 使用FormCollection类获取窗口数据
除了可以通过简单模型绑定机制获取窗口传过来的单栏数据之外,我们也可以通过FormCollection类一次获取整个窗口传送过来的数据。只要设定一个FormCollection类的参数,就可以获取所有从窗口传送过来的数据。这种用法与使用Request.Form()方法一样,不过在MVC里还是建议尽量不要用Request.Form()方法来获取窗口数据。
1 public ActionResult GetFormCollection(FormCollection form) 2 { 3 ViewData["uName"] = form["uName"]; 4 return View("ModelBinderDemo"); 5 }
8.3 复杂模型绑定
8.4 多个复杂模型的绑定
好比一个HTML代码中只有一个Form(表单)窗口,但是该窗口内有两组字段,是因为在编写数据绑定类里面设定了两组参数,分别为form1和form2,而这两组参数的类都是GuestbookForm,如:
1 public Action ComplexModelBinding(GuestbookForm form1, GuestbookForm form2) 2 { 3 InsertIntoDB(form1); 4 InsertIntoDB(form2); 5 return Redi("/"); 6 }
只需要做好模型绑定即可。
8.5 判断模型绑定的结果
验证数据,用自身的方法如Html.ValidationMessageFor()方法用来显示特定字段的错误信息。也可在Controller中判断业务逻辑。
九、动作过滤器(略)