了解ASP.NET MVC幾種ActionResult的本質:JavaScriptResult & JsonResult
在之前的兩篇文章(《EmptyResult & ContentResult》和《FileResult》)我們剖析了EmptyResult、ContentResult和FileResult這三種ActionResult是如何將Action執行的結果響應給客戶端的。本篇文章著重介紹在進行Ajax調用中經常使用的兩個ActionResult,即JavaScriptResult和JsonResult。[本文已經同步到《How ASP.NET MVC Works?》中]
目錄
一、JavaScriptResult
二、實例演示:通過JavaScriptResult返回字段在客戶端自動執行的JavaScript
三、JsonResult
JavaScriptResult使我們可以在服務端動態地生成一段JavaScript腳本,並以此作為請求的響應,而這段腳本會在客戶端被執行。其實JavaScriptResult的實現非常簡單,它僅僅是將表示JavaScript腳本的字符串通過當前的HttpResponse響應給請求的客戶端而已。如下麵的代碼片斷所示,JavaScriptResult的屬性Script表示響應的JavaScript腳本,而用於響應JavaScript腳本的ExecuteResult方法除了將腳本內容寫入當前HttpResponse之外,還會將響應的媒體類型設置為“”(不是“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的方式來提示訂單處理結果的消息(成功處理或者庫存不足),很顯然這段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>
運行我們的程序後,一個包含三個商品的購物車信息會被呈現出來,當我們輸入相應的訂購數量並點擊“提交訂單”後,訂單處理結果消息會彈出來。下圖所示的就是庫存不足的情況下顯示的消息。
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
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-25 17:03:44
上一篇:
了解ASP.NET MVC幾種ActionResult的本質:FileResult
下一篇:
雲效(原RDC)+ 容器服務完成持續集成
微芯片植入大腦可保存人們的大腦記憶內容
redis lua原理分析
大型網站架構不得不考慮的10個問題
java的cglib動態代理報java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter
如何保護個人信息安全? 代表建議:製定地方法規增強可操作性
bootstrap html5 java 整合redis 緩存 SSM 後台框架 rest接口 shiro druid maven
webbench 1.5 for linux 使用說明
python 虛擬開發環境搭建
報告|DDOS攻擊利潤可達95%
spring-cloud-sleuth+zipkin追蹤服務實現(一)