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

在之前的两篇文章(《EmptyResult & ContentResult》和《FileResult》)我们剖析了EmptyResult、ContentResult和FileResult这三种ActionResult是如何将Action执行的结果响应给客户端的。本篇文章着重介绍在进行Ajax调用中经常使用的两个ActionResult,即JavaScriptResult和JsonResult。[本文已经同步到《How ASP.NET MVC Works?》中]

目录 一、JavaScriptResult 二、实例演示:通过JavaScriptResult返回字段在客户端自动执行的JavaScript 三、JsonResult

一、JavaScriptResult

JavaScriptResult使我们可以在服务端动态地生成一段JavaScript脚本,并以此作为请求的响应,而这段脚本会在客户端被执行。其实JavaScriptResult的实现非常简单,它仅仅是将表示JavaScript脚本的字符串通过当前的HttpResponse响应给请求的客户端而已。如下面的代码片断所示,JavaScriptResult的属性Script表示响应的JavaScript脚本,而用于响应JavaScript脚本的ExecuteResult方法除了将脚本内容写入当前HttpResponse之外,还会将响应的媒体类型设置为“application/x-javascript”(不是“text/javascript”)。

   1: public class JavaScriptResult : ActionResult
   2: {
   3:     public override void ExecuteResult(ControllerContext context)
   4:     {        
   5:         HttpResponseBase response = context.HttpContext.Response;
   6:         response.ContentType = "application/x-javascript";
   7:         response.Write(this.Script);
   8:     }
   9:     public string Script { get; set; }
  10: }
  11:  
  12: public abstract class Controller : ControllerBase, ...
  13: {
  14:     //其他成员
  15:     protected virtual JavaScriptResult JavaScript(string script);
  16: }

抽象类Controller中定义了如上一个JavaScript方法根据指定的脚本字符串创建一个JavaScriptResult。实际上我们完全可以通过ContentResult来实现与JavaScriptResult一样的脚本响应功能,下面的两段程序是等效的。大部分浏览器会将媒体类型“application/x-javascript”等同于“text/javascript”,所以在通过ContentResult进行脚本响应时将媒体类型设置为“text/javascript”可以起到相同的效果。返回类型为JavaScriptResult的Action方法一般用于处理Ajax请求。

   1: //JavaScriptResult:
   2: public class FooController : Controller
   3: {
   4:     public ActionResult JavaScript()
   5:     {
   6:         return JavaScript("alert('Hello World!');");
   7:     }
   8: }
   9:  
  10: //ContentResult:
  11: public class FooController : Controller
  12: {
  13:     public ActionResult JavaScript()
  14:     {
  15:         return Content("alert('Hello World!');", "application/x-javascript");
  16:     }
  17: }

二、实例演示:通过JavaScriptResult返回字段在客户端自动执行的JavaScript

我们照例演示一个通过JavaScriptResult进行脚本响应的例子。我们演示一个在线购物的场景:用于完成了商品选购之后提交订单,服务端在处理订单的时候需要确认订购的商品是否超出了对应的库存量,如果存量充裕则正常处理该订单,否则提示库存不足,并将商品实时库存量显示给用户让他修正相应商品的购买量。我们利用JavaScript的方式来提示订单处理结果的消息(成功处理或者库存不足),很显然这段JavaScript应该是动态的(库存量是动态的)。

在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中定义一个ShoppingCart类表示购物车。如下面的代码片断所示,ShoppingCart是表示购物车商品项ShoppingCartItem对象的列表,而ShoppingCartItem的三个属性(Id、Name和Quantity)分别表示商品ID、名称和订购数量。

   1: public class ShoppingCart : List<ShoppingCartItem>
   2: {}
   3:  
   4: public class ShoppingCartItem
   5: {
   6:     public string     Id { get; set; }
   7:     public string     Name { get; set; }
   8:     public int        Quantity { get; set; }
   9: }

然后我们创建如下一个HomeController。我们在默认的Action方法Index中创建一个包含三个商品的ShoppingCart对象,并将其作为Model呈现在对应的View中。Action方法ProcessOrder用于处理提交的购买订单,如果订购商品的数量没有超过库存量(通过一个静态字典字段stock表示),则通过调用alert函数提示“购物订单成功处理”,否则提示“库存不足”,并将相应商品当前库存量显示出来。

   1: public class HomeController : Controller
   2: {
   3:     private static Dictionary<string, int> stock = new Dictionary<string, int>();
   4:     static HomeController()
   5:     {
   6:         stock.Add("001", 20);
   7:         stock.Add("002", 30);
   8:         stock.Add("003", 40);
   9:     }
  10:     public ActionResult Index()
  11:     {
  12:         ShoppingCart cart = new ShoppingCart();
  13:         cart.Add(new ShoppingCartItem { Id = "001", Quantity=1, Name = "商品A" });
  14:         cart.Add(new ShoppingCartItem { Id = "002", Quantity = 1, Name = "商品B" });
  15:         cart.Add(new ShoppingCartItem { Id = "003", Quantity = 1, Name = "商品C" });
  16:         return View(cart);
  17:     }
  18:         
  19:     public ActionResult ProcessOrder(ShoppingCart cart)
  20:     {
  21:         StringBuilder sb = new StringBuilder();
  22:         foreach (var cartItem in cart)
  23:         {
  24:             if (!CheckStock(cartItem.Id, cartItem.Quantity))
  25:             {
  26:                 sb.Append(string.Format("{0}: {1};", cartItem.Name,stock[cartItem.Id]));
  27:             }
  28:         }
  29:         if(string.IsNullOrEmpty(sb.ToString()))
  30:         {
  31:             return Content("alert('购物订单成功处理!');", "text/javascript");
  32:         }
  33:         string script = string.Format("alert('库存不足! ({0})');", sb.ToString().TrimEnd(';'));
  34:         return JavaScript(script); 
  35:     }
  36:  
  37:     private bool CheckStock(string id, int quantity)
  38:     {
  39:         return stock[id] >= quantity;
  40:     }
  41: }

