一、Jsonp简介:由于浏览器基于安全有同源策略(同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性)机制,所以前端无法使用Ajax来获取来获取其他域名下返回的数据,而Jsonp可以实现跨域访问。
Jsonp是基于<script>标签不受同源策略限制,可以加载任意地方的JavaScript文件来实现的。Jsonp实现理念就是,和服务端约定好一个函数名,当请求文件时,服务端返回一段JavaScript。这段JavaScript调用了约定好的函数,并且将数据当做参数传入。
二、简单Demo
1、在visual studio新建项目JsonpDemo和项目OtherDomain,项目结构如下
2、在JsonpDemo项目中新增HomeController及其视图,视图中代码如下:
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index</title> 11 </head> 12 <body> 13 <div> 14 <input type="button" id="getJsonpByHand" value="get jsonp by hand" /> 15 </div> 16 <script type="text/javascript" > 17 $("#getJsonpByHand").click(function () { 18 //55157位OtherDomain项目启动后网站的端口 19 CreateScript("http://localhost:55157/home/somejsonp?"); 20 }) 21 function CreateScript(src) { 22 $("<script><//script>").attr("src", src).appendTo("body") 23 } 24 //myCallBack就是与后端约定好的函数 25 function myCallBack(data) { 26 console.log(data); 27 } 28 </script> 29 </body> 30 </html>
3、在OtherDomain项目中新增HomeController及其视图,HomeController中代码如下:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace OtherDomain.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } public ActionResult SomeJsonp() { return new ContentResult() { //myCallBack为服务端与前端约定好的函数 Content = "myCallBack(" + JsonConvert.SerializeObject(new { Name = "Tom", Age = 23 }) + ")", ContentType = "text/html" }; } } }
4、启动两个项目,点击JsonpDemo项目页面中的按钮,谷歌浏览器控制台下看到
三、对上面Demo做一下简单优化,可以通过前端来指定与服务器约定的函数
1、修改JsonpDemo项目index.cshtml页面中,按钮点击事件下CreateScript方法传入参数为:http://localhost:55157/home/somejsonp?callback=myCallBack 也就是加上一个URL参数
2、修改OtherDomain项目下HomeController.cs中SomeJsonp方法如下:
public ActionResult SomeJsonp() { string func = Request.Params["callback"]; return new ContentResult() { Content = func+"(" + JsonConvert.SerializeObject(new { Name = "Tom", Age = 23 }) + ")", ContentType = "text/html" }; }
四、通过jQuery使用Jsonp实现跨域请求
1、继续沿用上面两个项目,修改JsonpDemo项目index.cshtml页面代码,新增一个按钮,并实现其click事件相关代码,如下:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> <script src="~/Scripts/jquery-1.8.2.min.js"></script> </head> <body> <div> <input type="button" id="getJsonpByHand" value="get jsonp by hand" /> <input type="button" id="getJsonpByJquery" value="get jsonp by jquery" /> </div> <script type="text/javascript" > $("#getJsonpByHand").click(function () { CreateScript("http://localhost:55157/home/somejsonp?callback=myCallBack"); }) function CreateScript(src) { $("<script><//script>").attr("src", src).appendTo("body") } $("#getJsonpByJquery").click(function () { $.ajax({ // url: 'http://localhost:55157/home/somejsonp?callback=myCallBack', url: 'http://localhost:55157/home/somejsonp', dataType: "jsonp", jsonp: "callback", success: function (data) { console.log(data) var a=123; var b=2; var c=35; } }) }) //function myCallBack(data) { // console.log(data); //} </script> </body> </html>
注意上面ajax参数dataType为jsonp,jsonp为callback
2、OtherDomain下HomeController.cs文件SomeJsonp代码和第三个里面代码一样,如下:
public ActionResult SomeJsonp() { string func = Request.Params["callback"]; return new ContentResult() { Content = func + "(" + JsonConvert.SerializeObject(new { Name = "Tom", Age = 23 }) + ")", ContentType = "text/html" }; }
3、运行两个项目,点击“get jsonp by jquery”按钮,可以在谷歌浏览器控制台下看到同样结果
4、注意:如果前端代码传入callback参数,那么前端也要自己实现相应参数的函数。如果没有传入,其实jQuery自己会默认传入并自己实现。
五、扩展:在OtherDomain项目下,简单自定义JsonpResult类,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace OtherDomain 8 { 9 public class JsonpResult : JsonResult 10 { 11 private const string CALLBACK_QUERYSTRING = "callback"; 12 //private const string CALLBACK_CONTENTTYPE = "application/x-javascript"; 13 private const string CALLBACK_CONTENTTYPE = "text/javascript"; 14 15 public override void ExecuteResult(ControllerContext controllerContext) 16 { 17 if (controllerContext != null) 18 { 19 var request = controllerContext.HttpContext.Request; 20 object callback = request[CALLBACK_QUERYSTRING]; 21 if (callback == null) 22 { 23 controllerContext.RouteData.Values.TryGetValue(CALLBACK_QUERYSTRING, out callback); 24 } 25 26 var hasCallback = !string.IsNullOrWhiteSpace(callback == null ? "" : callback as string); 27 if (hasCallback) 28 { 29 SetContentTypeIfEmpty(); 30 var response = controllerContext.HttpContext.Response; 31 response.Write(callback); 32 response.Write("("); 33 base.ExecuteResult(controllerContext); 34 response.Write(")"); 35 } 36 else 37 { 38 base.ExecuteResult(controllerContext); 39 } 40 } 41 } 42 43 private void SetContentTypeIfEmpty() 44 { 45 if (string.IsNullOrWhiteSpace(base.ContentType)) 46 { 47 base.ContentType = CALLBACK_CONTENTTYPE; 48 } 49 } 50 } 51 52 public static class ContollerExtensions 53 { 54 public static JsonpResult Jsonp(this Controller controller, object data, JsonRequestBehavior behavior = JsonRequestBehavior.DenyGet) 55 { 56 JsonpResult result = new JsonpResult(); 57 result.Data = data; 58 result.JsonRequestBehavior = behavior; 59 return result; 60 } 61 } 62 }
如果想要很好的结合到controller中,需要写如上ContollerExtensions的Controller的扩展方法
在OtherDomain项目下,需要修改SomeJsonp方法:
public ActionResult SomeJsonp() { return this.Jsonp(new { Name= "Tom", Age=23 }, JsonRequestBehavior.AllowGet); }