使用 ASP.NET Web API 的 JSONP

我正在使用 Web API 在 ASP.MVC MVC 4中创建一组新的服务。目前为止,还不错。我已经创建了这个服务并使其工作,现在我正试图使用 JQuery 来使用它。我可以使用 Fiddler 返回 JSON 字符串,这看起来没什么问题,但是因为服务存在于一个单独的站点上,尝试使用带有“ Not Alallow”的 JQuery 错误调用它。因此,这显然是我需要使用 JSONP 的情况。

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

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

64445 次浏览

在问了这个问题之后,我终于找到了我需要的东西,所以我正在回答它。

我偶然发现了这个 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);
}
})

看起来效果不错。

当然,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);
}
}

下面是用于 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);
}
}

约珀尔,托马斯。上面 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);
}

JSONP 只能处理 Http GET 请求。在 asp.net web api 中有一个 CORS 支持,可以很好地处理所有 http 动词。

这篇文章可能对你有帮助。

下面是一个带有一些改进的更新版本,它可以与 Web API 的 RTM 版本一起工作。

  • 根据请求自己的 Accept-Encoding标头选择正确的编码。前面示例中的 new StreamWriter()只使用 UTF-8。对 base.WriteToStreamAsync的调用可能使用不同的编码,导致输出损坏。
  • 将 JSONP 请求映射到 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>参数的“骇客行为”,但这是解决它所解决的问题的最快方法——因为 C # 只有静态内部类,所以它看不到 CallbackQueryParameter属性。将 Func传入绑定到 lambda 中的属性,因此 Mapping以后可以在 TryMatchMediaType中访问它。如果你有更优雅的方式,请评论!

更新

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);
}
}

对于那些正在使用 HttpSelfHostServer 的用户,这部分代码在 HttpContext 上将会失败。当前,因为它不存在于自主服务器上。

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);
}

请求。方法将提供“ GET”、“ POST”等,并且 GetQueryNameValuePair 可以检索?回调参数。因此,我修改后的代码看起来是这样的:

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 的 JSONP

您可以像下面这样使用 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;
}

不用托管您自己的 JSONP 格式化程序版本,您可以安装已经实现了一个的 格式化 NuGet 包(选择适合您的。NET 架构)。

将此格式化程序添加到 Application_Start:

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

不幸的是,我没有足够的声誉来评论,所以我会发布一个答案。@ Justin 提出了与标准 JsonFormatter 一起运行 格式化格式化程序的问题。这个问题在最新版本(实际上是前一段时间发布的)中得到了解决。此外,它应该可以与最新的 WebAPI 版本一起使用。

如果上下文是 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 中添加 JsonpFormater,请参考屏幕截图,< img src = “ https://i.stack.imgur.com/U6wzS.png”alt = “ enter image description here”> < img src = “ https://i.stack.imgur.com/U6wzS.png”alt = “ enter image description here”>

Jquery 代码 enter image description here

2)启用高尔夫球 -

启用我们需要添加微软的 cors。AspNet.WebApi.Cors nuget 包,需要在 WebApiConfig.cs 中启用 Cors

enter image description here

如需更多参考,您可以使用以下链接参考我在 GitHub 上的示例回购。 Https://github.com/mahesh353/ninject