我正在使用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方法?
Answers:
问了这个问题之后,我终于找到了我需要的东西,所以我正在回答。
我遇到了这个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);
}
})
看来效果很好。
GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
这是用于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网络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);
}
}
这是具有一些改进的更新版本,可与Web API的RTM版本一起使用。
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 Mapping
会将属性绑定到lambda中,因此以后可以在中访问它TryMatchMediaType
。如果您有更优雅的方式,请发表评论!
不幸的是,我没有足够的声誉来发表评论,因此我将发布答案。@Justin提出了与标准JsonFormatter一起运行WebApiContrib.Formatting.Jsonp格式化程序的问题。该问题已在最新版本(实际上是在一段时间之前发布)中得到解决。此外,它应与最新的Web API版本一起使用。
约翰·托马斯。上面的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()));
对于正在使用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。
看看这个。看看是否有帮助。
如果上下文是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