MVC笔记 Controller相关技术

一、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中判断业务逻辑。

九、动作过滤器(略)

 

你可能感兴趣的:(controller)