前端常见问题
选课中心 APP下载
当前位置:首页 > 技能类 > 前端 > 常见问题 > 了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult

了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult

更新时间:2020-09-05 04:12:07 来源: 阅读量:

【摘要】 了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult考必过小编为大家整理了关于了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult的信息,希望可以帮助到大家!

了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult

标签:valueBMIparc抽象为我setcookiebarpubpc

定义在Controller中的Action方法大都返回一个ActionRespt对象。ActionRespt是对Action执行结果的封装,用于最终对请求进行响应。ASP.NET MVC提供了一系列的ActionRespt,它们本质上是通过怎样的方式来响应请求的呢?这是这个系列着重讨论的主题。[本文已经同步到《How ASP.NET MVC Works?》中]

目录
一、ActionRespt对请求的响应
二、EmptyRespt
三、ContentRespt
四、实例演示:执行返回类型为非ActionRespt的Action方法得到的ActionRespt对象
五、实例演示:通过ContentRespt实现主题定制

一、ActionRespt对请求的响应

HTTP是一个单纯的采用请求/回复消息交换模式的网络协议,Web服务器在接收并处理来自客户端的请求后会根据处理结果对请求予以响应。对于来自客户端的访问请求,最终的处理体现在针对目标Action方法的执行,我们可以在定义Action方法的时候人为地控制对请求的响应。如果下面的代码片断所示,抽象类Controller具有一个只读的Response属性表示当前的HttpResponse,我们可以直接利用它来实现对请求的响应。我们也可以间接地通过表示当前HTTP上下文的HttpContext属性和表示Controller上下文的ControllerContext属性来获取用于响应请求的HttpResponse对象。

   1: pubpc abstract class Controller : ControllerBase, ...
   2: {
   3:     //其他成员
   4:     pubpc HttpResponseBase     Response { get; }
   5:     pubpc HttpContextBase     HttpContext { get; }
   6: }
   7: 
   8: pubpc abstract class ControllerBase : IController
   9: {
  10:     //其他成员
  11:     pubpc ControllerContext ControllerContext { get; set; }
  12: }

原则上讲,我们可以利用HttpResponse对请求响应作百分之一百地控制,但是我们一般并不这么做,而是将针对请求的响应实现在一个ActionRespt对象中。如下面的代码片断所示,ActionRespt是一个抽象类型,最终的请求响应实现在抽象方法ExecuteRespt方法中。

   1: pubpc abstract class ActionRespt
   2: {    
   3:     //其他成员
   4:     pubpc abstract void ExecuteRespt(ControllerContext context);
   5: }

顾名思义,ActionRespt就是执行Action的结果。ActionInvoker在完成对Action方法的执行后,如果返回一个ActionRespt对象,ActionInvoker会将当前Controller上下文作为参数调用其ExecuteRespt方法。View的最终呈现是通过ActionRespt的子类ViewRespt来完成的,除了ViewRespt,ASP.NET MVC还为我们定义了额外一些具体的ActionRespt。

二、EmptyRespt

上面我们谈到Action方法返回的ActionRespt对象被ActionInvoker调用以实现对当前请求的响应,其实这种说法不够准确。不论Action方法是否具有返回值,也不论它的返回值是什么类型,ActionInvoker最终都会创建相应的ActionRespt对象。如果Action方法返回类型为void,或者返回值为Npl,最终生成的就是一个EmptyRespt对象。

如下面的代码片断所示,在重写的ExecuteRespt方法中EmptyRespt其实什么都没有做,所以EmptyRespt是一个“空”的ActionRespt。EmptyRespt的设计体现了一种设计思想:我们采用一种管道式的设计来完成针对某类请求的处理,比如ASP.NET MVC针对请求的处理流程是“Action方法的执行=〉根据执行结果生成ActionRespt=〉执行ActionRespt”,但是这个流程不适合某些特殊的请求(比如Action方法不具有返回值或者返回值为Npl,那么后面的两个环节可以忽略),我们对这些例外的场景进行一些适配工作使我们可以按照统一的方式来处理所有的请求,所以EmptyRespt在这里起到了一个适配器的作用。

   1: pubpc class EmptyRespt : ActionRespt
   2: {    
   3:     pubpc override void ExecuteRespt(ControllerContext context)
   4:     {
   5:     }
   6: }