如下所示的是Action方法Index对应的View的定义,这是一个Model类型为ShoppingCart的强类型View。在一个以Ajax请求提交的表单(表单的Action属性对应着上面定义的Action方法ProcessOrder)中显示了购物车中的商品和数量,用于可以修改订购数量并通过点击“提交订单”按钮以Ajax请求的方式提交订单。

   1: @model ShoppingCart
   2: <html>
   3:     <head>
   4:         <title>用户登录</title>
   5:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.6.2.js")"></script>   1:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")"></script>        
   6:     </head>
   7:     <body>
   8:         @using (Ajax.BeginForm("ProcessOrder", new AjaxOptions()))
   9:         {
  10:             for (int i = 0; i < Model.Count; i++)
  11:             {         
  12:                 <div>
  13:                     @Html.HiddenFor(m=>m[i].Id)
  14:                     @Html.HiddenFor(m => m[i].Name)
  15:  
  16:                     @Html.DisplayFor(m => m[i].Name):
  17:                     @Html.EditorFor(m => m[i].Quantity)
  18:                 </div>
  19:             }
  20:             <input type="submit" value="提交订单" />
  21:         }
  22:     </body>
  23: </html>

运行我们的程序后,一个包含三个商品的购物车信息会被呈现出来,当我们输入相应的订购数量并点击“提交订单”后,订单处理结果消息会弹出来。下图所示的就是库存不足的情况下显示的消息。

三、JsonResult

JavaScript已经在Web应用中得到广泛的应用,而JSON则成了标准的数据格式。但是对于后台程序来说,数据却是通过一个基于某种CLR类型的对象来承载,当客户端调用某个Action方法并希望以JSON的格式返回请求的数据时,ASP.NET MVC需要有一种机制将CLR对象转换成JSON格式予以响应,而这可以通过JsonResult来解决。如下面的代码片断所示,JsonResult具有一个object类型的属性Data表示需要被转换成JSON格式的数据对象。属性ContentEncoding和ContentType表示为当前响应设置的编码方式和媒体类型,默认采用的媒体类型为“application/json”。

   1: public class JsonResult : ActionResult
   2: {    
   3:     public override void ExecuteResult(ControllerContext context);
   4:  
   5:     public object                 Data { get; set; }  
   6:     public Encoding               ContentEncoding { get; set; }
   7:     public string                 ContentType { get; set; }    
   8:     public JsonRequestBehavior    JsonRequestBehavior { get; set; }    
   9:     public int?                   MaxJsonLength { get; set; }
  10:     public int?                   RecursionLimit { get; set; }
  11: }
  12:  
  13: public enum JsonRequestBehavior
  14: {
  15:     AllowGet,
  16:     DenyGet
  17: }

出于对安全的考虑,JsonResult在默认的情况下不能作为对HTTP-GET请求的响应,在这种情况下并会直接抛出一个InvalidOperationException异常。我们可以通过它的JsonRequestBehavior属性开启JsonResult对HTTP-GET请求的支持。该属性类型为JsonRequestBehavior枚举,两个枚举项AllowGet和DenyGet分别表示允许/拒绝支持对HTTP-GET请求的响应。JsonResult的JsonRequestBehavior属性在初始化的时候被设置为DenyGet,如果我们需要用创建的JsonResult来响应HTTP-GET请求,需要显式地将它的JsonRequestBehavior属性设置为AllowGet。

CLR对象到JSON格式字符串的序列化过程通过具有如下定义的序列化器JavaScriptSerializer来完成。JavaScriptSerializer的Serialize和Deserialize方法实现了CLR对象的序列化和对JSON字符串的反序列化。

   1: public class JavaScriptSerializer
   2: {
   3:     //其他成员 
   4:     public string Serialize(object obj); 
   5:     public object Deserialize(string input, Type targetType);  
   6:  
   7:     public int MaxJsonLength {  get; set; }
   8:     public int RecursionLimit { get; set; }
   9: }

