照理来讲本节也应该讲Web API原理,目前已经探讨完了比较底层的Web API消息处理管道以及Web Host寄宿管道,接下来应该要触及控制器、Action方法,以及过滤器、模型绑定等等,想一想也是心痛不已,水太深了,摸索原理关键是太枯燥和乏味了,可是呢,从情感上仍是挺乐意去摸索原理,而情绪上不太乐意去探究原理,因而乎,本文就由此诞生了,借此文缓解下枯燥的心情和压抑的情绪。后续继续摸索原理。javascript
接下来咱们要讲的就是利用JSONP和利用Cors这两种方式来实现跨域,请看下文。。。。。html
Web API并无提供JSONP Formatter,可是这并不能影响咱们前进的脚步,咱们能够自定义Formatter来实现JSONP功能。既然是利用JSONP跨域,那么就得简单介绍下JSONP。java
浏览器都是基于同源策略,使其脚本不能跨站点来得到服务器端数据,可是办法老是人想出来的,这个时候就须要JSONP了,固然也能够用别的办法实现,JSONP是一种能实现让基于JavaScript的客户端程序绕过跨站点脚本的限制从而从非当前的服务器上来得到数据的方式。默认状况下,应用程序利用Ajax是不容许访问远程跨域,可是咱们能够利用<script>标签加载JSONP来实现这种跨站点限制。这也不失为一种好的解决方案。JSONP的工做原理是当JSON数据返回时经过组合JSON数据,并将其包裹到一个函数中进行调用,利用JQuery更能很好的去实现这点。web
假若有这样以下的一个URL:ajax
http://www.cnblogs.com/CreateMyself/WebAPI/xpy0928
但咱们利用Ajax发出GET请求来获取服务器端数据时那将是垂手可得,可是,可是,可是,重要的前提说三遍,前提是在相同域下,如果不一样的域下,利用Ajax来访问数据估计不是这么轻松了吧。可是,可是,可是,重要的话再说三遍,此时咱们就利用JSONP来实现跨域,此时将会变成以下请求模式:json
http://www.cnblogs.com/CreateMyself/WebAPI/xpy0928?callback=?
发出以下URL请求经过一个callback回调,这样获得的结果是和同一站点的结果是一致的,JQuery会反序列会这些数据并将其推入到函数中。api
它主要就是经过调用函数将返回的JSON数据进行包裹,相似于以下形式:跨域
Query7d59824917124eeb85e5872d0a4e7e5d([{"Id":"4836628","Name":"xpy0928"},{......}])
在JavaScript客户端发出请求后,当响应数据时,将其数据做为执行要调用函数的参数,并在其内部将JSON数据进行反序列化浏览器
下面咱们就原理来进行演示,请看以下代码:服务器
function JSONP(url, callback) { var id = "_" + "Query" + (new Date()).getTime(); //建立一个几乎惟一的id window[id] = function (result) { //建立一个全局回调处理函数 if (callback) callback(result); var getId = document.getElementById(id); //移除Script标签和id getId.parentNode.removeChild(getId); window[getId] = null; //调用函数后进行销毁 } url = url.replace("callback=?", "callback=" + id); var script = document.createElement("script"); //建立Script标签并执行window[id]函数 script.setAttribute("id", id); script.setAttribute("src", url); script.setAttribute("type", "text/javascript"); document.body.appendChild(script); }
简单进行调用则以下:
function JSONPFunction() { JSONP("http://localhost:23133/api/default?callback=?", function(jsonData){ //将返回的数据jsonData做为调用函数的参数 } };
以上是利用原生的JS实现,可是在JQuery中却对此进行了封装,以下:
$.getJSON("http://www.cnblogs.com/CreateMyself/WebAPI/xpy0928?callback=?",function(jsonData){ })
上述callback=?,对于callback中的?而言,JQuery会自动生成咱们上述手动建立的全局处理函数,并在调用完函数以后自动销毁,毫无疑问该回调函数就相似于JS中的代理对象,也就是所谓的临时代理函数,同时JQuery也会自动去检测该请求是不是跨域请求,若不是,则以普通Ajax进行请求,不然则以异步加载JS文件的形式来执行JSONP中的回调函数。
上述讲了JSONP原理和实现,那么结合Web API是如何实现的呢?咱们只能自定义Formatter来手动实现这个功能,既然是有关于JSON,那么天然是继承于 JsonMediaypeFormatter 了,代码以下:
自定义JsonpFormatter并继承于JsonMediaTypeFormatter:
public class JsonpFormatter : JsonMediaTypeFormatter { //当请求过来是带有text/javascript时处理JSONP请求 public JsonpFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript")); JsonpParameterName = "callback"; } //查找函数名 public string JsonpParameterName { get; set; } private string JsonpCallbackFunction; public override bool CanWriteType(Type type) { return true; } //重写此方法来捕获请求对象 public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType) { var formatter = new JsonpFormatter() { JsonpCallbackFunction = GetJsonCallbackFunction(request) }; //运用JSON.NET来序列化自定义 formatter.SerializerSettings.Converters.Add(new StringEnumConverter()); formatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; return formatter; } //重写此方法写入到流并返回 public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) { if (string.IsNullOrEmpty(JsonpCallbackFunction)) return base.WriteToStreamAsync(type, value, stream, content, transportContext); StreamWriter writer = null; try { writer = new StreamWriter(stream); writer.Write(JsonpCallbackFunction + "("); writer.Flush(); } catch (Exception ex) { try { if (writer != null) writer.Dispose(); } catch { } var tcs = new TaskCompletionSource<object>(); tcs.SetException(ex); return tcs.Task; } return base.WriteToStreamAsync(type, value, stream, content, transportContext) .ContinueWith(innerTask => { if (innerTask.Status == TaskStatus.RanToCompletion) { writer.Write(")"); writer.Flush(); } }, TaskContinuationOptions.ExecuteSynchronously) .ContinueWith(innerTask => { writer.Dispose(); return innerTask; }, TaskContinuationOptions.ExecuteSynchronously) .Unwrap(); } //从查询字符串中得到JSONP Callback回调函数 private string GetJsonCallbackFunction(HttpRequestMessage request) { if (request.Method != HttpMethod.Get) return null; var query = HttpUtility.ParseQueryString(request.RequestUri.Query); var queryVal = query[this.JsonpParameterName]; if (string.IsNullOrEmpty(queryVal)) return null; return queryVal; } }
此时只需将此自定义类在Web API配置文件中进行注册便可:
GlobalConfiguration .Configuration .Formatters .Insert(0, new JsonpFormatter());
给出后台测试数据:
public class Person { public string Name { get; set; } public int Age { get; set; } public string Gender { get; set; } } public IEnumerable<Person> GetAllPerson() { Person[] Person = new Person[] { new Person{ Name="xpy0928", Age =11, Gender="男"}, new Person{ Name="xpy0929", Age =12, Gender="女"}, new Person{ Name="xpy0930", Age =13, Gender="男"}, }; return Person; }
接下来就是进行验证了。调用上述前台所写的JSONP方法:
function getPerson() { JSONP("http://localhost:23133/api/default?callback=?", function (persons) { $.each(persons, function (index, person) { var html = "<ul>"; html += "<li>Name: " + person.Name + "</li>"; html += "<li>Age:" + person.Age + "</li>"; html += "<li>Gender: " + person.Gender + "</li>"; html += "</ul>"; $("#person").append($(html)); }); }); }; $(function () { $("#btn").click(function () { getPerson(); }); });
上述也可自行利用Ajax来请求,如下几项必不可少:
$.ajax({ type: "Get", url: "http://localhost:23133/api/default/?callback=?", dataType: "json", contentType: "application/json; charset=utf-8",
.......
})
点击加载数据:
<input type="button" value="获取数据" id="btn" /> <ul id="person"></ul>
既然是跨站点就开两个应用程序就得了呗,服务器端:localhost:23133,客户端:localhost:29199,走你,完事:
一切圆满结束,彷佛利用JSONP实现跨域是个不错的解决方案,可是有的人就问了,JSONP也有局限性啊,只能针对于Get请求不能用于POST请求啊,而且还须要手动去写这么操蛋的代码,有点使人发指,恩,是的,确实是个问题,你想到的同时我也替你想到了,请看下文!
使用Cors跨域配置是极其的简单,可是前提是你得经过NuGet下载程序包,搜索程序包【Microsoft.AspNet.WebApi.Cors】便可,如图:
下载完成后,有两种配置跨域的方式
在Web API配置文件中进行全局配置:
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
若你仅仅只是想某个控制器应用跨域也就是说实现局部控制器跨域,固然你也能够经过添加特性来实现这点:
[EnableCors(origins: "*", headers: "*", methods: "*")] public class HomeController : Controller { }
在被请求的服务器端的Web API配置文件中,进行全文配置,接下来发出POST请求以下:
$("#btn").click(function () { $.ajax({ type: "POST", url: "http://localhost:23133/api/Default/PostAllPerson", dataType: "json", contentType: "application/json; charset=utf-8", cache: false, success: function (persons) { $.each(persons, function (index, person) { var html = "<ul>"; html += "<li>Name: " + person.Name + "</li>"; html += "<li>Age:" + person.Age + "</li>"; html += "<li>Gender: " + person.Gender + "</li>"; html += "</ul>"; $("#person").append($(html)); }); } }); });
如咱们所指望的同样,测试经过:
在控制器上进行局部配置,并发出Get请求,修改以下:
[EnableCors(origins: "*", headers: "*", methods: "*")] public class DefaultController : ApiController { public IEnumerable<Person> GetAllPerson() {} }
发出请求以下:
$.ajax({ type: "Get", url: "http://localhost:23133/api/Default", dataType: "json", ........ })
咱们查看其请求报文头信息以及返回状态码便知是否成功,以下(如预期同样):
经测试利用Cors实现对于Get和POST请求都是来者不拒,都能很友好的返回响应的数据而且配置简单。固然Cors的功能远不止如此简单,更多详细信息,请参看【Cors-Origin For WebAPI】
利用JSONP能较好的实如今Web API上的跨域,可是有两个严重的缺陷,第一:只能是Get请求。第二:你得自定义实现JsonMediaTypeFormatter。在Cors未出世以前你没有更好的解决方案,你只能忍受,自从Cors出世,咱们再也不受请求的限制,再也不手动去实现,只需稍微配置便可达到咱们所需,因此利用Cors实现跨域将是不可替代的方案。