带有ASP.NET Web API的JSONP

我正在使用Web API在ASP.MVC MVC 4中创build一组新的服务。 到目前为止,这是伟大的。 我已经创build了服务,并得到它的工作,现在我正在尝试使用JQuery使用它。 我可以使用Fiddler取回JSONstring,而且看起来没问题,但是因为这个服务存在于一个单独的站点上,所以试图用JQuery错误来调用它,使用“Not Allowed”。 所以,这显然是我需要使用JSONP的情况。

我知道Web API是新的,但我希望有人能帮助我。

如何使用JSONP调用Web API方法?

问了这个问题后,我终于find了我所需要的,所以我正在回答。

我碰到了这个JsonpMediaTypeFormatter 。 把它添加到你的global.asax的Application_Start中,做到这一点:

 var config = GlobalConfiguration.Configuration; config.Formatters.Insert(0, new JsonpMediaTypeFormatter()); 

而且您可以使用如下所示的JQuery AJAX调用:

 $.ajax({ url: 'http://myurl.com', type: 'GET', dataType: 'jsonp', success: function (data) { alert(data.MyProperty); } }) 

这似乎工作得很好。

这是用于WebAPI RC的JsonpMediaTypeFormatter的更新版本:

 public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter { private string callbackQueryParameter; public JsonpMediaTypeFormatter() { SupportedMediaTypes.Add(DefaultMediaType); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript")); MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType)); } public string CallbackQueryParameter { get { return callbackQueryParameter ?? "callback"; } set { callbackQueryParameter = value; } } public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) { string callback; if (IsJsonpRequest(out callback)) { return Task.Factory.StartNew(() => { var writer = new StreamWriter(stream); writer.Write(callback + "("); writer.Flush(); base.WriteToStreamAsync(type, value, stream, content, transportContext).Wait(); writer.Write(")"); writer.Flush(); }); } else { return base.WriteToStreamAsync(type, value, stream, content, transportContext); } } private bool IsJsonpRequest(out string callback) { callback = null; if (HttpContext.Current.Request.HttpMethod != "GET") return false; callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter]; return !string.IsNullOrEmpty(callback); } } 

你可以像这样使用ActionFilterAttribute:

 public class JsonCallbackAttribute : ActionFilterAttribute { private const string CallbackQueryParameter = "callback"; public override void OnActionExecuted(HttpActionExecutedContext context) { var callback = string.Empty; if (IsJsonp(out callback)) { var jsonBuilder = new StringBuilder(callback); jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result); context.Response.Content = new StringContent(jsonBuilder.ToString()); } base.OnActionExecuted(context); } private bool IsJsonp(out string callback) { callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter]; return !string.IsNullOrEmpty(callback); } } 

然后把它放在你的行动:

 [JsonCallback] public IEnumerable<User> User() { return _user; } 