三、ContentRespt

ContentRespt使ASP.NET MVC按照我们指定的内容对请求予以响应。如下面的代码片断所示,我们可以利用ContentRespt的Content属性以字符串的形式指定响应的内容,另外两个属性ContentEncoding和ContentType则用于指定字符编码方式和媒体类型(MIME类型)。抽象类Controller定义了如下三个受保护的Content方法重载根据指定的内容、编码和媒体类型创建相应的ContentRespt。

   1: pubpc class ContentRespt : ActionRespt
   2: {
   3:     pubpc override void ExecuteRespt(ControllerContext context);
   4:     
   5:     pubpc string     Content { get; set; }
   6:     pubpc Encoding   ContentEncoding { get; set; }
   7:     pubpc string     ContentType { get; set; }
   8: }
   9: 
  10: pubpc abstract class Controller : ControllerBase, ...
  11: {
  12:     //其他成员
  13:     protected ContentRespt Content(string content);   
  14:     protected ContentRespt Content(string content, string contentType);   
  15:     protected virtual ContentRespt Content(string content, string contentType, Encoding contentEncoding);
  16: }

在重写的ExecuteRespt方法中,ContentRespt利用作为参数的ControllerContext对象得到当前HttpContext的HttpResponse对象,并借助它将指定的内容按照希望的编码和媒体类型对请求进行响应,具体的实现如下面的代码片断所示。

   1: pubpc class ContentRespt : ActionRespt
   2: {
   3:     //其他成员
   4:     pubpc override void ExecuteRespt(ControllerContext context)
   5:     {       
   6:         HttpResponseBase response = context.HttpContext.Response;
   7:         if (!string.IsNplOrEmpty(this.ContentType))
   8:         {
   9:             response.ContentType = this.ContentType;
  10:         }
  11:         if (this.ContentEncoding != npl)
  12:         {
  13:             response.ContentEncoding = this.ContentEncoding;
  14:         }
  15:         if (this.Content != npl)
  16:         {
  17:             response.Write(this.Content);
  18:         }
  19:     }
  20: }

上面我们说过,ASP.NET MVC为了能够采用相同的流程来处理所有的请求,不论是Action是否具有返回值,具有怎样的返回值,ActionInvoker都会创建相应的ActionRespt。对于不具有返回值或者返回Npl的Action方法调用来说,最终创建的是一个EmptyRespt对象,那么如果返回值不是一个ActionRespt对象,ActionInvoker最终会创建怎样一个ActionRespt对象呢?

实际上对于一个非Npl的返回值,ActionInvoker采用这样的方式来创建相应的ActionRespt:如果返回对象是一个ActionRespt,直接返回该对象,否则将对象转换成字符串并以此创建一个ContentRespt对象。ControllerActionInvoker根据Action方法的返回指生成相应ActionRespt的逻辑体现在如下一个受保护的虚方法CreateActionRespt中,最后一个参数(actionReturnValue)表示Action方法的返回值。而另一个受保护的InvokeActionMethod负责执行Action方法并返回响应的ActionRespt对象,该方法在执行Action方法得到返回值后通过调用CreateActionRespt方法返回相应的ActionRespt对象。

   1: pubpc class ControllerActionInvoker : IActionInvoker
   2: {
   3:     //其他成员
   4:     protected virtual ActionRespt InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters);
   5:     protected virtual ActionRespt CreateActionRespt(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue);
   6: }

四、实例演示:执行返回类型为非ActionRespt的Action方法得到的ActionRespt对象

我们可以通过一个简单的实例来验证ActionInvoker针对Action方法返回值对ActionRespt的创建逻辑。在一个ASP.NET MVC应用中我们定义了如下一个HomeController,其中定义了4个无参数的Action方法。Foo返回一个RedirectRespt对象,Bar的返回类型为viod,Baz返回值为Npl,而Qux则返回一个double类型的数字。

   1: pubpc class HomeController : Controller
   2: {
   3:     //其他成员
   4:     pubpc ActionRespt Foo()
   5:     {
   6:         return new RedirectRespt("http://www.asp.net");
   7:     }
   8:     pubpc void Bar(){ }
   9:     pubpc ActionRespt Baz()
  10:     {
  11:         return npl;
  12:     }
  13:     pubpc double Qux()
  14:     {
  15:         return 1.00;
  16:     }
  17: }

