博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
通过扩展让ASP.NET Web API支持JSONP
阅读量:6527 次
发布时间:2019-06-24

本文共 7132 字,大约阅读时间需要 23 分钟。

同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。同源策略以及跨域资源共享在大部分情况下针对的是Ajax请求。同源策略主要限制了通过XMLHttpRequest实现的Ajax请求,如果请求的是一个“异源”地址,浏览器将不允许读取返回的内容。JSONP是一种常用的解决跨域资源共享的解决方案,现在我们利用ASP.NET Web API自身的扩展性提供一种“通用”的JSONP实现方案。

一、JsonpMediaTypeFormatter

在《》,我们是在具体的Action方法中将返回的JSON对象“填充”到JavaScript回调函数中,现在我们通过自定义的MediaTypeFormatter来为JSONP提供一种更为通用的实现方式。

我们通过继承JsonMediaTypeFormatter定义了如下一个JsonpMediaTypeFormatter类型。它的只读属性Callback代表JavaScript回调函数名称,改属性在构造函数中指定。在重写的方法WriteToStreamAsync中,对于非JSONP调用(回调函数不存在),我们直接调用基类的同名方法对响应对象实施针对JSON的序列化,否则调用WriteToStream方法将对象序列化后的JSON字符串填充到JavaScript回调函数中。

1: public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
2: {
3:     public string Callback { get; private set; }
4: 
5:     public JsonpMediaTypeFormatter(string callback = null)
6:     {
7:         this.Callback = callback;
8:     }
9: 
10:     public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
11:     {
12:         if (string.IsNullOrEmpty(this.Callback))
13:         {
14:             return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
15:         }
16:         try
17:         {
18:             this.WriteToStream(type, value, writeStream, content);
19:             return Task.FromResult
(new AsyncVoid());
20:         }
21:         catch (Exception exception)
22:         {
23:             TaskCompletionSource
source = new TaskCompletionSource
();
24:             source.SetException(exception);
25:             return source.Task;
26:         }
27:     }
28: 
29:     private void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
30:     {
31:         JsonSerializer serializer = JsonSerializer.Create(this.SerializerSettings);
32:         using(StreamWriter streamWriter = new StreamWriter(writeStream, this.SupportedEncodings.First()))
33:         using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter) { CloseOutput = false })
35:         {
36:             jsonTextWriter.WriteRaw(this.Callback + "(");
37:             serializer.Serialize(jsonTextWriter, value);
38:             jsonTextWriter.WriteRaw(")");
39:         }
40:     }
41: 
42:     public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
43:     {
44:         if (request.Method != HttpMethod.Get)
45:         {
46:             return this;
47:         }
48:         string callback;
49:         if (request.GetQueryNameValuePairs().ToDictionary(pair => pair.Key,
50:              pair => pair.Value).TryGetValue("callback", out callback))
51:         {
52:             return new JsonpMediaTypeFormatter(callback);
53:         }
54:         return this;
55:     }
56: 
57:     [StructLayout(LayoutKind.Sequential, Size = 1)]
58:     private struct AsyncVoid
59:     {}
60: }

我们重写了GetPerRequestFormatterInstance方法,在默认情况下,当ASP.NET Web API采用内容协商机制选择出与当前请求相匹配的MediaTypeFormatter后,会调用此方法来创建真正用于序列化响应结果的MediaTypeFormatter对象。在重写的这个GetPerRequestFormatterInstance方法中,我们尝试从请求的URL中得到携带的JavaScript回调函数名称,即一个名为“callback”的查询字符串。如果回调函数名不存在,则直接返回自身,否则返回据此创建的JsonpMediaTypeFormatter对象。

二、将JsonpMediaTypeFormatter的应用到ASP.NET Web API中

接下来我们通过于一个简单的实例来演示同源策略针对跨域Ajax请求的限制。如图右图所示,我们利用Visual Studio在同一个解决方案中创建了两个Web应用。从项目名称可以看出,WebApi和MvcApp分别为ASP.NET Web API和MVC应用,后者是Web API的调用者。我们直接采用默认的IIS Express作为两个应用的宿主,并且固定了端口号:WebApi和MvcApp的端口号分别为“3721”和“9527”,所以指向两个应用的URI肯定不可能是同源的。

我们在WebApi应用中定义了如下一个继承自ApiController的ContactsController类型,它具有的唯一Action方法GetAllContacts返回一组联系人列表。

1: public class ContactsController : ApiController
2: {
3:     public IEnumerable
GetAllContacts()
4:     {
5:         Contact[] contacts = new Contact[]
6:         {
7:             new Contact{ Name="张三", PhoneNo="123", EmailAddress="zhangsan@gmail.com"},
8:             new Contact{ Name="李四", PhoneNo="456", EmailAddress="lisi@gmail.com"},
9:             new Contact{ Name="王五", PhoneNo="789", EmailAddress="wangwu@gmail.com"},
10:         };
11:         return contacts;
12:     }
13: }
14: 
15: public class Contact
16: {
17:     public string Name { get; set; }
18:     public string PhoneNo { get; set; }
19:     public string EmailAddress { get; set; }
20: }

