MVC 定义JsonpResult实现跨域请求
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获取到了数据,和上面的一样