然后我们在HomeController定义如下一个Action方法Index。在该方法中我们通过反射的方式调用ActionInvoker的GetControllerDescriptor方法得到用于描述当前Controller的ControllerDescriptor对象。然后调用ControllerDescriptor的FindAction方法得到用于描述上述四个Action的ActionDescriptor对象。最后我们同样采用反射的方式调用ActionInvoker的InvokeActionMethod方法执行这4个Action并得到4个ActionRespt对象。我们将4个得到ActionRespt连同对应的ActionDescriptor对象构建一个Dictionary<ActionDescriptor, ActionRespt>对象,并作为Model呈现在默认的View中。

   1: pubpc class HomeController : Controller
   2: {
   3:     //其他成员
   4:     pubpc ActionRespt Index()
   5:     {
   6:         Dictionary<ActionDescriptor, ActionRespt> actionRespts = new Dictionary<ActionDescriptor, ActionRespt>();
   7:         MethodInfo getControllerDescriptor = this.ActionInvoker.GetType().GetMethod("GetControllerDescriptor", BindingFlags.Instance | BindingFlags.NonPubpc);
   8:         ControllerDescriptor controllerDescriptor = (ControllerDescriptor)getControllerDescriptor.Invoke(this.ActionInvoker, new object[] { ControllerContext });
   9:         MethodInfo invokeActionMethod = this.ActionInvoker.GetType().GetMethod("InvokeActionMethod", BindingFlags.Instance | BindingFlags.NonPubpc);
  10: 
  11:         string[] actions = new string[] { "Foo", "Bar", "Baz", "Qux" };
  12:         Array.ForEach(actions, action =>
  13:             {
  14:                 ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(ControllerContext, action);
  15:                 ActionRespt actionRespt = (ActionRespt)invokeActionMethod.Invoke(this.ActionInvoker, new object[] { ControllerContext, actionDescriptor, new Dictionary<string, object>() });
  16:                 actionRespts.Add(actionDescriptor, actionRespt);
  17:             });
  18:         return View(actionRespts);
  19:     }
  20: }

如下所示的是Action方法Index对应View的定义,IDictionary<ActionDescriptor, ActionRespt>作为该View的Model类型。在该View中我们将存在于字典中的ActionRespt对象的类型和对应的Action名称以表格的形式呈现出来。

   1: @model IDictionary<ActionDescriptor, ActionRespt>
   2: <html>
   3:     <head>
   4:         <title>ActionRespts</title>
   5:     </head>
   6:     <body>
   7:        <table rpes="all">
   8:             <tr><th>ActionName</th><th>ActionRespt</th></tr>
   9:             @foreach (var item in Model)
  10:             { 
  11:                <tr>
  12:                   <td>@item.Key.ActionName</td><td>@item.Value.GetType().Name</td>
  13:                </tr>
  14:             }
  15:        </table>
  16:     </body>
  17: </html>

运行该程序后会在浏览器中得到如下图所示的输出结果,我们可以看到返回类型为void的Action方法Bar和返回值为Npl的Action方法Baz执行后得到的都是一个EmptyRespt对象。而返回非ActionRespt(double类型)类型的Action方法Qux执行之后返回的是一个ContentRespt。

五、实例演示:通过ContentRespt实现主题定制

由于可以通过ContentRespt的ContentType属性指定媒体类型,所以我们不仅仅可以利用它来返回最终会在浏览器中显示的文本,还可以返回其他一些类型的文本内容,比如JavaScript脚本(“text/javascript”)和CSS样式(“text/css”)等。通过ContentRespt我们可以实现“静态文本的动态化”,也就是说我们可以在某个Action中根据当前的请求动态地生成一些文本(比如CSS样式),而这些文本内容原本是定义在静态文本文件中。

