我正在使用 Web API 在 ASP.MVC MVC 4 中创建一组新的服务。到目前为止,这很棒。我已经创建了服务并让它工作,现在我正在尝试使用 JQuery 来使用它。我可以使用 Fiddler 取回 JSON 字符串,这似乎没问题,但是由于该服务存在于一个单独的站点上,因此尝试使用带有“不允许”的 JQuery 错误来调用它。所以,这显然是我需要使用 JSONP 的情况。
我知道 Web API 是新的,但我希望有人可以帮助我。
如何使用 JSONP 调用 Web API 方法?
我正在使用 Web API 在 ASP.MVC MVC 4 中创建一组新的服务。到目前为止,这很棒。我已经创建了服务并让它工作,现在我正在尝试使用 JQuery 来使用它。我可以使用 Fiddler 取回 JSON 字符串,这似乎没问题,但是由于该服务存在于一个单独的站点上,因此尝试使用带有“不允许”的 JQuery 错误来调用它。所以,这显然是我需要使用 JSONP 的情况。
我知道 Web API 是新的,但我希望有人可以帮助我。
如何使用 JSONP 调用 Web API 方法?
在问了这个问题之后,我终于找到了我需要的东西,所以我正在回答它。
我遇到了这个JsonpMediaTypeFormatter。通过执行以下操作将其添加到Application_Start
global.asax 中:
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 格式化程序,它可以为您提供漂亮的 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 的实现最适合我使用 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
标头选择正确的编码。前面new StreamWriter()
示例中的 UTF-8 将简单地使用。调用base.WriteToStreamAsync
可能使用不同的编码,导致输出损坏。application/javascript
Content-Type
标头;前面的示例将输出 JSONP,但带有application/json
标头。这项工作在嵌套Mapping
类中完成(参见提供 JSONP 的最佳内容类型?)StreamWriter
直接获取字节并将它们写入输出流。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
in 会绑定 lambda 中的属性,因此Mapping
稍后可以在 in 中访问它TryMatchMediaType
。如果您有更优雅的方式,请发表评论!
不幸的是,我没有足够的声誉来发表评论,所以我会发布一个答案。@Justin 提出了在标准 JsonFormatter 旁边运行WebApiContrib.Formatting.Jsonp格式化程序的问题。该问题已在最新版本中得到解决(实际上是前一段时间发布的)。此外,它应该适用于最新的 Web API 版本。
乔珀尔,托马斯。上面 Peter Moberg 给出的答案对于 RC 版本应该是正确的,因为他继承的 JsonMediaTypeFormatter 已经使用了 NewtonSoft Json 序列化程序,因此他所拥有的应该可以在没有任何更改的情况下工作。
但是,当您可以执行以下操作时,为什么人们仍在使用 out 参数
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()));
对于那些使用 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 shim。
C。
看看这个。看看有没有帮助。
如果上下文是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参考截图,
2) 启用 Cors -
要启用 cors,我们需要添加 Microsoft.AspNet.WebApi.Cors nuget 包,并且需要在 WebApiConfig.cs 中启用 cors 参考截图
如需更多参考,您可以使用以下链接在 GitHub 上参考我的示例存储库。 https://github.com/mahesh353/Ninject.WebAPi/tree/develop