我正在使用Web API在ASP.MVC MVC 4中创建一组新服务 . 到目前为止,这很棒 . 我已经创建了该服务并使其工作,现在我正在尝试使用JQuery来使用它 . 我可以使用Fiddler取回JSON字符串,它似乎没问题,但是因为该服务存在于一个单独的站点上,所以尝试使用“不允许”的JQuery错误调用它 . 所以,这显然是我需要使用JSONP的情况 .
我知道Web API是新的,但我希望有人可以帮助我 .
如何使用JSONP调用Web API方法?
您可以像这样使用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; }
在提出这个问题之后,我终于找到了我需要的东西,所以我正在回答它 .
我碰到了这个JsonpMediaTypeFormatter . 通过执行以下操作将其添加到global.asax的 Application_Start 中:
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); } }
当然Brian的答案是正确的,但如果您已经使用了Json.Net格式化程序,它为您提供了相当快的json日期和更快的序列化,那么您不能只为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's implementation对我来说最适合RC .
JSONP仅适用于Http GET请求 . asp.net web api中有一个CORS支持,适用于所有http动词 .
This文章可能对您有所帮助 .
Updated
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 标头选择正确的编码 . 前面示例中的 new StreamWriter() 只使用UTF-8 . 对 base.WriteToStreamAsync 的调用可能使用不同的编码,导致输出损坏 .
Accept-Encoding
new StreamWriter()
base.WriteToStreamAsync
将JSONP请求映射到 application/javascript Content-Type 标头;上一个示例将输出JSONP,但使用 application/json 标头 . 这项工作是在嵌套的 Mapping 类中完成的(参见Best content type to serve JSONP?)
application/javascript
Content-Type
application/json
Mapping
放弃 StreamWriter 的构造和刷新开销,直接获取字节并将它们写入输出流 .
StreamWriter
使用任务并行库的 ContinueWith 机制将多个任务链接在一起,而不是等待任务 .
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 中访问它 . 如果你有更优雅的方式,请评论!
Func<string>
CallbackQueryParameter
Func
TryMatchMediaType
johperl,托马斯 . 上面的Peter Moberg给出的答案对于RC版本来说应该是正确的,因为他继承的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格式化程序版本(选择适用于您的.NET Framework的版本) .
将此格式化程序添加到 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 with Web API
如果上下文是 Web Api ,感谢并参考 010227leo 的答案,则必须考虑 WebContext.Current 值,这将是 null .
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); } }
我们可以用两种方式解决CORS(跨域资源共享)问题,
1)使用Jsonp 2)启用Cors
1)使用Jsonp-使用Jsonp我们需要安装WebApiContrib.Formatting.Jsonp nuget包并需要在WebApiConfig.cs中添加JsonpFormmater参考截图,
Jquery代码
2)启用Cors -
启用我们需要添加Microsoft.AspNet.WebApi.Cors nuget包的cors,需要在WebApiConfig.cs中启用cors参考截图
有关更多参考,您可以使用以下链接在GitHub上引用我的样本仓库 . https://github.com/mahesh353/Ninject.WebAPi/tree/develop
15 回答
您可以像这样使用ActionFilterAttribute:
然后把它放在你的行动上:
在提出这个问题之后,我终于找到了我需要的东西,所以我正在回答它 .
我碰到了这个JsonpMediaTypeFormatter . 通过执行以下操作将其添加到global.asax的
Application_Start
中:你很高兴看到这样的JQuery AJAX调用:
它似乎工作得很好 .
以下是与WebAPI RC一起使用的JsonpMediaTypeFormatter的更新版本:
当然Brian的答案是正确的,但如果您已经使用了Json.Net格式化程序,它为您提供了相当快的json日期和更快的序列化,那么您不能只为jsonp添加第二个格式化程序,您必须将两者结合起来 . 无论如何最好使用它,正如Scott Hanselman所说,ASP.NET Web API的发布默认会使用Json.Net序列化程序 .
Rick Strahl's implementation对我来说最适合RC .
JSONP仅适用于Http GET请求 . asp.net web api中有一个CORS支持,适用于所有http动词 .
This文章可能对您有所帮助 .
Updated
这是一个带有若干改进的更新版本,它与RTM版本的Web API一起使用 .
根据请求自己的
Accept-Encoding
标头选择正确的编码 . 前面示例中的new StreamWriter()
只使用UTF-8 . 对base.WriteToStreamAsync
的调用可能使用不同的编码,导致输出损坏 .将JSONP请求映射到
application/javascript
Content-Type
标头;上一个示例将输出JSONP,但使用application/json
标头 . 这项工作是在嵌套的Mapping
类中完成的(参见Best content type to serve JSONP?)放弃
StreamWriter
的构造和刷新开销,直接获取字节并将它们写入输出流 .使用任务并行库的
ContinueWith
机制将多个任务链接在一起,而不是等待任务 .码:
我知道内部类构造函数中
Func<string>
参数的"hackiness",但它是解决它所解决问题的最快方法 - 因为C#只有静态内部类,所以它看不到CallbackQueryParameter
属性 . 传递Func
将绑定lambda中的属性,因此Mapping
稍后将能够在TryMatchMediaType
中访问它 . 如果你有更优雅的方式,请评论!johperl,托马斯 . 上面的Peter Moberg给出的答案对于RC版本来说应该是正确的,因为他继承的JsonMediaTypeFormatter已经使用了NewtonSoft Json序列化器,所以他应该使用任何更改 .
然而,为什么人们仍然在使用参数,当你可以只做以下
您可以安装WebApiContrib.Formatting.Jsonp NuGet包而不是托管您自己的JSONP格式化程序版本(选择适用于您的.NET Framework的版本) .
将此格式化程序添加到
Application_Start
:不幸的是,我不会发布一个答案 . @Justin提出了在标准JsonFormatter旁边运行WebApiContrib.Formatting.Jsonp格式化程序的问题 . 该问题已在最新版本中解决(实际上已在前一段时间发布) . 此外,它应该与最新的Web API版本一起使用 .
对于那些使用HttpSelfHostServer的人来说,这部分代码将在HttpContext.Current上失败,因为它在自托管服务器上不存在 .
但是,您可以通过此覆盖拦截自主“上下文” .
request.Method将为您提供“GET”,“POST”等,GetQueryNameValuePairs可以检索?callback参数 . 因此,我修改后的代码如下:
希望这有助于你们中的一些人 . 这样您就不一定需要HttpContext垫片 .
C .
检查一下 . 看看它是否有帮助 .
JSONP with Web API
如果上下文是
Web Api
,感谢并参考010227leo
的答案,则必须考虑WebContext.Current
值,这将是null
.所以我将他的代码更新为:
我们可以用两种方式解决CORS(跨域资源共享)问题,
1)使用Jsonp 2)启用Cors
1)使用Jsonp-使用Jsonp我们需要安装WebApiContrib.Formatting.Jsonp nuget包并需要在WebApiConfig.cs中添加JsonpFormmater参考截图,
Jquery代码
2)启用Cors -
启用我们需要添加Microsoft.AspNet.WebApi.Cors nuget包的cors,需要在WebApiConfig.cs中启用cors参考截图
有关更多参考,您可以使用以下链接在GitHub上引用我的样本仓库 . https://github.com/mahesh353/Ninject.WebAPi/tree/develop