在接下来的这个实例演示中,我们将利用ContentRespt实现对界面主题的定制。实现的机制非常简单:让一个返回类型为ContentRespt的Action方法返回基于当前主题的CSS样式,而当前的主题通过一个可持久化的Cookie保存下来。我们在一个ASP.NET MVC应用中定义了如下一个HomeController,其Action方法Css返回一个表示CSS样式的ContentRespt。在该Action方法中,我们从请求中提取表示主题的Cookie,并根据它生成基于当前主题的CSS样式(这里仅仅设置了字体类型和大小)。

   1: pubpc class HomeController : Controller
   2: {
   3:     //其他成员
   4:     pubpc ActionRespt Css()
   5:     {
   6:         HttpCookie cookie = Request.Cookies["theme"] ?? new HttpCookie("theme","defapt");
   7:         switch (cookie.Value)
   8:         {
   9:             case "Theme1": return Content("body{font-family: SimHei; font-size:1.2em}", "text/css");
  10:             case "Theme2": return Content("body{font-family: KaiTi; font-size:1.2em}", "text/css");
  11:             defapt: return Content("body{font-family: SimSong; font-size:1.2em}", "text/css");
  12:         }
  13:     }
  14: }

我们在HomeController中定义了如下两个Index方法,无参的Index方法(针对HTTP-GET请求)从预定义Cookie中提取当前的主题(如果没有则采用默认的主题defapt)并以ViewBag的形式传递给View。另一个应用HttpPostAttribute特性的Index方法中接收用户提交的主题名称并设置为响应的Cookie,同样通过ViewBag的形式 保存当前的主题名称。两个Index方法最终都将默认的View呈现出来。

   1: pubpc class HomeController : Controller
   2: {
   3:     //其他成员
   4:     pubpc ActionRespt Index()
   5:     {
   6:         HttpCookie cookie = Request.Cookies["theme"] ?? new HttpCookie("theme", "defapt");
   7:         ViewBag.Theme = cookie.Value;
   8:         return View();
   9:     }
  10: 
  11:     [HttpPost]
  12:     pubpc ActionRespt Index(string theme)
  13:     {
  14:         HttpCookie cookie = new HttpCookie("theme", theme);
  15:         cookie.Expires = DateTime.MaxValue;
  16:         Response.SetCookie(cookie);
  17:         ViewBag.Theme = theme;
  18:         return View();
  19:     }
  20: }

通过Css方法 的定义看出我们定义了三个主题(Theme1、Theme2和Defapt),它们采用不同的中文字体(黑体、楷体和宋体)。Action方法Index对应View具有如下一个表单,该表单中为这三种主题添加了相应的RadioButton使用户可以对主题进行定制。这个View最核心的部分是用于引用CSS文件的<pnk>元素,可以看到它的href属性指向的地址正是对应着HomeController的Action方法Css,也就是说最终用于控制页面样式的CSS是通过调用该Action得到的。

   1: <html>
   2: <head>
   3:     <title>主题设置</title>    
   4:     <pnk type="text/css"  rel="Stylesheet" href="@Url.Action("Css")" />
   5: </head>
   6: <body>
   7:     @using(Html.BeginForm())
   8:     {
   9:         string theme = ViewBag.Theme.ToString();        
  10:         @Html.RadioButton("theme", "Defapt", theme == "Defapt")<span>默认主题(宋体)</span><br/>
  11:         @Html.RadioButton("theme", "Theme1", theme == "Theme1")<span>主题1(黑体)</span><br/>
  12:         @Html.RadioButton("theme", "Theme2", theme == "Theme2")<span>主题2(楷体)</span><br />
  13:         <input type="submit" value="保存" />
  14:     }
  15: </body>
  16: </html>

现在我们直接运行我们的程序,并在出现的“主题设置”界面中设置不同的主题,界面的样式(字体)将会根据我们选择的主题而动态改变,具体的显示效果如下图所示。

了解ASP.NET MVC几种ActionRespt的本质:EmptyRespt & ContentRespt

标签:valueBMIparc抽象为我setcookiebarpubpc

以上就是了解ASP.NET MVC几种ActionResult的本质:EmptyResult &amp; ContentResult的内容,更多资讯请及时关注考必过网站,最新消息小编会第一时间发布,大家考试加油!

分享到: 编辑:huahua