当然,Brian的答案是正确的,但是如果你已经在使用Json.Net格式化程序,它给了你漂亮的jsondate和更快的序列化,那么你不能只为jsonp添加第二个格式化程序,你必须结合这两个。 无论如何,最好使用它,正如Scott Hanselman所说,ASP.NET Web API的发布默认使用Json.Net序列化程序。

 public class JsonNetFormatter : MediaTypeFormatter { private JsonSerializerSettings _jsonSerializerSettings; private string callbackQueryParameter; public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings) { _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings(); // Fill out the mediatype and encoding we support SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); Encoding = new UTF8Encoding(false, true); //we also support jsonp. SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript")); MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", "application/json")); } public string CallbackQueryParameter { get { return callbackQueryParameter ?? "jsoncallback"; } set { callbackQueryParameter = value; } } protected override bool CanReadType(Type type) { if (type == typeof(IKeyValueModel)) return false; return true; } protected override bool CanWriteType(Type type) { return true; } protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { // Create a serializer JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings); // Create task reading the content return Task.Factory.StartNew(() => { using (StreamReader streamReader = new StreamReader(stream, Encoding)) { using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { return serializer.Deserialize(jsonTextReader, type); } } }); } protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { string callback; var isJsonp = IsJsonpRequest(formatterContext.Response.RequestMessage, out callback); // Create a serializer JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings); // Create task writing the serialized content return Task.Factory.StartNew(() => { using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false }) { if (isJsonp) { jsonTextWriter.WriteRaw(callback + "("); jsonTextWriter.Flush(); } serializer.Serialize(jsonTextWriter, value); jsonTextWriter.Flush(); if (isJsonp) { jsonTextWriter.WriteRaw(")"); jsonTextWriter.Flush(); } } }); } private bool IsJsonpRequest(HttpRequestMessage request, out string callback) { callback = null; if (request.Method != HttpMethod.Get) return false; var query = HttpUtility.ParseQueryString(request.RequestUri.Query); callback = query[CallbackQueryParameter]; return !string.IsNullOrEmpty(callback); } } 

Rick Strahl的实施对我来说最适合RC。

JSONP只适用于HTTP GET请求。 在asp.net web api中有一个CORS支持,可以和所有的http动词一起使用。

本文可能对您有所帮助。

更新

 public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter { private string callbackQueryParameter; public JsonpMediaTypeFormatter() { SupportedMediaTypes.Add(DefaultMediaType); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript")); MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType)); } public string CallbackQueryParameter { get { return callbackQueryParameter ?? "callback"; } set { callbackQueryParameter = value; } } public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { string callback; if (IsJsonpRequest(out callback)) { return Task.Factory.StartNew(() => { var writer = new StreamWriter(writeStream); writer.Write(callback + "("); writer.Flush(); base.WriteToStreamAsync(type, value, writeStream, content, transportContext).Wait(); writer.Write(")"); writer.Flush(); }); } else { return base.WriteToStreamAsync(type, value, writeStream, content, transportContext); } } private bool IsJsonpRequest(out string callback) { callback = null; if (HttpContext.Current.Request.HttpMethod != "GET") return false; callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter]; return !string.IsNullOrEmpty(callback); } } 

这里有一个更新的版本,有几个改进,可以与RTM版本的Web API一起使用。

  • 根据请求自己的Accept-Encoding标头select正确的编码。 前面例子中的new StreamWriter()将简单地使用UTF-8。 对base.WriteToStreamAsync的调用可能使用不同的编码,导致输出损坏。
  • 将JSONP请求映射到application/javascript Content-Type标头; 前面的例子会输出JSONP,但是使用application/json头。 这个工作是在嵌套的Mapping类中完成的(比较最好的内容types为JSONP提供服务 )
  • 放弃StreamWriter的构造和刷新开销,直接获取字节并将它们写入输出stream。
  • 而不是等待任务,使用任务并行库的ContinueWith机制将多个任务链接在一起。

码:

 public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter { private string _callbackQueryParameter; public JsonpMediaTypeFormatter() { SupportedMediaTypes.Add(DefaultMediaType); SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/javascript")); // need a lambda here so that it'll always get the 'live' value of CallbackQueryParameter. MediaTypeMappings.Add(new Mapping(() => CallbackQueryParameter, "application/javascript")); } public string CallbackQueryParameter { get { return _callbackQueryParameter ?? "callback"; } set { _callbackQueryParameter = value; } } public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { var callback = GetCallbackName(); if (!String.IsNullOrEmpty(callback)) { // select the correct encoding to use. Encoding encoding = SelectCharacterEncoding(content.Headers); // write the callback and opening paren. return Task.Factory.StartNew(() => { var bytes = encoding.GetBytes(callback + "("); writeStream.Write(bytes, 0, bytes.Length); }) // then we do the actual JSON serialization... .ContinueWith(t => base.WriteToStreamAsync(type, value, writeStream, content, transportContext)) // finally, we close the parens. .ContinueWith(t => { var bytes = encoding.GetBytes(")"); writeStream.Write(bytes, 0, bytes.Length); }); } return base.WriteToStreamAsync(type, value, writeStream, content, transportContext); } private string GetCallbackName() { if (HttpContext.Current.Request.HttpMethod != "GET") return null; return HttpContext.Current.Request.QueryString[CallbackQueryParameter]; } #region Nested type: Mapping private class Mapping : MediaTypeMapping { private readonly Func<string> _param; public Mapping(Func<string> discriminator, string mediaType) : base(mediaType) { _param = discriminator; } public override double TryMatchMediaType(HttpRequestMessage request) { if (request.RequestUri.Query.Contains(_param() + "=")) return 1.0; return 0.0; } } #endregion } 

我知道内部类构造函数中的Func<string>参数的“hackiness”,但它是解决它所解决的问题的最快方法 – 因为C#只有静态内部类,所以不能看到CallbackQueryParameter属性。 传入Func将绑定lambda中的属性,以便Mapping可以稍后在TryMatchMediaType访问它。 如果你有更优雅的方式,请评论!

johperl,托马斯。 上面的Peter Moberg给出的答案对于RC版本应该是正确的,因为他inheritance的JsonMediaTypeFormatter已经使用了NewtonSoft Json序列化程序,所以他应该使用任何更改。

但是,为什么人们仍在使用参数,何时才能做到以下几点

 public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext) { var isJsonpRequest = IsJsonpRequest(); if(isJsonpRequest.Item1) { return Task.Factory.StartNew(() => { var writer = new StreamWriter(stream); writer.Write(isJsonpRequest.Item2 + "("); writer.Flush(); base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext).Wait(); writer.Write(")"); writer.Flush(); }); } return base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext); } private Tuple<bool, string> IsJsonpRequest() { if(HttpContext.Current.Request.HttpMethod != "GET") return new Tuple<bool, string>(false, null); var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter]; return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback); } 

