java 调用webapi jsonp_通过扩展让ASP.NET Web API支持JSONP

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

一、JsonpMediaTypeFormatter

在《[CORS:跨域资源共享] 同源策略与JSONP》,我们是在具体的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对象。

f37a0a8a99db21911721730b2a35d9d2.png

二、将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获得的联系人列表正常地显示出来。

f4d32d4c61ec23a675c279e16fbbc6bd.png

三、针对JSONP的请求和响应

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

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":wangwu@gmail.com}])

CORS系列文章

[1] 同源策略与JSONP

[2] 利用扩展让ASP.NET Web API支持JSONP

[3] W3C的CORS规范

[4] 利用扩展让ASP.NET Web API支持CORS

[5] ASP.NET Web API自身对CORS的支持: 从实例开始

[6] ASP.NET Web API自身对CORS的支持: CORS授权策略的定义和提供

[7] ASP.NET Web API自身对CORS的支持: CORS授权检验的实施

[8] ASP.NET Web API自身对CORS的支持: CorsMessageHandler

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值