JavaScriptSerializer具有两个整型的属性MaxJsonLength和RecursionLimit,它们对应着JsonResult的同名属性。MaxJsonLength限制了被反序列化和序列化生成的JSON字符串的长度,默认值位为2097152(0x200000,等同于 4 MB 的 Unicode 字符串数据)。RecursionLimit用于设置被序列化对象和反序列化生成对象结构的允许的层级数,默认值为100。定义在JsonResult的ExecuteResult方法中通过JavaScriptSerializer对数据对象的序列化,并将序列化生成的JSON字符串作为内容对请求进行响应,具体的逻辑基本上可以通过下面的代码片断来体现。

   1: public class JsonResult : ActionResult
   2: {
   3:     //其他成员
   4:     public override void ExecuteResult(ControllerContext context)
   5:     {
   6:         //确认是否用于响应HTTP-GET请求
   7:         if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Compare(context.HttpContext.Request.HttpMethod, "GET", true)  == 0)
   8:         {
   9:             throw new InvalidOperationException();
  10:         }
  11:  
  12:         HttpResponseBase response = context.HttpContext.Response
  13:         //设置媒体类型和编码方式
  14:         response.ContentType = string.IsNullOrEmpty(this.ContentType) ?"application/json" : this.ContentType;
  15:         if (this.ContentEncoding != null)
  16:         {
  17:             response.ContentEncoding = this.ContentEncoding;
  18:         }
  19:  
  20:         //创建JavaScriptSerializer将数据对象序列化成JSON字符串并写入当前HttpResponse
  21:         if (null == this.Data)return;
  22:         JavaScriptSerializer serializer = new JavaScriptSerializer()
  23:         {
  24:             MaxJsonLength = this.MaxJsonLength.HasValue ? this.MaxJsonLength.Value : 0x200000,
  25:             RecursionLimit = this.RecursionLimit.HasValue ? this.RecursionLimit.Value : 100
  26:         };
  27:         response.Write(serializer.Serialize(this.Data));        
  28:     }
  29: }

在抽象类Controller同样定义如下一系列的Json方法用于根据指定的数据对象、编码方式以及JsonRequestBehavior来创相应的JsonResult。

   1: public abstract class Controller : ControllerBase,...
   2: {
   3:     //其他成员
   4:     protected internal JsonResult Json(object data);
   5:     protected internal JsonResult Json(object data, string contentType);
   6:     protected internal JsonResult Json(object data, JsonRequestBehavior behavior);
   7:     protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding);
   8:     protected internal JsonResult Json(object data, string contentType, JsonRequestBehavior behavior);
   9:     protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior);
  10: }

了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult 了解ASP.NET MVC几种ActionResult的本质:FileResult 了解ASP.NET MVC几种ActionResult的本质:JavaScriptResult & JsonResult 了解ASP.NET MVC几种ActionResult的本质:HttpStatusCodeResult & RedirectResult/RedirectToRouteResult

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏james大数据架构

Android实现TCP断点上传,后台C#服务实现接收

终端实现大文件上传一直都是比较难的技术,其中涉及到后端与前端的交互,稳定性和流量大小,而且实现原理每个人都有自己的想法,后端主流用的比较多的是Http来实现,因...

3029
来自专栏Core Net

ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事

3495
来自专栏大内老A

WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效

本篇文章来源于几天前一个朋友向我咨询的问题。问题是这样的,他说他采用ASP.NET应用程序的方式对定义的WCF服务进行寄宿(Hosting),并使用配置的方式对...

1917
来自专栏偏前端工程师的驿站

asp.net 解码gb2312下urlencode后的字符串

公司网站前期的网页用了gb2312保存用户数据,而我负责的部分用的是utf8,今天恰好要获取前期录入的数据于是毫无悬念地出现乱码问题,经过一番网上的搜索还是找不...

2095
来自专栏c#开发者

Asp.net Dynamic Data之四定义字段的显示/编辑模板和自定义验证逻辑

Asp.net Dynamic Data之四定义字段的显示/编辑模板和自定义验证逻辑 Asp.net Dynamic data提供了一些字段模板有比如在Det...

3595
来自专栏大内老A

ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求

我们通过《以Web的形式发布静态文件》和《条件请求与区间请求》中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为Static...

3255
来自专栏cnblogs

简单实现 C# 与 Javascript的兼容

本文章介绍下自己这刚实现的一个c#与js交互的插件。需求来源于一次与朋友的讨论。主要对话如下: 朋友:最近我想模拟一些数据,来测试我现在写的接口,但手工编写这些...

26210
来自专栏大内老A

WCF技术剖析之十七:消息(Message)详解(下篇)

《WCF技术剖析(卷1)》自出版近20天以来,得到了园子里的朋友和广大WCF爱好者的一致好评,并被卓越网计算机书店作为首页推荐,在这里对大家的支持表示感谢。同时...

2305
来自专栏有趣的django

32.Django form组件

Form组件  Django的Form主要具有一下几大功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面...

3289
来自专栏智能大石头

实体处理模块IEntityModule

在2015年7月16日,XCode新增了实体处理模块IEntityModule,用于拦截实体对象添删改操作。

1160

扫码关注云+社区

领取腾讯云代金券