asp.net web api自定义Formatter之JsonpMediaTypeFormatter

asp.net web api自定义Formatter之JsonpMediaTypeFormatter

6792发表于2016-05-27

这前我有写过jsonp及为什么要用Jsonp的文章。今天我就来说说怎么自定义一个MediaTypeFormatter在web api的服务端实现Jsonp。

接口地址1:http://localhost:10019/api/Values/Get

调用地址2:http://localhost:16949/Test/Index

两个地址的端口是不一样的,浏览器会视为两个域,浏览器有脚步安全机制,如果地址2中的js要用ajax调用地址2中的接口返回数据是不行的,会报以下错误:

XMLHttpRequest cannot load http://localhost:10019/api/Values/Get/1/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:16949' is therefore not allowed access.

下面通过具体的实例说明一个Jsonp在web api前后台的完整运用。

1、web api的服务端接口(地址1)

ValuesController:

using System;
using System.Collections.Generic;
using System.linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace Lanhu.seoDayCharge.API.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "value"+id;
        }

        // POST api/values
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }
}

其中我们用到里面的这个方法:

// GET api/values/5
public string Get(int id)

2、自定义JsonpMediaTypeFormatter(地址1)

JsonpMediaTypeFormatter:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;

namespace Lanhu.seoDayCharge.API.Infrastructure
{
    public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
    {
        private readonly HttpRequestMessage _request;
        private string _callbackQueryParameter;
        public JsonpMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(DefaultMediaType);
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
            MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
        }

        public JsonpMediaTypeFormatter(HttpRequestMessage request)
            : this()
        {
            this._request = request;
        }

        public string CallbackQueryParameter
        {
            get { return _callbackQueryParameter ?? "callback"; }
            set { _callbackQueryParameter = value; }
        }

        public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type,
            HttpRequestMessage request,
            MediaTypeHeaderValue mediaType)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (request == null)
                throw new ArgumentNullException("request");
            return new JsonpMediaTypeFormatter(request);
        }


        public override Task WriteToStreamAsync(Type type,
            object value,
            Stream stream,
            HttpContent content,
            TransportContext transportContext)
        {
            string callback;
            //判断请求是否是Jsonp,通过有是否有参数callback为判断依据
            if (IsJsonpRequest(_request, out callback))
            {
                return Task.Factory.StartNew(() =>
                {
                    //开始加入callback参数值(
                    var writer = new StreamWriter(stream);
                    writer.Write(callback + "(");
                    writer.Flush();

                    base.WriteToStreamAsync(type,
                        value, stream, content, transportContext)
                            .ContinueWith(_ =>
                            {
                                writer.Write(")");
                                writer.Flush();
                            });
                    //结尾加上")"
                });
            }

            return base.WriteToStreamAsync(type,value, stream, content, transportContext);
        }

        private bool IsJsonpRequest(HttpRequestMessage request,
            out string callback)
        {
            callback = null;
            if (request == null || request.Method != HttpMethod.Get)
            {
                return false;
            }
            var query =HttpUtility.ParseQueryString(request.RequestUri.Query);
            callback = query[CallbackQueryParameter];
            return !string.IsNullOrEmpty(callback);
        }
    }
}

上面定义了一个MediaTypeFormatter,最终结果并把返回的数据用回调函数callback的看包裹起来如:

jQuery17209830529219470918_1464278101908("value1")

3、注册自定义的JsonpMediaTypeFormatter(地址1)

JsonpMediaTypeFormatter:

using Lanhu.seoDayCharge.API.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace Lanhu.seoDayCharge.API
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {

            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

            //注册自定义的JsonpMediaTypeFormatter
            var config = GlobalConfiguration.Configuration;
            config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

注意:上面 注册时一定要用

config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

表示JsonpMediaTypeFormatter为第一个这样JsonpMediaTypeFormatter才人生效。因为config.Formatters是一个集合,里面有框架内置的的一些MediaTypeFormatter,这或注册的顺序有关系,先注册先执行。

把我们定义的JsonpMediaTypeFormatter插入到第一个位置表示最新判断。

4、视图中的js调用(地址2)

Index.cshtml

@{
    ViewBag.Title = "Jsonp测试Demo";
}

<h2>Jsonp测试Demo</h2>
<script src="~/Content/js/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        $.getJSON("http://localhost:10019/api/Values/Get/1/jsonp/?callback=?", function (data) {
            alert(data);
        });
    })
  
</script>

5、运行效果

6、浏览器口的请求原理及返回数

真正请求地址:

http://localhost:10019/api/Values/Get/1/jsonp/?callback=jQuery17207970995132345706_1464276841289&_=1464276841316

返回结果:jQuery17207970995132345706_1464276841289("value1")


小编蓝狐