现在我们在WebApi应用的Global.asax中利用如下的程序创建这个JsonpMediaTypeFormatter对象并添加当前注册的MediaTypeFormatter列表中。为了让它被优先选择,我们将这个JsonpMediaTypeFormatter对象放在此列表的最前端。

1: public class WebApiApplication : System.Web.HttpApplication
2: {
3:     protected void Application_Start()
4:     {
5:         GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter ());
6:         //其他操作
7:     }
8: }

接下来们在MvcApp应用中定义如下一个HomeController,默认的Action方法Index会将对应的View呈现出来。

1: public class HomeController : Controller
2: {
3:     public ActionResult Index()
4:     {
5:         return View();
6:     }
7: }

如下所示的是Action方法Index对应View的定义。我们的目的在于:当页面成功加载之后以Ajax请求的形式调用上面定义的Web API获取联系人列表,并将自呈现在页面上。如下面的代码片断所示,我们直接调用$.ajax方法并将dataType参数设置为“jsonp”。

1: 
2: 
3:     联系人列表
4:     
5: 
6: 
7:     
    8:     
    9:         $(function ()
    10:         {
    11:             $.ajax({
    12:                 Type       : "GET",
    13:                 url        : "http://localhost:3721/api/contacts",
    14:                 dataType   : "jsonp",
    15:                 success    : listContacts
    16:             });
    17:         });
    18: 
    19:         function listContacts(contacts) {
    20:             $.each(contacts, function (index, contact) {
    21:                 var html = "
    • ";
  • 22:                 html += "
  • Name: " + contact.Name + "
  • ";
    23:                 html += "
  • Phone No:" + contact.PhoneNo + "
  • ";
    24:                 html += "
  • Email Address: " + contact.EmailAddress + "
  • ";
    25:                 html += "";
    26:                 $("#contacts").append($(html));
    27:             });
    28:         }
    29:     
    30: 
    31: 

    直接运行该ASP.NET MVC程序之后,会得到如下图所示的输出结果,通过跨域调用Web API获得的联系人列表正常地显示出来。

    三、针对JSONP的请求和响应

    如下所示的针对JSONP的Ajax请求和响应内容。可以看到请求的URL中通过查询字符串“callback”提供了JavaScript回调函数的名称,而响应的主体部分不是单纯的JSON对象,而是将JSON对象填充到回调返回中而生成的一个函数调用语句。

    1: GET  &_=1386232694514 HTTP/1.1
    2: Host: localhost:3721
    3: Connection: keep-alive
    4: Accept: */*
    5: User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
    6: Referer: http://localhost:9527/
    7: Accept-Encoding: gzip,deflate,sdch
    8: 
    9: HTTP/1.1 200 OK
    10: Cache-Control: no-cache
    11: Pragma: no-cache
    12: Content-Type: application/json; charset=utf-8
    13: Expires: -1
    14: Server: Microsoft-IIS/8.0
    15: X-AspNet-Version: 4.0.30319
    16: X-SourceFiles: =?UTF-8?B?RTpc5oiR55qE6JGX5L2cXEFTUC5ORVQgV2ViIEFQSeahhuaetuaPreenmFxOZXcgU2FtcGxlc1xDaGFwdGVyIDE0XFMxNDAzXFdlYkFwaVxhcGlcY29ud?=
    17: X-Powered-By: ASP.NET
    18: Date: Thu, 05 Dec 2013 08:38:15 GMT
    19: Content-Length: 248
    20: 
    21: jQuery110205729522893670946_1386232694513([{"Name":"张三","PhoneNo":"123","EmailAddress":"zhangsan@gmail.com"},{"Name":"李四","PhoneNo":"456","EmailAddress":"lisi@gmail.com"},{"Name":"王五","PhoneNo":"789","EmailAddress":}])

     

    CORS系列文章
    [1]
    [2]
    [3]
    [4]
    [5]
    [6]
    [7]
    [8]

    转载地址:http://lgvbo.baihongyu.com/

    你可能感兴趣的文章
    minGW, cygwin, GnuWin32【C++的跨平台交叉编译问题】
    查看>>
    我的Dll(动态链接库)学习笔记(转)
    查看>>
    应用程序域
    查看>>
    有向图的拓扑排序算法JAVA实现
    查看>>
    HTML页面跳转的5种方法
    查看>>
    ArcGIS Engine开发之旅02--ArcGIS Engine中的类库
    查看>>
    李洪强-C语言5-函数
    查看>>
    开源监控利器grafana
    查看>>
    Android获取当前时间与星期几
    查看>>
    jenkins2 multibranch
    查看>>
    Css定位-定位
    查看>>
    sort,uniq命令
    查看>>
    am335x 电容屏驱动添加。
    查看>>
    JavaScript Unicode字符操作
    查看>>
    rhel-server-7.2-x86_64无法联网(VMware环境)
    查看>>
    Nginx配置中的log_format用法梳理(设置详细的日志格式)
    查看>>
    Atitit 软件工程概览attilax总结
    查看>>
    优化LibreOffice如此简单
    查看>>
    【Oracle 数据迁移】环境oracle 11gR2,exp无法导出空表的表结构【转载】
    查看>>
    秒杀系统设计方案
    查看>>