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":"4836628","Name":"xpy0928"},{......}])

JSONP的工作原理是怎样的呢?

在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中如何实现呢? 

上述讲了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实现跨域

使用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实现跨域将是不可替代的方案。

 

时间: 2024-10-29 20:44:23

Web APi之手动实现JSONP或安装配置Cors跨域(七)的相关文章

使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【七】——实现资源的分页

原文:使用ASP.NET Web Api构建基于REST风格的服务实战系列教程[七]--实现资源的分页 系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html 前言 这篇文章我们将使用不同的方式实现手动分页(关于高端大气上档次的OData本文暂不涉及,但有可能会在系列的后期介绍,还没确定...),对于分页的结果,我们将采用2种不同的方式响应给客户端(1.将分页元数据封装在响应Body中2.在http响应报文头部添加分页信息). 众所周知,在服务器端一

通过扩展让ASP.NET Web API支持W3C的CORS规范

让ASP.NET Web API支持JSONP和W3C的CORS规范是解决"跨域资源共享"的两种途径,在<通过扩展让ASP.NET Web API支持JSONP>中我们实现了前者,并且在<W3C的CORS Specification>一文中我们对W3C的CORS规范进行了详细介绍,现在我们通过一个具体的实例来演示如何利用ASP.NET Web API具有的扩展点来实现针对CORS的支持. 目录 一.ActionFilter OR HttpMessageHandl

Web APi之消息处理管道(五)

前言 MVC有一套请求处理的机制,当然Web API也有自己的一套消息处理管道,该消息处理管道贯穿始终都是通过HttpMessageHandler来完成.我们知道请求信息存在 RequestMessage 中,而响应信息则存在 ResponseMessage 中,当请求信息进入到管道中,此时HttpMessageHandler会对此进行相应的处理,当执行到控制器上的方法时此时就会进行响应,生成的响应信息HttpResponseMessage就会逆向通过HttpMessageHandler依次进行

JSONP跨域的原理解析及其实现介绍

 JSONP跨域GET请求是一个常用的解决方案,下面我们来看一下JSONP跨域是如何实现的,并且探讨下JSONP跨域的原理 JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same-Origin Policy"(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容.    JavaScript这个安全策略在进行多if

[IT]JSONP跨域的原理解析

JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same-Origin Policy"(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript 只能访问与包含它的文档在同一域下的内容. JavaScript这个安全策略在进行多iframe或多窗口编程.以及Ajax编程时显得尤为重要.根据这个策略,在baidu.com下的页面中包含的JavaScript代码

AJAX跨域请求之JSONP获取JSON数据_AJAX相关

Asynchronous JavaScript and XML (Ajax ) 是驱动新一代 Web 站点(流行术语为 Web 2.0 站点)的关键技术.Ajax 允许在不干扰 Web 应用程序的显示和行为的情况下在后台进行数据检索.使用 XMLHttpRequest 函数获取数据,它是一种 API,允许客户端 JavaScript 通过 HTTP 连接到远程服务器.Ajax 也是许多 mashup 的驱动力,它可将来自多个地方的内容集成为单一 Web 应用程序. 不过,由于受到浏览器的限制,该

JSONP跨域的原理解析及其实现介绍_javascript技巧

JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same-Origin Policy"(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容. JavaScript这个安全策略在进行多iframe或多窗口编程.以及Ajax编程时显得尤为重要.根据这个策略,在baidu.com下的页面中包含的JavaScript代码,

深入分析JSONP跨域的原理_基础知识

JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same- Origin Policy"(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档 在同一域下的内容. JavaScript这个安全策略在进行多iframe或多窗口编程.以及Ajax编程时显得尤为重要.根据这个策略,在baidu.com下的 页面中包含的JavaScript

Hue安装配置实践

Hue是一个开源的Apache Hadoop UI系统,最早是由Cloudera Desktop演化而来,由Cloudera贡献给开源社区,它是基于Python Web框架Django实现的.通过使用Hue我们可以在浏览器端的Web控制台上与Hadoop集群进行交互来分析处理数据,例如操作HDFS上的数据,运行MapReduce Job等等.很早以前就听说过Hue的便利与强大,一直没能亲自尝试使用,下面先通过官网给出的特性,通过翻译原文简单了解一下Hue所支持的功能特性集合: 默认基于轻量级sq