javascript
Web API 实现JSONP或者安装配置Cors跨域
前言
照理來說本節也應該講Web API原理,目前已經探討完了比較底層的Web API消息處理管道以及Web Host寄宿管道,接下來應該要觸及控制器、Action方法,以及過濾器、模型綁定等等,想想也是心痛不已,水太深了,摸索原理關鍵是太枯燥和乏味了,但是呢,從情感上還是挺樂意去摸索原理,而情緒上不太樂意去探究原理,于是乎,本文就由此誕生了,借此文緩解下枯燥的心情和壓抑的情緒。后續繼續摸索原理。
接下來我們要講的就是利用JSONP和利用Cors這兩種方式來實現跨域,請看下文。。。。。
JSONP實現跨域
Web API并沒有提供JSONP ?Formatter,但是這并不能影響我們前進的腳步,我們可以自定義Formatter來實現JSONP功能。既然是利用JSONP跨域,那么就得簡單介紹下JSONP。
為什么需要JSONP?
瀏覽器都是基于同源策略,使其腳本不能跨站點來獲得服務器端數據,但是辦法總是人想出來的,這個時候就需要JSONP了,當然也可以用別的辦法實現,JSONP是一種能實現讓基于JavaScript的客戶端程序繞過跨站點腳本的限制從而從非當前的服務器上來獲得數據的方式。默認情況下,應用程序利用Ajax是不允許訪問遠程跨域,但是我們可以利用<script>標簽加載JSONP來實現這種跨站點限制。這也不失為一種好的解決方案。JSONP的工作原理是當JSON數據返回時通過組合JSON數據,并將其包裹到一個函數中進行調用,利用JQuery更能很好的去實現這點。
假如有這樣如下的一個URL:
http://www.cnblogs.com/CreateMyself/WebAPI/xpy0928但我們利用Ajax發出GET請求來獲取服務器端數據時那將是輕而易舉,但是,但是,但是,重要的前提說三遍,前提是在相同域下,若是不同的域下,利用Ajax來訪問數據估計不是這么輕松了吧。但是,但是,但是,重要的話再說三遍,此時我們就利用JSONP來實現跨域,此時將會變成如下請求模式:
http://www.cnblogs.com/CreateMyself/WebAPI/xpy0928?callback=?發出如下URL請求通過一個callback回調,這樣得到的結果是和同一站點的結果是一致的,JQuery會反序列會這些數據并將其推入到函數中。
JSONP數據是怎樣的?
它主要就是通過調用函數將返回的JSON數據進行包裹,類似于如下形式:
Query7d59824917124eeb85e5872d0a4e7e5d([{"Id":"123","Name":"xoy0928"},{......}])JSONP的工作原理是怎樣的呢?
在JavaScript客戶端發出請求后,當響應數據時,將其數據作為執行要調用函數的參數,并在其內部將JSON數據進行反序列化
下面我們就原理來進行演示,請看如下代碼:
function JSONP(url, callback) {var id = "_" + "Query" + (new Date()).getTime(); //創建一個幾乎唯一的idwindow[id] = function (result) { //創建一個全局回調處理函數if (callback)callback(result);var getId = document.getElementById(id); //移除Script標簽和idgetId.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作為調用函數的參數} };JSONP在Web API中如何實現呢??
上述講了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;}}
第二步
此時應將此自定義類進行注冊即可:
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 = "<li><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實現跨域
使用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 = "<li><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實現跨域將是不可替代的方案。
轉:http://www.cnblogs.com/CreateMyself/p/4836628.html
總結
以上是生活随笔為你收集整理的Web API 实现JSONP或者安装配置Cors跨域的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美团极速支付怎么关闭
- 下一篇: webapi同一个Controller多