您可以安装WebApiContrib.Formatting.Jsonp NuGet包,而不是自己的JSONP格式化版本(已经实现的版本)(select适用于您的.NET Framework的版本)。

将此格式化Application_Start添加到Application_Start

 GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter())); 

不幸的是,我没有足够的评价,所以我会发表一个答案。 @Justin提出了与标准JsonFormatter一起运行WebApiContrib.Formatting.Jsonp格式化程序的问题。 这个问题已经在最新版本中解决了(实际上是前一段时间发布的)。 另外,它应该使用最新的Web API版本。

对于那些正在使用HttpSelfHostServer的人来说,这部分代码在HttpContext.Current上将失败,因为它在自己的主机服务器上不存在。

 private Tuple<bool, string> IsJsonpRequest() { if(HttpContext.Current.Request.HttpMethod != "GET") return new Tuple<bool, string>(false, null); var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter]; return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback); } 

但是你可以通过这个覆盖来拦截自己的主机“上下文”。

 public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType) { _method = request.Method; _callbackMethodName = request.GetQueryNameValuePairs() .Where(x => x.Key == CallbackQueryParameter) .Select(x => x.Value) .FirstOrDefault(); return base.GetPerRequestFormatterInstance(type, request, mediaType); } 

request.Method会给你“GET”,“POST”等,GetQueryNameValuePairs可以检索?callback参数。 因此我修改后的代码如下所示:

 private Tuple<bool, string> IsJsonpRequest() { if (_method.Method != "GET") return new Tuple<bool, string>(false, null); return new Tuple<bool, string>(!string.IsNullOrEmpty(_callbackMethodName), _callbackMethodName); } 

希望这有助于你们中的一些人。 这样你不一定需要一个HttpContext垫片。

C。

检查一下。 看看是否有帮助。

JSONP与Web API

如果上下文是Web Api ,则感谢并引用010227leo的答案,则必须考虑WebContext.Current值,该值将为null

所以我更新了他的代码:

 public class JsonCallbackAttribute : ActionFilterAttribute { private const string CallbackQueryParameter = "callback"; public override void OnActionExecuted(HttpActionExecutedContext context) { var callback = context.Request.GetQueryNameValuePairs().Where(item => item.Key == CallbackQueryParameter).Select(item => item.Value).SingleOrDefault(); if (!string.IsNullOrEmpty(callback)) { var jsonBuilder = new StringBuilder(callback); jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result); context.Response.Content = new StringContent(jsonBuilder.ToString()); } base.OnActionExecuted(context); } }