1:原理
在js中,XMLHttpRequest是不能請求不同域的數據,但是script標簽卻可以,所以可以用script標簽實現跨域請求。具體是定義一個函數,例如jsonp1234,請求不同域的url時帶上函數名,例如:http://otherdomain.com/index?callback=jsonp1234,然后服務端根據callback獲取這個函數名,然后傳入json字符串作為函數參數。
2:實現
http://localhost:62203/home/index頁面代碼如下
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> <script> function showMessage(result) { alert(result.name) } </script> <script src="http://localhost:16308/home/index?callback=showMessage" type="text/javascript"></script> </head> <body> <div> </div> </body> </html>
主要是這句
<script src="http://localhost:16308/home/index?callback=showMessage" type="text/javascript"></script>,
可以看到,訪問的是不同的站點,並且callback的參數值為showMessage,
http://localhost:16308/home/index代碼如下
public ActionResult Index() { string callback = this.Request["callback"]; string json="{\"name\":\"server\"}"; this.Response.Write(callback + "(" + json + ")"); return new EmptyResult(); }
根據callback獲取函數名,然后將json字符串作為函數參數。
訪問頁面http://localhost:62203/home/index,效果如下
可見,站點localhost:62203從站點localhost:16308獲取到了數據。
但是我們看服務端的實現,這也太不美觀,也比較麻煩。
public ActionResult Index() { string callback = this.Request["callback"]; string json="{\"name\":\"server\"}"; this.Response.Write(callback + "(" + json + ")"); return new EmptyResult(); }
我們想要的是調用一個方法,就能實現跨域了,那如何實現呢。看到Controller有個this.Json方法,類型是JsonResult,我們可以參考這個類。定義一個類JsonpResult,派生於JsonResult,在ExecuteResult方法根據callback獲取函數名,然后傳入json字符串作為函數參數。
public class JsonpResult : JsonResult { public static readonly string JsonpCallbackName = "callback"; public static readonly string CallbackApplicationType = "application/json"; public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) && String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException(); } var response = context.HttpContext.Response; if (!String.IsNullOrEmpty(ContentType)) response.ContentType = ContentType; else response.ContentType = CallbackApplicationType; if (ContentEncoding != null) response.ContentEncoding = this.ContentEncoding; if (Data != null) { String buffer; var request = context.HttpContext.Request; var serializer = new JavaScriptSerializer(); if (request[JsonpCallbackName] != null) buffer = String.Format("{0}({1})", request[JsonpCallbackName], serializer.Serialize(Data));//首先根據callback獲取獲取函數名,然后傳入json字符串作為函數參數 else buffer = serializer.Serialize(Data); response.Write(buffer); } } }
JsonpResult類有了,但是想在Controller這樣使用this.Jsonp,所以為Controller類定義一個擴展方法,
public static class ControllerExtension { public static JsonpResult Jsonp(this Controller controller, object data) { JsonpResult result = new JsonpResult() { Data = data, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; return result; } }
這是在Controller就可以直接使用this.Jsonp了,把跨域服務端的代碼改下
public ActionResult Index() { return this.Jsonp(new { name = "server JsonpResult" }); }
相比上面那個簡潔多了
再次打開http://localhost:62203/home/index
同樣,站點localhost:62203從站點localhost:16308獲取到了數據,和上面的一樣